diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index d00e8e4ebc..f5cb9612d6 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -141,11 +141,11 @@ android { buildTypes { debug { - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' + // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' signingConfig signingConfigs.development } release { - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' + // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' signingConfig signingConfigs.publishing } } @@ -278,6 +278,12 @@ tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn << [collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore] } +clean.dependsOn 'cleanNoTranslate' + +task cleanNoTranslate() { + delete ('res/values/no_translate.xml') +} + repositories { ivy { name = "OsmAndBinariesIvy" @@ -290,6 +296,7 @@ repositories { } dependencies { + compile project(":eclipse-compile:appcompat") compile project(path: ":OsmAnd-java", configuration: "android") compile fileTree( dir: "libs", @@ -300,9 +307,6 @@ dependencies { "android-support*.jar", "OsmAndCore_android.jar", "OsmAndCore_wrapper.jar"]) - compile "com.github.ksoichiro:android-observablescrollview:1.5.0" - compile "com.android.support:appcompat-v7:21.0.3" - compile "com.github.shell-software:fab:1.0.5" legacyCompile "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@jar" qtcoredebugCompile "net.osmand:OsmAndCore_androidNativeDebug:0.1-SNAPSHOT@aar" qtcoredebugCompile "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" diff --git a/OsmAnd/libs/android-support-v4.jar b/OsmAnd/libs/android-support-v4.jar deleted file mode 100644 index 4ebdaa9ed9..0000000000 Binary files a/OsmAnd/libs/android-support-v4.jar and /dev/null differ diff --git a/OsmAnd/libs/android-support-v7-appcompat.jar b/OsmAnd/libs/android-support-v7-appcompat.jar deleted file mode 100644 index fdd6c5bf88..0000000000 Binary files a/OsmAnd/libs/android-support-v7-appcompat.jar and /dev/null differ diff --git a/OsmAnd/project.properties b/OsmAnd/project.properties index 74d1d1c29b..fd62b55da2 100644 --- a/OsmAnd/project.properties +++ b/OsmAnd/project.properties @@ -13,6 +13,3 @@ split.density=false target=android-21 dex.force.jumbo=true android.library.reference.1=../eclipse-compile/appcompat -android.library.reference.2=../eclipse-compile/observable -android.library.reference.2=../eclipse-compile/observable -android.library.reference.3=../eclipse-compile/fab diff --git a/OsmAnd/res/layout/osmo_create_group.xml b/OsmAnd/res/layout/osmo_create_group.xml index 81d2f930b0..ea881cc33f 100644 --- a/OsmAnd/res/layout/osmo_create_group.xml +++ b/OsmAnd/res/layout/osmo_create_group.xml @@ -30,7 +30,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/osmo_grop_name_length_alert" - android:textColor="@color/fab_material_red_500"/> + android:textColor="@color/osmbug_closed"/> + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/anim/abc_popup_enter.xml b/eclipse-compile/appcompat/res/anim/abc_popup_enter.xml new file mode 100644 index 0000000000..91664da17e --- /dev/null +++ b/eclipse-compile/appcompat/res/anim/abc_popup_enter.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/anim/abc_popup_exit.xml b/eclipse-compile/appcompat/res/anim/abc_popup_exit.xml new file mode 100644 index 0000000000..db7e8073a8 --- /dev/null +++ b/eclipse-compile/appcompat/res/anim/abc_popup_exit.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml b/eclipse-compile/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml new file mode 100644 index 0000000000..9a23cd2025 --- /dev/null +++ b/eclipse-compile/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/color/switch_thumb_material_dark.xml b/eclipse-compile/appcompat/res/color/switch_thumb_material_dark.xml new file mode 100644 index 0000000000..6153382c7c --- /dev/null +++ b/eclipse-compile/appcompat/res/color/switch_thumb_material_dark.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/color/switch_thumb_material_light.xml b/eclipse-compile/appcompat/res/color/switch_thumb_material_light.xml new file mode 100644 index 0000000000..94d7114821 --- /dev/null +++ b/eclipse-compile/appcompat/res/color/switch_thumb_material_light.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png new file mode 100644 index 0000000000..4d9f861f88 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png index 7a9e9bd2b9..99110085fe 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png index 874edbff62..69ff9dde3a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png index 0d3e1e7a16..9218981b4f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png index a8c390efa2..a58857635f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png new file mode 100644 index 0000000000..b184dbc69d Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png new file mode 100644 index 0000000000..6549c52760 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png index 8e7b62f046..705e0d9b78 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png index adcb9e96c6..b69271067b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png index e51ef280dd..2264398234 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png index 6c36eae2f4..f61e8e3e3c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png index 82459ea944..0fd15563a2 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png index 47263ea749..65ccd8f410 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png index aa23c591e4..b9ff1db574 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png index 03b1aac4e0..70eb073788 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png index 4c17541301..e78bcaf57a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png index 675f3ee928..9a87820577 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png index a30dc06761..8610c50150 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png index 413b220fde..2d971a94bf 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png index 0eaceddf16..ee40812968 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png index f7382d373d..b9baa0cca9 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png index eefd59e523..a87d2cdc76 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png index 2fa6d7e769..1e571f5c6f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png index 555270842a..c09ec90e0f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png index 4ea7afa00e..62fbd2cb50 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png index 596accb8a1..2f6ef9160a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png index 2054530ed2..863ce95f61 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png index f6fd30dcdc..b6d467774e 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png index ca8e9a2778..e01c7392a9 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png index 76a5c53d71..a6b3696e48 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png index 385734ee46..9d8451aab1 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png index de7ac29d6a..9de0263919 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png index 0ebe65e796..56436a1ccc 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png index 21b213579a..4b0b10a7a3 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_text_cursor_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_text_cursor_mtrl_alpha.9.png new file mode 100644 index 0000000000..5e0bf843ec Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_text_cursor_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png index b9a81bec80..5b13bc17ad 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png index 368262986a..0078bf6b6b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png index ce577e5007..a74ab260c1 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png index 7c305ab71d..6282df4e69 100644 Binary files a/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png index dcdd03b7fa..2e1062fa99 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png index 5338f02a42..a262b0c872 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png index fd27a0f1bf..9ed43ca4d7 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png index d6e0b99841..4cd8a27c8b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png index 482e142d17..e300b7cfc7 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png index 5aaad7eb53..05b1e119c4 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png index c0246b3c1f..aa7b3238b3 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png index 74160c38cc..d02a5da113 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png index 753496a865..a188f2fbec 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png index 8a4e22efc2..e95ba942d6 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png index 694426772e..87bf8d36b1 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png index 2d63334637..b097e48a26 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png index 2b308bf9c9..de37158159 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png index 9b5be204b7..ac86165d5e 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png index 07d0a5d30c..8b2adf6bd5 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png index bd1029d80a..0b895042f8 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png index 33f6587983..7dc69341d2 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png index a5015c6823..884cd1279b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png index 2f12fc0d57..90fe333ac3 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png index b1641732e9..930630de58 100644 Binary files a/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png new file mode 100644 index 0000000000..fa0ed8fe95 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png index 70793c4748..7a9fcbcbfe 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png index 8aa1be2b6d..3b052e5774 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png index 54ef48082e..96a86931cb 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png index 4f8a162a0b..827d63425d 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png new file mode 100644 index 0000000000..09084757b1 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png new file mode 100644 index 0000000000..a5a437f3d1 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png index 03d3dfb5cd..b2191dad9f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png index 66358308d9..2a94e6e4ce 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png index ae8cccdd6f..038e000864 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png index 667435189e..8043d4cac8 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png index bbc43b19a9..e80681aeb7 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png index 42ac8ca683..9603e76e27 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png index b5f6176586..44c1423216 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png index 6aa238c562..80c069557c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png index aa4f1c213a..3966d6ad8c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png index 1d8ad18a0c..017e45edec 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png index d40353c517..ec0cff4935 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png index 488d1ab7da..966938b9d8 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png index e0d5ac4e5e..d05f969e99 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png index 0fb57b2ea2..451818ce54 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png index fca776fb9a..a216da173d 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png index 070bdbfdbc..1e571f5c6f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png index 00f05d8c97..addb54a226 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png index 3bf8e03623..5fcd5b207a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png index fd0e8d7d73..251b98913d 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png index 061904c42c..01efec045b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png index 92da2f0dd3..f1d1b61708 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png index 42cb6463e4..10851f6c87 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png index 02b25f09fe..91b0cb8d01 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png index e9204993dc..5f55cd5539 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png index bbf59287fd..ed75cb8128 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png index 4918d33fde..fcd81de0c2 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png index b69529cb78..12b0a79c58 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_text_cursor_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_text_cursor_mtrl_alpha.9.png new file mode 100644 index 0000000000..36348a8b9a Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_text_cursor_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png index f3d06fe0e3..3ffa25193c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png index f0e7db873e..0eb61f1521 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png index d7faacf3eb..0c766f30db 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png index 0a36039914..4f66d7adce 100644 Binary files a/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/eclipse-compile/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png new file mode 100644 index 0000000000..530ca456da Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/eclipse-compile/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png new file mode 100644 index 0000000000..b527495ee6 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png new file mode 100644 index 0000000000..6284eaaa17 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png index 9244174b91..49025208b6 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png index 5f40d737d7..59a683ab60 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png index d068dbeb8c..03bf49cc5e 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png index 99244967ed..342323b4b5 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png new file mode 100644 index 0000000000..c0333f982c Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png new file mode 100644 index 0000000000..2f29c39cdf Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png index 8a648b8ba4..0d2274389f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png index 435ce2150d..17fc083dff 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png index ed8d341147..600178a98a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png index 27bdcb79e3..c465e82fc9 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png index 84968eedbc..76e07f0970 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png index c10a1b723d..1015e1f443 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png index bd80981c3c..b3fa6bc2f0 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png index a9e6cc5609..c8a6d25853 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png index ce5d4a7ed3..3c5e683e7c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png index bb9d84d3a3..f87733af1c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png index 9f9cb3bfde..9aabc43ce6 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png index 53d08148b1..c039c8e0b0 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png index 7accf52ac3..b57ee1935e 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png index 05cfab7eef..76f2696557 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png index b7d8dc70a1..d0385ba4ce 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png index 0d2836d868..1e571f5c6f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png index b545f8e578..67c25aefff 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png index eda10e6123..17c34a1a93 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png index 29037a0d77..988548a103 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png index f4af926571..15fcf6a322 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png index 88726b6916..65275b38c7 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png index c6a7d4d87c..5b58e76054 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png index 4fda86774c..c67a5ddcd1 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png index a081ceb95d..b5dd854b20 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png index d4bd169b9d..bcf6b7f059 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png index fd47f15e4b..cd1396bca9 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png index 5610d8c8d2..2242d2f94b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_text_cursor_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_text_cursor_mtrl_alpha.9.png new file mode 100644 index 0000000000..666b10a2f2 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_text_cursor_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png index 7174b67fa4..8ff3a8304c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png index 46dad22fb8..e7e693a7b8 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png index 33c1035620..819171ad65 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png index 0226f84968..4def8c8fab 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png new file mode 100644 index 0000000000..4eae28fde7 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png index 0d544d90b0..accf80e4af 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png index 810a02942f..8c82ec3d7a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png index c9af24b3f2..8fc0a9b879 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png index db1d93af67..92b712e5d4 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png new file mode 100644 index 0000000000..78bbeba102 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png new file mode 100644 index 0000000000..c4ba8e64fd Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png index b149e47588..133de5258b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png index 00fb83ec9f..ff3b59630c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png index 1dd64b9ad4..f6d2f3294f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png index c2d6a542cd..39178bf31a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png index 24a194fb88..f54f4f9d11 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png index fc1b8b4426..65cf0c1eb9 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png index 8e1ab5bbfb..d041623731 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png index 5fc17a4d13..9dff893e77 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png index 11a9f9787e..a1f8c33394 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png index cada2fb702..28a3bbf21c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png index 556c30df8d..29a4e52951 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png index f0a0b73737..162ab9847a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png index 66f7d1627b..a1866ba45f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png index 6f60bd3c2b..d967ae70fa 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png index 658c5a5a29..5baef9ff2f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png index b8ac46d17e..987b2bc25a 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png index 76cad17395..8b050e8551 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png index 8f436eaf15..00e370a1a9 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png index d4952eaf09..719c7b5ebf 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png index 1352a1702a..75bd5803fd 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png index 175b82ca6d..9cc366665c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png index aad8a46870..224a08157f 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png index f5c18d0889..2ab970d516 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png index fb7d715fae..ee4bfe7d0b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png index 2e7bc12c1e..6940b603ea 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png index 3e3174d08c..96bec46c2e 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png index 248f4f8604..eeb74c8693 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_text_cursor_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_text_cursor_mtrl_alpha.9.png new file mode 100644 index 0000000000..08ee2b4779 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_text_cursor_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png index 661d5f0a8d..4d3d3a4d05 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png index d7696c3140..c5acb84f04 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png index b6efff3096..30328ae1d2 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png index 2b253fb266..d4f3650622 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png index 5dd0e5ba60..4dc870e49b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png index f0ff1a70f3..4e18de21a6 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png index adef871801..5fa326654e 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png index 44028af07b..c11cb2ec65 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png index d3f2a9a4d8..e075ab8dda 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png index a3caefb7f4..25274ee8a1 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png index 70c2040210..16b0f1d409 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png index 72522081dc..7b2a480a02 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png index 2a6f6ba82d..fe93d873f6 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png index 13cc0fd03e..4b2d05ab0c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png index e232cf7c6b..16e9e14d5e 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png index 8e9041f3ae..129d30f84c 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png index 66fc42f5f0..fa6ab02ba9 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 0000000000..77318c7111 Binary files /dev/null and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png index c873e9b0c8..098c25a1bb 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png index fe00ae5fee..76c4eeb26b 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png index 1086e9d6df..6b8bc0a8e3 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png index 1e4a74c8a9..c2393abe68 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png index 5813179d4c..929be19b09 100644 Binary files a/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png and b/eclipse-compile/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/eclipse-compile/appcompat/res/drawable/abc_btn_borderless_material.xml b/eclipse-compile/appcompat/res/drawable/abc_btn_borderless_material.xml new file mode 100644 index 0000000000..f3894600ba --- /dev/null +++ b/eclipse-compile/appcompat/res/drawable/abc_btn_borderless_material.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/eclipse-compile/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml b/eclipse-compile/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml new file mode 100644 index 0000000000..c50d4b10f0 --- /dev/null +++ b/eclipse-compile/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/eclipse-compile/appcompat/res/drawable/abc_dialog_material_background_dark.xml b/eclipse-compile/appcompat/res/drawable/abc_dialog_material_background_dark.xml new file mode 100644 index 0000000000..41c4a6f842 --- /dev/null +++ b/eclipse-compile/appcompat/res/drawable/abc_dialog_material_background_dark.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/drawable/abc_dialog_material_background_light.xml b/eclipse-compile/appcompat/res/drawable/abc_dialog_material_background_light.xml new file mode 100644 index 0000000000..248b13af94 --- /dev/null +++ b/eclipse-compile/appcompat/res/drawable/abc_dialog_material_background_light.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/drawable/abc_edit_text_material.xml b/eclipse-compile/appcompat/res/drawable/abc_edit_text_material.xml index 754ab18d03..46c4e91200 100644 --- a/eclipse-compile/appcompat/res/drawable/abc_edit_text_material.xml +++ b/eclipse-compile/appcompat/res/drawable/abc_edit_text_material.xml @@ -15,16 +15,15 @@ --> + android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material" + android:insetRight="@dimen/abc_edit_text_inset_horizontal_material" + android:insetTop="@dimen/abc_edit_text_inset_top_material" + android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"> - - - - + + + diff --git a/eclipse-compile/appcompat/res/drawable/abc_ratingbar_full_material.xml b/eclipse-compile/appcompat/res/drawable/abc_ratingbar_full_material.xml new file mode 100644 index 0000000000..535e2da25e --- /dev/null +++ b/eclipse-compile/appcompat/res/drawable/abc_ratingbar_full_material.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/eclipse-compile/appcompat/res/drawable/abc_spinner_textfield_background_material.xml b/eclipse-compile/appcompat/res/drawable/abc_spinner_textfield_background_material.xml new file mode 100644 index 0000000000..d0f46a8097 --- /dev/null +++ b/eclipse-compile/appcompat/res/drawable/abc_spinner_textfield_background_material.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/layout/abc_action_menu_item_layout.xml b/eclipse-compile/appcompat/res/layout/abc_action_menu_item_layout.xml index 150ea50dc3..b1d68274b0 100644 --- a/eclipse-compile/appcompat/res/layout/abc_action_menu_item_layout.xml +++ b/eclipse-compile/appcompat/res/layout/abc_action_menu_item_layout.xml @@ -14,7 +14,7 @@ limitations under the License. --> - - - + - \ No newline at end of file + + + + + + + + + + + diff --git a/eclipse-compile/appcompat/res/layout/abc_dialog_title_material.xml b/eclipse-compile/appcompat/res/layout/abc_dialog_title_material.xml new file mode 100644 index 0000000000..068b9e907c --- /dev/null +++ b/eclipse-compile/appcompat/res/layout/abc_dialog_title_material.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/layout/abc_expanded_menu_layout.xml b/eclipse-compile/appcompat/res/layout/abc_expanded_menu_layout.xml index 20e8b19be9..ef1d1409b4 100644 --- a/eclipse-compile/appcompat/res/layout/abc_expanded_menu_layout.xml +++ b/eclipse-compile/appcompat/res/layout/abc_expanded_menu_layout.xml @@ -14,9 +14,8 @@ limitations under the License. --> - + android:layout_height="wrap_content" /> diff --git a/eclipse-compile/appcompat/res/layout/abc_list_menu_item_layout.xml b/eclipse-compile/appcompat/res/layout/abc_list_menu_item_layout.xml index 1cee43e704..2bd54b0dbe 100644 --- a/eclipse-compile/appcompat/res/layout/abc_list_menu_item_layout.xml +++ b/eclipse-compile/appcompat/res/layout/abc_list_menu_item_layout.xml @@ -14,7 +14,7 @@ limitations under the License. --> - @@ -57,4 +57,4 @@ - + diff --git a/eclipse-compile/appcompat/res/layout/abc_popup_menu_item_layout.xml b/eclipse-compile/appcompat/res/layout/abc_popup_menu_item_layout.xml index 76820e0789..43fa49a6d6 100644 --- a/eclipse-compile/appcompat/res/layout/abc_popup_menu_item_layout.xml +++ b/eclipse-compile/appcompat/res/layout/abc_popup_menu_item_layout.xml @@ -14,7 +14,7 @@ limitations under the License. --> - - + diff --git a/eclipse-compile/appcompat/res/values-af/strings.xml b/eclipse-compile/appcompat/res/values-af/strings.xml index 474f3aa5a2..549ab7654c 100644 --- a/eclipse-compile/appcompat/res/values-af/strings.xml +++ b/eclipse-compile/appcompat/res/values-af/strings.xml @@ -20,9 +20,11 @@ "Navigeer tuis" "Navigeer op" "Nog opsies" + "Vou in" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Soek" + "Soek …" "Soeknavraag" "Vee navraag uit" "Dien navraag in" diff --git a/eclipse-compile/appcompat/res/values-am/strings.xml b/eclipse-compile/appcompat/res/values-am/strings.xml index dbd53d6ec4..9bcea17b8e 100644 --- a/eclipse-compile/appcompat/res/values-am/strings.xml +++ b/eclipse-compile/appcompat/res/values-am/strings.xml @@ -20,9 +20,11 @@ "ወደ መነሻ ይዳስሱ" "ወደ ላይ ይዳስሱ" "ተጨማሪ አማራጮች" + "ሰብስብ" "%1$s፣ %2$s" "%1$s፣ %2$s፣ %3$s" "ፍለጋ" + "ፈልግ…" "የፍለጋ ጥያቄ" "መጠይቅ አጽዳ" "መጠይቅ ያስረክቡ" diff --git a/eclipse-compile/appcompat/res/values-ar/strings.xml b/eclipse-compile/appcompat/res/values-ar/strings.xml index 84d6fbaa70..4ed5f59d45 100644 --- a/eclipse-compile/appcompat/res/values-ar/strings.xml +++ b/eclipse-compile/appcompat/res/values-ar/strings.xml @@ -20,9 +20,11 @@ "التنقل إلى الشاشة الرئيسية" "التنقل إلى أعلى" "خيارات إضافية" + "تصغير" "%1$s، %2$s" "%1$s، %2$s، %3$s" "بحث" + "بحث…" "طلب البحث" "محو طلب البحث" "إرسال طلب البحث" diff --git a/eclipse-compile/appcompat/res/values-bg/strings.xml b/eclipse-compile/appcompat/res/values-bg/strings.xml index 9d87ef7b23..74963a2079 100644 --- a/eclipse-compile/appcompat/res/values-bg/strings.xml +++ b/eclipse-compile/appcompat/res/values-bg/strings.xml @@ -20,9 +20,11 @@ "Придвижване към „Начало“" "Придвижване нагоре" "Още опции" + "Свиване" "„%1$s“ – %2$s" "„%1$s“, „%2$s“ – %3$s" "Търсене" + "Търсете…" "Заявка за търсене" "Изчистване на заявката" "Изпращане на заявката" diff --git a/eclipse-compile/appcompat/res/values-bn-rBD/strings.xml b/eclipse-compile/appcompat/res/values-bn-rBD/strings.xml index ee522c6791..93a599757e 100644 --- a/eclipse-compile/appcompat/res/values-bn-rBD/strings.xml +++ b/eclipse-compile/appcompat/res/values-bn-rBD/strings.xml @@ -20,9 +20,11 @@ "হোম এ নেভিগেট করুন" "উপরের দিকে নেভিগেট করুন" "আরো বিকল্প" + "সঙ্কুচিত করুন" "%1$s, %2$s" "%1$s, %2$s, %3$s" "অনুসন্ধান করুন" + "অনুসন্ধান..." "ক্যোয়ারী অনুসন্ধান করুন" "ক্যোয়ারী সাফ করুন" "ক্যোয়ারী জমা দিন" diff --git a/eclipse-compile/appcompat/res/values-ca/strings.xml b/eclipse-compile/appcompat/res/values-ca/strings.xml index 5fe4b0dd37..97789f520f 100644 --- a/eclipse-compile/appcompat/res/values-ca/strings.xml +++ b/eclipse-compile/appcompat/res/values-ca/strings.xml @@ -20,9 +20,11 @@ "Navega a la pàgina d\'inici" "Navega cap a dalt" "Més opcions" + "Replega" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Cerca" + "Cerca..." "Consulta de cerca" "Esborra la consulta" "Envia la consulta" diff --git a/eclipse-compile/appcompat/res/values-cs/strings.xml b/eclipse-compile/appcompat/res/values-cs/strings.xml index 13c9ba8eb3..9c3b2b0414 100644 --- a/eclipse-compile/appcompat/res/values-cs/strings.xml +++ b/eclipse-compile/appcompat/res/values-cs/strings.xml @@ -20,9 +20,11 @@ "Přejít na plochu" "Přejít nahoru" "Více možností" + "Sbalit" "%1$s – %2$s" "%1$s, %2$s – %3$s" "Hledat" + "Vyhledat…" "Vyhledávací dotaz" "Smazat dotaz" "Odeslat dotaz" diff --git a/eclipse-compile/appcompat/res/values-da/strings.xml b/eclipse-compile/appcompat/res/values-da/strings.xml index 03fec328ca..fda0c2453c 100644 --- a/eclipse-compile/appcompat/res/values-da/strings.xml +++ b/eclipse-compile/appcompat/res/values-da/strings.xml @@ -20,9 +20,11 @@ "Naviger hjem" "Naviger op" "Flere muligheder" + "Skjul" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Søg" + "Søg…" "Søgeforespørgsel" "Ryd forespørgslen" "Indsend forespørgslen" diff --git a/eclipse-compile/appcompat/res/values-de/strings.xml b/eclipse-compile/appcompat/res/values-de/strings.xml index 8a0224c47d..2905d60788 100644 --- a/eclipse-compile/appcompat/res/values-de/strings.xml +++ b/eclipse-compile/appcompat/res/values-de/strings.xml @@ -20,9 +20,11 @@ "Zur Startseite" "Nach oben" "Weitere Optionen" + "Minimieren" "%1$s: %2$s" "%1$s, %2$s: %3$s" "Suchen" + "Suchen…" "Suchanfrage" "Suchanfrage löschen" "Suchanfrage senden" diff --git a/eclipse-compile/appcompat/res/values-el/strings.xml b/eclipse-compile/appcompat/res/values-el/strings.xml index 52d1b81e74..779c83f954 100644 --- a/eclipse-compile/appcompat/res/values-el/strings.xml +++ b/eclipse-compile/appcompat/res/values-el/strings.xml @@ -20,9 +20,11 @@ "Πλοήγηση στην αρχική σελίδα" "Πλοήγηση προς τα επάνω" "Περισσότερες επιλογές" + "Σύμπτυξη" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Αναζήτηση" + "Αναζήτηση…" "Ερώτημα αναζήτησης" "Διαγραφή ερωτήματος" "Υποβολή ερωτήματος" diff --git a/eclipse-compile/appcompat/res/values-en-rGB/strings.xml b/eclipse-compile/appcompat/res/values-en-rGB/strings.xml index 8a8a1119ed..a85156e85d 100644 --- a/eclipse-compile/appcompat/res/values-en-rGB/strings.xml +++ b/eclipse-compile/appcompat/res/values-en-rGB/strings.xml @@ -20,9 +20,11 @@ "Navigate home" "Navigate up" "More options" + "Collapse" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Search" + "Search…" "Search query" "Clear query" "Submit query" diff --git a/eclipse-compile/appcompat/res/values-en-rIN/strings.xml b/eclipse-compile/appcompat/res/values-en-rIN/strings.xml index 8a8a1119ed..a85156e85d 100644 --- a/eclipse-compile/appcompat/res/values-en-rIN/strings.xml +++ b/eclipse-compile/appcompat/res/values-en-rIN/strings.xml @@ -20,9 +20,11 @@ "Navigate home" "Navigate up" "More options" + "Collapse" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Search" + "Search…" "Search query" "Clear query" "Submit query" diff --git a/eclipse-compile/appcompat/res/values-es-rUS/strings.xml b/eclipse-compile/appcompat/res/values-es-rUS/strings.xml index ea5004cbb9..b8488e17fa 100644 --- a/eclipse-compile/appcompat/res/values-es-rUS/strings.xml +++ b/eclipse-compile/appcompat/res/values-es-rUS/strings.xml @@ -20,9 +20,11 @@ "Navegar a la página principal" "Navegar hacia arriba" "Más opciones" + "Contraer" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Búsqueda" + "Buscar…" "Consulta de búsqueda" "Eliminar la consulta" "Enviar consulta" diff --git a/eclipse-compile/appcompat/res/values-es/strings.xml b/eclipse-compile/appcompat/res/values-es/strings.xml index c50796ee8c..70ea32d15a 100644 --- a/eclipse-compile/appcompat/res/values-es/strings.xml +++ b/eclipse-compile/appcompat/res/values-es/strings.xml @@ -20,9 +20,11 @@ "Ir a la pantalla de inicio" "Desplazarse hacia arriba" "Más opciones" + "Contraer" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Buscar" + "Buscar…" "Consulta" "Borrar consulta" "Enviar consulta" diff --git a/eclipse-compile/appcompat/res/values-et-rEE/strings.xml b/eclipse-compile/appcompat/res/values-et-rEE/strings.xml index 139fcf915d..cf4deacc1e 100644 --- a/eclipse-compile/appcompat/res/values-et-rEE/strings.xml +++ b/eclipse-compile/appcompat/res/values-et-rEE/strings.xml @@ -20,9 +20,11 @@ "Navigeerimine avaekraanile" "Navigeerimine üles" "Rohkem valikuid" + "Ahendamine" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Otsing" + "Otsige …" "Otsingupäring" "Päringu tühistamine" "Päringu esitamine" diff --git a/eclipse-compile/appcompat/res/values-eu-rES/strings.xml b/eclipse-compile/appcompat/res/values-eu-rES/strings.xml index 541c2ed3cd..dddc924ac3 100644 --- a/eclipse-compile/appcompat/res/values-eu-rES/strings.xml +++ b/eclipse-compile/appcompat/res/values-eu-rES/strings.xml @@ -20,9 +20,11 @@ "Joan orri nagusira" "Joan gora" "Aukera gehiago" + "Tolestu" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Bilatu" + "Bilatu…" "Bilaketa-kontsulta" "Garbitu kontsulta" "Bidali kontsulta" diff --git a/eclipse-compile/appcompat/res/values-fa/strings.xml b/eclipse-compile/appcompat/res/values-fa/strings.xml index c317bdaf3a..3e85c47a2d 100644 --- a/eclipse-compile/appcompat/res/values-fa/strings.xml +++ b/eclipse-compile/appcompat/res/values-fa/strings.xml @@ -20,9 +20,11 @@ "پیمایش به صفحه اصلی" "پیمایش به بالا" "گزینه‌های بیشتر" + "کوچک کردن" "‏%1$s‏، %2$s" "‏%1$s‏، %2$s‏، %3$s" "جستجو" + "جستجو…" "عبارت جستجو" "پاک کردن عبارت جستجو" "ارسال عبارت جستجو" diff --git a/eclipse-compile/appcompat/res/values-fi/strings.xml b/eclipse-compile/appcompat/res/values-fi/strings.xml index 218229b0f1..f706ebe9e8 100644 --- a/eclipse-compile/appcompat/res/values-fi/strings.xml +++ b/eclipse-compile/appcompat/res/values-fi/strings.xml @@ -20,9 +20,11 @@ "Siirry etusivulle" "Siirry ylös" "Lisää" + "Kutista" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Haku" + "Haku…" "Hakulauseke" "Tyhjennä kysely" "Lähetä kysely" diff --git a/eclipse-compile/appcompat/res/values-fr-rCA/strings.xml b/eclipse-compile/appcompat/res/values-fr-rCA/strings.xml index 571ff9a333..979bfa5480 100644 --- a/eclipse-compile/appcompat/res/values-fr-rCA/strings.xml +++ b/eclipse-compile/appcompat/res/values-fr-rCA/strings.xml @@ -20,9 +20,11 @@ "Revenir à l\'accueil" "Revenir en haut de la page" "Plus d\'options" + "Réduire" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Rechercher" + "Recherche en cours..." "Requête de recherche" "Effacer la requête" "Envoyer la requête" diff --git a/eclipse-compile/appcompat/res/values-fr/strings.xml b/eclipse-compile/appcompat/res/values-fr/strings.xml index 353665a880..df851d3634 100644 --- a/eclipse-compile/appcompat/res/values-fr/strings.xml +++ b/eclipse-compile/appcompat/res/values-fr/strings.xml @@ -20,9 +20,11 @@ "Revenir à l\'accueil" "Revenir en haut de la page" "Plus d\'options" + "Réduire" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Rechercher" + "Rechercher…" "Requête de recherche" "Effacer la requête" "Envoyer la requête" diff --git a/eclipse-compile/appcompat/res/values-gl-rES/strings.xml b/eclipse-compile/appcompat/res/values-gl-rES/strings.xml index 3f665ed6ce..618aec0e20 100644 --- a/eclipse-compile/appcompat/res/values-gl-rES/strings.xml +++ b/eclipse-compile/appcompat/res/values-gl-rES/strings.xml @@ -20,9 +20,11 @@ "Ir á páxina de inicio" "Desprazarse cara arriba" "Máis opcións" + "Contraer" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Buscar" + "Buscar…" "Consulta de busca" "Borrar consulta" "Enviar consulta" diff --git a/eclipse-compile/appcompat/res/values-h720dp/dimens.xml b/eclipse-compile/appcompat/res/values-h720dp/dimens.xml new file mode 100644 index 0000000000..09c43f0ef4 --- /dev/null +++ b/eclipse-compile/appcompat/res/values-h720dp/dimens.xml @@ -0,0 +1,20 @@ + + + + + + 54dip + diff --git a/eclipse-compile/appcompat/res/values-hdpi/styles_base.xml b/eclipse-compile/appcompat/res/values-hdpi/styles_base.xml new file mode 100644 index 0000000000..442ea292a0 --- /dev/null +++ b/eclipse-compile/appcompat/res/values-hdpi/styles_base.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/values-hi/strings.xml b/eclipse-compile/appcompat/res/values-hi/strings.xml index 23cfacaa30..2ee69d967a 100644 --- a/eclipse-compile/appcompat/res/values-hi/strings.xml +++ b/eclipse-compile/appcompat/res/values-hi/strings.xml @@ -17,12 +17,14 @@ "पूर्ण" - "मुखपृष्ठ पर नेविगेट करें" + "मुख्यपृष्ठ पर नेविगेट करें" "ऊपर नेविगेट करें" "अधिक विकल्प" + "संक्षिप्त करें" "%1$s, %2$s" "%1$s, %2$s, %3$s" "खोजें" + "खोजा जा रहा है…" "खोज क्वेरी" "क्‍वेरी साफ़ करें" "क्वेरी सबमिट करें" diff --git a/eclipse-compile/appcompat/res/values-hr/strings.xml b/eclipse-compile/appcompat/res/values-hr/strings.xml index 034859635c..7e8968f68a 100644 --- a/eclipse-compile/appcompat/res/values-hr/strings.xml +++ b/eclipse-compile/appcompat/res/values-hr/strings.xml @@ -20,9 +20,11 @@ "Idi na početnu" "Idi gore" "Dodatne opcije" + "Sažmi" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Pretraživanje" + "Pretražite…" "Upit za pretraživanje" "Izbriši upit" "Pošalji upit" diff --git a/eclipse-compile/appcompat/res/values-hu/strings.xml b/eclipse-compile/appcompat/res/values-hu/strings.xml index fc67f00ef5..7fe27d2887 100644 --- a/eclipse-compile/appcompat/res/values-hu/strings.xml +++ b/eclipse-compile/appcompat/res/values-hu/strings.xml @@ -20,9 +20,11 @@ "Ugrás a főoldalra" "Felfelé mozgatás" "További lehetőségek" + "Összecsukás" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Keresés" + "Keresés…" "Keresési lekérdezés" "Lekérdezés törlése" "Lekérdezés küldése" diff --git a/eclipse-compile/appcompat/res/values-hy-rAM/strings.xml b/eclipse-compile/appcompat/res/values-hy-rAM/strings.xml index da67fe4c5f..47c29a5bd5 100644 --- a/eclipse-compile/appcompat/res/values-hy-rAM/strings.xml +++ b/eclipse-compile/appcompat/res/values-hy-rAM/strings.xml @@ -20,9 +20,11 @@ "Ուղղվել տուն" "Ուղղվել վերև" "Այլ ընտրանքներ" + "Թաքցնել" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Որոնել" + "Որոնում..." "Որոնման հարցում" "Մաքրել հարցումը" "Ուղարկել հարցումը" diff --git a/eclipse-compile/appcompat/res/values-in/strings.xml b/eclipse-compile/appcompat/res/values-in/strings.xml index 3c31755ad0..d102ba6dc2 100644 --- a/eclipse-compile/appcompat/res/values-in/strings.xml +++ b/eclipse-compile/appcompat/res/values-in/strings.xml @@ -20,9 +20,11 @@ "Navigasi ke beranda" "Navigasi naik" "Opsi lain" + "Ciutkan" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Telusuri" + "Telusuri..." "Kueri penelusuran" "Hapus kueri" "Kirim kueri" diff --git a/eclipse-compile/appcompat/res/values-is-rIS/strings.xml b/eclipse-compile/appcompat/res/values-is-rIS/strings.xml index 7846b514f5..a205e8bf73 100644 --- a/eclipse-compile/appcompat/res/values-is-rIS/strings.xml +++ b/eclipse-compile/appcompat/res/values-is-rIS/strings.xml @@ -20,9 +20,11 @@ "Fara heim" "Fara upp" "Fleiri valkostir" + "Minnka" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Leita" + "Leita…" "Leitarfyrirspurn" "Hreinsa fyrirspurn" "Senda fyrirspurn" diff --git a/eclipse-compile/appcompat/res/values-it/strings.xml b/eclipse-compile/appcompat/res/values-it/strings.xml index 6ed52be06d..71cb54fcb7 100644 --- a/eclipse-compile/appcompat/res/values-it/strings.xml +++ b/eclipse-compile/appcompat/res/values-it/strings.xml @@ -20,9 +20,11 @@ "Vai alla home page" "Vai in alto" "Altre opzioni" + "Comprimi" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Cerca" + "Cerca…" "Query di ricerca" "Cancella query" "Invia query" diff --git a/eclipse-compile/appcompat/res/values-iw/strings.xml b/eclipse-compile/appcompat/res/values-iw/strings.xml index fec0e62287..c5ef730fb8 100644 --- a/eclipse-compile/appcompat/res/values-iw/strings.xml +++ b/eclipse-compile/appcompat/res/values-iw/strings.xml @@ -20,9 +20,11 @@ "נווט לדף הבית" "נווט למעלה" "עוד אפשרויות" + "כווץ" "‏%1$s‏, %2$s" "‏%1$s‏, %2$s‏, %3$s" "חפש" + "חפש…" "שאילתת חיפוש" "מחק שאילתה" "שלח שאילתה" diff --git a/eclipse-compile/appcompat/res/values-ja/strings.xml b/eclipse-compile/appcompat/res/values-ja/strings.xml index 181dd5e5fa..736d454701 100644 --- a/eclipse-compile/appcompat/res/values-ja/strings.xml +++ b/eclipse-compile/appcompat/res/values-ja/strings.xml @@ -20,9 +20,11 @@ "ホームへ移動" "上へ移動" "その他のオプション" + "折りたたむ" "%1$s、%2$s" "%1$s、%2$s、%3$s" "検索" + "検索…" "検索キーワード" "検索キーワードを削除" "検索キーワードを送信" diff --git a/eclipse-compile/appcompat/res/values-ka-rGE/strings.xml b/eclipse-compile/appcompat/res/values-ka-rGE/strings.xml index a96f26c3fe..2341e3d0a3 100644 --- a/eclipse-compile/appcompat/res/values-ka-rGE/strings.xml +++ b/eclipse-compile/appcompat/res/values-ka-rGE/strings.xml @@ -20,9 +20,11 @@ "მთავარზე ნავიგაცია" "ზემოთ ნავიგაცია" "მეტი ვარიანტები" + "აკეცვა" "%1$s, %2$s" "%1$s, %2$s, %3$s" "ძიება" + "ძიება..." "ძიების მოთხოვნა" "მოთხოვნის გასუფთავება" "მოთხოვნის გადაგზავნა" diff --git a/eclipse-compile/appcompat/res/values-kk-rKZ/strings.xml b/eclipse-compile/appcompat/res/values-kk-rKZ/strings.xml index fb20a0049c..1af9a5ffbf 100644 --- a/eclipse-compile/appcompat/res/values-kk-rKZ/strings.xml +++ b/eclipse-compile/appcompat/res/values-kk-rKZ/strings.xml @@ -20,9 +20,11 @@ "Негізгі бетте қозғалу" "Жоғары қозғалу" "Басқа опциялар" + "Тасалау" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Іздеу" + "Іздеу…" "Сұрақты іздеу" "Сұрақты жою" "Сұрақты жіберу" diff --git a/eclipse-compile/appcompat/res/values-km-rKH/strings.xml b/eclipse-compile/appcompat/res/values-km-rKH/strings.xml index 704f4ee48f..5fda61ae57 100644 --- a/eclipse-compile/appcompat/res/values-km-rKH/strings.xml +++ b/eclipse-compile/appcompat/res/values-km-rKH/strings.xml @@ -20,9 +20,11 @@ "រកមើល​ទៅ​ដើម" "រកមើល​ឡើងលើ" "ជម្រើស​ច្រើន​ទៀត" + "បង្រួម" "%1$s, %2$s" "%1$s, %2$s, %3$s" "ស្វែងរក" + "ស្វែងរក…" "ស្វែងរក​សំណួរ" "សម្អាត​សំណួរ" "ដាក់​​​ស្នើ​សំណួរ" diff --git a/eclipse-compile/appcompat/res/values-kn-rIN/strings.xml b/eclipse-compile/appcompat/res/values-kn-rIN/strings.xml index d472ff32ea..669c21ccaf 100644 --- a/eclipse-compile/appcompat/res/values-kn-rIN/strings.xml +++ b/eclipse-compile/appcompat/res/values-kn-rIN/strings.xml @@ -20,9 +20,11 @@ "ಮುಖಪುಟವನ್ನು ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ" "ಮೇಲಕ್ಕೆ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ" "ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳು" + "ಸಂಕುಚಿಸು" "%1$s, %2$s" "%1$s, %2$s, %3$s" "ಹುಡುಕು" + "ಹುಡುಕಿ…" "ಪ್ರಶ್ನೆಯನ್ನು ಹುಡುಕಿ" "ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸು" "ಪ್ರಶ್ನೆಯನ್ನು ಸಲ್ಲಿಸು" diff --git a/eclipse-compile/appcompat/res/values-ko/strings.xml b/eclipse-compile/appcompat/res/values-ko/strings.xml index 0a92a83dc7..ad83225fcd 100644 --- a/eclipse-compile/appcompat/res/values-ko/strings.xml +++ b/eclipse-compile/appcompat/res/values-ko/strings.xml @@ -20,9 +20,11 @@ "홈 탐색" "위로 탐색" "옵션 더보기" + "접기" "%1$s, %2$s" "%1$s, %2$s, %3$s" "검색" + "검색..." "검색어" "검색어 삭제" "검색어 보내기" diff --git a/eclipse-compile/appcompat/res/values-ky-rKG/strings.xml b/eclipse-compile/appcompat/res/values-ky-rKG/strings.xml index b091f608ca..b9f0bb1654 100644 --- a/eclipse-compile/appcompat/res/values-ky-rKG/strings.xml +++ b/eclipse-compile/appcompat/res/values-ky-rKG/strings.xml @@ -20,9 +20,11 @@ "Үйгө багыттоо" "Жогору" "Көбүрөөк мүмкүнчүлүктөр" + "Жыйнап коюу" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Издөө" + "Издөө…" "Издөө талаптары" "Талаптарды тазалоо" "Талап жөнөтүү" diff --git a/eclipse-compile/appcompat/res/values-lo-rLA/strings.xml b/eclipse-compile/appcompat/res/values-lo-rLA/strings.xml index 33614e6715..45f830f3f9 100644 --- a/eclipse-compile/appcompat/res/values-lo-rLA/strings.xml +++ b/eclipse-compile/appcompat/res/values-lo-rLA/strings.xml @@ -20,9 +20,11 @@ "ກັບໄປໜ້າຫຼັກ" "ຂຶ້ນເທິງ" "ໂຕເລືອກອື່ນ" + "ຫຍໍ້" "%1$s, %2$s" "%1$s, %2$s, %3$s" "ຊອກຫາ" + "ຊອກຫາ" "ຊອກຫາ" "ລຶບຂໍ້ຄວາມຊອກຫາ" "ສົ່ງການຊອກຫາ" diff --git a/eclipse-compile/appcompat/res/values-lt/strings.xml b/eclipse-compile/appcompat/res/values-lt/strings.xml index 3c992a7bb1..27713a79b4 100644 --- a/eclipse-compile/appcompat/res/values-lt/strings.xml +++ b/eclipse-compile/appcompat/res/values-lt/strings.xml @@ -20,9 +20,11 @@ "Eiti į pagrindinį puslapį" "Eiti į viršų" "Daugiau parinkčių" + "Sutraukti" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Paieška" + "Ieškoti..." "Paieškos užklausa" "Išvalyti užklausą" "Pateikti užklausą" diff --git a/eclipse-compile/appcompat/res/values-lv/strings.xml b/eclipse-compile/appcompat/res/values-lv/strings.xml index 3bd72591b1..986e8eb985 100644 --- a/eclipse-compile/appcompat/res/values-lv/strings.xml +++ b/eclipse-compile/appcompat/res/values-lv/strings.xml @@ -20,9 +20,11 @@ "Pārvietoties uz sākuma ekrānu" "Pārvietoties augšup" "Vairāk opciju" + "Sakļaut" "%1$s: %2$s" "%1$s, %2$s: %3$s" "Meklēt" + "Meklējiet…" "Meklēšanas vaicājums" "Notīrīt vaicājumu" "Iesniegt vaicājumu" diff --git a/eclipse-compile/appcompat/res/values-mk-rMK/strings.xml b/eclipse-compile/appcompat/res/values-mk-rMK/strings.xml index b1abf10683..916809aaf1 100644 --- a/eclipse-compile/appcompat/res/values-mk-rMK/strings.xml +++ b/eclipse-compile/appcompat/res/values-mk-rMK/strings.xml @@ -20,11 +20,13 @@ "Движи се кон дома" "Движи се нагоре" "Повеќе опции" + "Собери" "%1$s, %2$s, %3$s" "Пребарај" + "Пребарување…" "Пребарај барање" "Исчисти барање" "Поднеси барање" diff --git a/eclipse-compile/appcompat/res/values-ml-rIN/strings.xml b/eclipse-compile/appcompat/res/values-ml-rIN/strings.xml index f7512adaaf..c122ed5c64 100644 --- a/eclipse-compile/appcompat/res/values-ml-rIN/strings.xml +++ b/eclipse-compile/appcompat/res/values-ml-rIN/strings.xml @@ -20,13 +20,15 @@ "ഹോമിലേക്ക് നാവിഗേറ്റുചെയ്യുക" "മുകളിലേക്ക് നാവിഗേറ്റുചെയ്യുക" "കൂടുതല്‍ ഓപ്‌ഷനുകള്‍" + "ചുരുക്കുക" "%1$s, %2$s" "%1$s, %2$s, %3$s" "തിരയൽ" + "തിരയുക…" "തിരയൽ അന്വേഷണം" "അന്വേഷണം മായ്‌ക്കുക" "അന്വേഷണം സമർപ്പിക്കുക" - "വോയ്‌സ് തിരയൽ" + "ശബ്ദ തിരയൽ" "ഒരു അപ്ലിക്കേഷൻ തിരഞ്ഞെടുക്കുക" "എല്ലാം കാണുക" "%s എന്നതുമായി പങ്കിടുക" diff --git a/eclipse-compile/appcompat/res/values-mn-rMN/strings.xml b/eclipse-compile/appcompat/res/values-mn-rMN/strings.xml index a1a9c6fb01..eadbb9fc0e 100644 --- a/eclipse-compile/appcompat/res/values-mn-rMN/strings.xml +++ b/eclipse-compile/appcompat/res/values-mn-rMN/strings.xml @@ -20,9 +20,11 @@ "Нүүр хуудас руу шилжих" "Дээш шилжих" "Нэмэлт сонголтууд" + "Хумих" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Хайх" + "Хайх..." "Хайх асуулга" "Асуулгыг цэвэрлэх" "Асуулгыг илгээх" diff --git a/eclipse-compile/appcompat/res/values-mr-rIN/strings.xml b/eclipse-compile/appcompat/res/values-mr-rIN/strings.xml index 3d0e71888c..a2fb945118 100644 --- a/eclipse-compile/appcompat/res/values-mr-rIN/strings.xml +++ b/eclipse-compile/appcompat/res/values-mr-rIN/strings.xml @@ -20,9 +20,11 @@ "मुख्‍यपृष्‍ठ नेव्‍हिगेट करा" "वर नेव्‍हिगेट करा" "अधिक पर्याय" + "संक्षिप्त करा" "%1$s, %2$s" "%1$s, %2$s, %3$s" "शोध" + "शोधा…" "शोध क्वेरी" "क्‍वेरी स्‍पष्‍ट करा" "क्वेरी सबमिट करा" diff --git a/eclipse-compile/appcompat/res/values-ms-rMY/strings.xml b/eclipse-compile/appcompat/res/values-ms-rMY/strings.xml index d2886a1a6d..f42fe3d614 100644 --- a/eclipse-compile/appcompat/res/values-ms-rMY/strings.xml +++ b/eclipse-compile/appcompat/res/values-ms-rMY/strings.xml @@ -20,9 +20,11 @@ "Navigasi skrin utama" "Navigasi ke atas" "Lagi pilihan" + "Runtuhkan" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Cari" + "Cari…" "Pertanyaan carian" "Kosongkan pertanyaan" "Serah pertanyaan" diff --git a/eclipse-compile/appcompat/res/values-my-rMM/strings.xml b/eclipse-compile/appcompat/res/values-my-rMM/strings.xml index 3ac8472d1a..6313bcb35d 100644 --- a/eclipse-compile/appcompat/res/values-my-rMM/strings.xml +++ b/eclipse-compile/appcompat/res/values-my-rMM/strings.xml @@ -20,9 +20,11 @@ "မူလနေရာကို သွားရန်" "အပေါ်သို့သွားရန်" "ပိုမိုရွေးချယ်စရာများ" + "ခေါက်ရန်" "%1$s၊ %2$s" "%1$s ၊ %2$s ၊ %3$s" "ရှာဖွေရန်" + "ရှာဖွေပါ..." "ရှာစရာ အချက်အလက်နေရာ" "ရှာစရာ အချက်အလက်များ ရှင်းလင်းရန်" "ရှာဖွေစရာ အချက်အလက်ကို အတည်ပြုရန်" diff --git a/eclipse-compile/appcompat/res/values-nb/strings.xml b/eclipse-compile/appcompat/res/values-nb/strings.xml index 3dbd071e76..6e50a58186 100644 --- a/eclipse-compile/appcompat/res/values-nb/strings.xml +++ b/eclipse-compile/appcompat/res/values-nb/strings.xml @@ -20,9 +20,11 @@ "Gå til startsiden" "Gå opp" "Flere alternativer" + "Skjul" "%1$s – %2$s" "%1$s – %2$s – %3$s" "Søk" + "Søk …" "Søkeord" "Slett søket" "Utfør søket" diff --git a/eclipse-compile/appcompat/res/values-ne-rNP/strings.xml b/eclipse-compile/appcompat/res/values-ne-rNP/strings.xml index 01546624d3..5b804d87d1 100644 --- a/eclipse-compile/appcompat/res/values-ne-rNP/strings.xml +++ b/eclipse-compile/appcompat/res/values-ne-rNP/strings.xml @@ -20,9 +20,11 @@ "गृह खोज्नुहोस्" "माथि खोज्नुहोस्" "थप विकल्पहरू" + "संक्षिप्त पार्नुहोस्" "%1$s, %2$s" "%1$s, %2$s, %3$s" "खोज्नुहोस्" + "खोज्नुहोस्..." "जिज्ञासाको खोज गर्नुहोस्" "प्रश्‍न हटाउनुहोस्" "जिज्ञासा पेस गर्नुहोस्" diff --git a/eclipse-compile/appcompat/res/values-nl/strings.xml b/eclipse-compile/appcompat/res/values-nl/strings.xml index 330de8daa6..61546dff52 100644 --- a/eclipse-compile/appcompat/res/values-nl/strings.xml +++ b/eclipse-compile/appcompat/res/values-nl/strings.xml @@ -20,9 +20,11 @@ "Navigeren naar startpositie" "Omhoog navigeren" "Meer opties" + "Samenvouwen" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Zoeken" + "Zoeken…" "Zoekopdracht" "Zoekopdracht wissen" "Zoekopdracht verzenden" diff --git a/eclipse-compile/appcompat/res/values-pl/strings.xml b/eclipse-compile/appcompat/res/values-pl/strings.xml index 8e32155f19..9d99e9226f 100644 --- a/eclipse-compile/appcompat/res/values-pl/strings.xml +++ b/eclipse-compile/appcompat/res/values-pl/strings.xml @@ -20,9 +20,11 @@ "Przejdź do strony głównej" "Przejdź wyżej" "Więcej opcji" + "Zwiń" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Szukaj" + "Szukaj…" "Wyszukiwane hasło" "Wyczyść zapytanie" "Wyślij zapytanie" diff --git a/eclipse-compile/appcompat/res/values-pt-rPT/strings.xml b/eclipse-compile/appcompat/res/values-pt-rPT/strings.xml index e1c622e980..e905530d3c 100644 --- a/eclipse-compile/appcompat/res/values-pt-rPT/strings.xml +++ b/eclipse-compile/appcompat/res/values-pt-rPT/strings.xml @@ -20,9 +20,11 @@ "Navegar para a página inicial" "Navegar para cima" "Mais opções" + "Reduzir" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Pesquisar" + "Pesquisar..." "Consulta de pesquisa" "Limpar consulta" "Enviar consulta" diff --git a/eclipse-compile/appcompat/res/values-pt/strings.xml b/eclipse-compile/appcompat/res/values-pt/strings.xml index abdd650c60..b6c94e901c 100644 --- a/eclipse-compile/appcompat/res/values-pt/strings.xml +++ b/eclipse-compile/appcompat/res/values-pt/strings.xml @@ -20,9 +20,11 @@ "Navegar para a página inicial" "Navegar para cima" "Mais opções" + "Recolher" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Pesquisar" + "Pesquisar..." "Consulta de pesquisa" "Limpar consulta" "Enviar consulta" diff --git a/eclipse-compile/appcompat/res/values-ro/strings.xml b/eclipse-compile/appcompat/res/values-ro/strings.xml index 6dd2b67c6a..4c741f391d 100644 --- a/eclipse-compile/appcompat/res/values-ro/strings.xml +++ b/eclipse-compile/appcompat/res/values-ro/strings.xml @@ -20,9 +20,11 @@ "Navigați la ecranul de pornire" "Navigați în sus" "Mai multe opțiuni" + "Restrângeți" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Căutați" + "Căutați…" "Interogare de căutare" "Ștergeți interogarea" "Trimiteți interogarea" diff --git a/eclipse-compile/appcompat/res/values-ru/strings.xml b/eclipse-compile/appcompat/res/values-ru/strings.xml index 9c5ed8958d..4879b5d6b3 100644 --- a/eclipse-compile/appcompat/res/values-ru/strings.xml +++ b/eclipse-compile/appcompat/res/values-ru/strings.xml @@ -20,9 +20,11 @@ "Перейти на главный экран" "Перейти вверх" "Другие параметры" + "Свернуть" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Поиск" + "Поиск" "Поисковый запрос" "Удалить запрос" "Отправить запрос" diff --git a/eclipse-compile/appcompat/res/values-si-rLK/strings.xml b/eclipse-compile/appcompat/res/values-si-rLK/strings.xml index 22809d63d1..252448f307 100644 --- a/eclipse-compile/appcompat/res/values-si-rLK/strings.xml +++ b/eclipse-compile/appcompat/res/values-si-rLK/strings.xml @@ -20,9 +20,11 @@ "ගෙදරට සංචාලනය කරන්න" "ඉහලට සංචාලනය කරන්න" "තවත් විකල්ප" + "හකුළන්න" "%1$s, %2$s" "%1$s, %2$s, %3$s" "සෙවීම" + "සොයන්න..." "සෙවුම් විමසුම" "විමසුම හිස් කරන්න" "විමසුම යොමු කරන්න" diff --git a/eclipse-compile/appcompat/res/values-sk/strings.xml b/eclipse-compile/appcompat/res/values-sk/strings.xml index 2b09cce78f..501e0653fd 100644 --- a/eclipse-compile/appcompat/res/values-sk/strings.xml +++ b/eclipse-compile/appcompat/res/values-sk/strings.xml @@ -20,9 +20,11 @@ "Prejsť na plochu" "Prejsť hore" "Ďalšie možnosti" + "Zbaliť" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Hľadať" + "Vyhľadať…" "Vyhľadávací dopyt" "Vymazať dopyt" "Odoslať dopyt" diff --git a/eclipse-compile/appcompat/res/values-sl/strings.xml b/eclipse-compile/appcompat/res/values-sl/strings.xml index a522de23ca..da49123fca 100644 --- a/eclipse-compile/appcompat/res/values-sl/strings.xml +++ b/eclipse-compile/appcompat/res/values-sl/strings.xml @@ -20,9 +20,11 @@ "Krmarjenje domov" "Krmarjenje navzgor" "Več možnosti" + "Strni" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Iskanje" + "Iskanje …" "Iskalna poizvedba" "Izbris poizvedbe" "Pošiljanje poizvedbe" diff --git a/eclipse-compile/appcompat/res/values-sr/strings.xml b/eclipse-compile/appcompat/res/values-sr/strings.xml index c26945bfb8..9e2a9e8c9d 100644 --- a/eclipse-compile/appcompat/res/values-sr/strings.xml +++ b/eclipse-compile/appcompat/res/values-sr/strings.xml @@ -20,9 +20,11 @@ "Одлазак на Почетну" "Кретање нагоре" "Још опција" + "Скупи" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Претрага" + "Претражите..." "Упит за претрагу" "Брисање упита" "Слање упита" diff --git a/eclipse-compile/appcompat/res/values-sv/strings.xml b/eclipse-compile/appcompat/res/values-sv/strings.xml index 3120ad805f..905e3ea740 100644 --- a/eclipse-compile/appcompat/res/values-sv/strings.xml +++ b/eclipse-compile/appcompat/res/values-sv/strings.xml @@ -20,9 +20,11 @@ "Visa startsidan" "Navigera uppåt" "Fler alternativ" + "Komprimera" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Sök" + "Sök …" "Sökfråga" "Ta bort frågan" "Skicka fråga" diff --git a/eclipse-compile/appcompat/res/values-sw/strings.xml b/eclipse-compile/appcompat/res/values-sw/strings.xml index afe54f6fd8..7287c0dc94 100644 --- a/eclipse-compile/appcompat/res/values-sw/strings.xml +++ b/eclipse-compile/appcompat/res/values-sw/strings.xml @@ -20,9 +20,11 @@ "Nenda mwanzo" "Nenda juu" "Chaguo zaidi" + "Kunja" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Tafuta" + "Tafuta…" "Hoja ya utafutaji" "Futa hoja" "Wasilisha hoja" diff --git a/eclipse-compile/appcompat/res/values-sw600dp/dimens.xml b/eclipse-compile/appcompat/res/values-sw600dp/dimens.xml index cba2150682..e221b50164 100644 --- a/eclipse-compile/appcompat/res/values-sw600dp/dimens.xml +++ b/eclipse-compile/appcompat/res/values-sw600dp/dimens.xml @@ -29,5 +29,12 @@ 64dp 4dp + + 24dp + + + 8dp + + 18dp \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/values-ta-rIN/strings.xml b/eclipse-compile/appcompat/res/values-ta-rIN/strings.xml index 542fd3468a..ab728a602a 100644 --- a/eclipse-compile/appcompat/res/values-ta-rIN/strings.xml +++ b/eclipse-compile/appcompat/res/values-ta-rIN/strings.xml @@ -20,9 +20,11 @@ "முகப்பிற்கு வழிசெலுத்து" "மேலே வழிசெலுத்து" "மேலும் விருப்பங்கள்" + "சுருக்கு" "%1$s, %2$s" "%1$s, %2$s, %3$s" "தேடு" + "தேடு..." "தேடல் வினவல்" "வினவலை அழி" "வினவலைச் சமர்ப்பி" diff --git a/eclipse-compile/appcompat/res/values-te-rIN/strings.xml b/eclipse-compile/appcompat/res/values-te-rIN/strings.xml index 5f36cc5c18..901859b015 100644 --- a/eclipse-compile/appcompat/res/values-te-rIN/strings.xml +++ b/eclipse-compile/appcompat/res/values-te-rIN/strings.xml @@ -20,9 +20,11 @@ "హోమ్‌కు నావిగేట్ చేయండి" "పైకి నావిగేట్ చేయండి" "మరిన్ని ఎంపికలు" + "కుదించండి" "%1$s, %2$s" "%1$s, %2$s, %3$s" "శోధించు" + "శోధించు..." "ప్రశ్న శోధించండి" "ప్రశ్నను క్లియర్ చేయి" "ప్రశ్నని సమర్పించు" diff --git a/eclipse-compile/appcompat/res/values-th/strings.xml b/eclipse-compile/appcompat/res/values-th/strings.xml index d8c04c4454..e962aa56bd 100644 --- a/eclipse-compile/appcompat/res/values-th/strings.xml +++ b/eclipse-compile/appcompat/res/values-th/strings.xml @@ -20,9 +20,11 @@ "นำทางไปหน้าแรก" "นำทางขึ้น" "ตัวเลือกอื่น" + "ยุบ" "%1$s, %2$s" "%1$s, %2$s, %3$s" "ค้นหา" + "ค้นหา…" "ข้อความค้นหา" "ล้างข้อความค้นหา" "ส่งข้อความค้นหา" diff --git a/eclipse-compile/appcompat/res/values-tl/strings.xml b/eclipse-compile/appcompat/res/values-tl/strings.xml index 0384435313..f41b15ff6f 100644 --- a/eclipse-compile/appcompat/res/values-tl/strings.xml +++ b/eclipse-compile/appcompat/res/values-tl/strings.xml @@ -20,9 +20,11 @@ "Mag-navigate patungo sa home" "Mag-navigate pataas" "Higit pang mga opsyon" + "I-collapse" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Maghanap" + "Maghanap…" "Query sa paghahanap" "I-clear ang query" "Isumite ang query" diff --git a/eclipse-compile/appcompat/res/values-tr/strings.xml b/eclipse-compile/appcompat/res/values-tr/strings.xml index c06069cf4c..9cde4a2214 100644 --- a/eclipse-compile/appcompat/res/values-tr/strings.xml +++ b/eclipse-compile/appcompat/res/values-tr/strings.xml @@ -20,9 +20,11 @@ "Ana ekrana git" "Yukarı git" "Diğer seçenekler" + "Daralt" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Ara" + "Ara…" "Arama sorgusu" "Sorguyu temizle" "Sorguyu gönder" diff --git a/eclipse-compile/appcompat/res/values-uk/strings.xml b/eclipse-compile/appcompat/res/values-uk/strings.xml index d07404b654..0a5c31cd18 100644 --- a/eclipse-compile/appcompat/res/values-uk/strings.xml +++ b/eclipse-compile/appcompat/res/values-uk/strings.xml @@ -20,9 +20,11 @@ "Перейти на головний" "Перейти вгору" "Інші опції" + "Згорнути" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Пошук" + "Пошук…" "Пошуковий запит" "Очистити запит" "Надіслати запит" diff --git a/eclipse-compile/appcompat/res/values-ur-rPK/strings.xml b/eclipse-compile/appcompat/res/values-ur-rPK/strings.xml index 89c0ea69e6..e6f6260d4d 100644 --- a/eclipse-compile/appcompat/res/values-ur-rPK/strings.xml +++ b/eclipse-compile/appcompat/res/values-ur-rPK/strings.xml @@ -20,9 +20,11 @@ "ہوم پر نیویگیٹ کریں" "اوپر نیویگیٹ کریں" "مزید اختیارات" + "سکیڑیں" "%1$s, %2$s" "%1$s, %2$s, %3$s" "تلاش کریں" + "تلاش کریں…" "استفسار تلاش کریں" "استفسار صاف کریں" "استفسار جمع کرائیں" diff --git a/eclipse-compile/appcompat/res/values-uz-rUZ/strings.xml b/eclipse-compile/appcompat/res/values-uz-rUZ/strings.xml index 537afa169d..241b3b121b 100644 --- a/eclipse-compile/appcompat/res/values-uz-rUZ/strings.xml +++ b/eclipse-compile/appcompat/res/values-uz-rUZ/strings.xml @@ -20,9 +20,11 @@ "Boshiga o‘tish" "Yuqoriga o‘tish" "Qo‘shimcha sozlamalar" + "Yig‘ish" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Izlash" + "Qidirish…" "So‘rovni izlash" "So‘rovni tozalash" "So‘rov yaratish" diff --git a/eclipse-compile/appcompat/res/values-v11/themes_base.xml b/eclipse-compile/appcompat/res/values-v11/themes_base.xml index ca583fab12..d344bf594e 100644 --- a/eclipse-compile/appcompat/res/values-v11/themes_base.xml +++ b/eclipse-compile/appcompat/res/values-v11/themes_base.xml @@ -22,12 +22,17 @@ unbundled Action Bar. --> - - - - - - - - - - - - + + + + + diff --git a/eclipse-compile/fab/res/values-sw600dp/dimens.xml b/eclipse-compile/appcompat/res/values-v14/styles_base_text.xml similarity index 59% rename from eclipse-compile/fab/res/values-sw600dp/dimens.xml rename to eclipse-compile/appcompat/res/values-v14/styles_base_text.xml index 3ca9bdfce2..54c8de2ae4 100644 --- a/eclipse-compile/fab/res/values-sw600dp/dimens.xml +++ b/eclipse-compile/appcompat/res/values-v14/styles_base_text.xml @@ -1,23 +1,26 @@ - - 24dp - + + + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/values-v14/themes_base.xml b/eclipse-compile/appcompat/res/values-v14/themes_base.xml index 3f26ca2233..5fdc73c72b 100644 --- a/eclipse-compile/appcompat/res/values-v14/themes_base.xml +++ b/eclipse-compile/appcompat/res/values-v14/themes_base.xml @@ -16,41 +16,21 @@ - - - - - diff --git a/eclipse-compile/appcompat/res/values-v17/styles_rtl.xml b/eclipse-compile/appcompat/res/values-v17/styles_rtl.xml index 0c7d861144..d89a63d057 100644 --- a/eclipse-compile/appcompat/res/values-v17/styles_rtl.xml +++ b/eclipse-compile/appcompat/res/values-v17/styles_rtl.xml @@ -47,14 +47,9 @@ 8dp - - + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/values-v18/dimens.xml b/eclipse-compile/appcompat/res/values-v18/dimens.xml new file mode 100644 index 0000000000..bb784b76b9 --- /dev/null +++ b/eclipse-compile/appcompat/res/values-v18/dimens.xml @@ -0,0 +1,22 @@ + + + + + + + 0px + + diff --git a/eclipse-compile/appcompat/res/values-v21/styles_base.xml b/eclipse-compile/appcompat/res/values-v21/styles_base.xml index 648dfd2d80..aac01dc37e 100644 --- a/eclipse-compile/appcompat/res/values-v21/styles_base.xml +++ b/eclipse-compile/appcompat/res/values-v21/styles_base.xml @@ -78,6 +78,7 @@ - - + - - - @@ -212,18 +215,31 @@ diff --git a/eclipse-compile/appcompat/res/values-vi/strings.xml b/eclipse-compile/appcompat/res/values-vi/strings.xml index 21dd883430..9cf34c2974 100644 --- a/eclipse-compile/appcompat/res/values-vi/strings.xml +++ b/eclipse-compile/appcompat/res/values-vi/strings.xml @@ -20,9 +20,11 @@ "Điều hướng về trang chủ" "Điều hướng lên trên" "Thêm tùy chọn" + "Thu gọn" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Tìm kiếm" + "Tìm kiếm…" "Tìm kiếm truy vấn" "Xóa truy vấn" "Gửi truy vấn" diff --git a/eclipse-compile/appcompat/res/values-zh-rCN/strings.xml b/eclipse-compile/appcompat/res/values-zh-rCN/strings.xml index 54e2c86f42..a0b492a03c 100644 --- a/eclipse-compile/appcompat/res/values-zh-rCN/strings.xml +++ b/eclipse-compile/appcompat/res/values-zh-rCN/strings.xml @@ -20,9 +20,11 @@ "转到主屏幕" "转到上一层级" "更多选项" + "收起" "%1$s:%2$s" "%1$s - %2$s:%3$s" "搜索" + "搜索…" "搜索查询" "清除查询" "提交查询" diff --git a/eclipse-compile/appcompat/res/values-zh-rHK/strings.xml b/eclipse-compile/appcompat/res/values-zh-rHK/strings.xml index e35d46512a..2e373073ac 100644 --- a/eclipse-compile/appcompat/res/values-zh-rHK/strings.xml +++ b/eclipse-compile/appcompat/res/values-zh-rHK/strings.xml @@ -20,9 +20,11 @@ "瀏覽主頁" "向上瀏覽" "更多選項" + "收合" "%1$s:%2$s" "%1$s (%2$s):%3$s" "搜尋" + "搜尋…" "搜尋查詢" "清除查詢" "提交查詢" diff --git a/eclipse-compile/appcompat/res/values-zh-rTW/strings.xml b/eclipse-compile/appcompat/res/values-zh-rTW/strings.xml index 24d530cb1b..41a940101d 100644 --- a/eclipse-compile/appcompat/res/values-zh-rTW/strings.xml +++ b/eclipse-compile/appcompat/res/values-zh-rTW/strings.xml @@ -20,9 +20,11 @@ "瀏覽首頁" "向上瀏覽" "更多選項" + "收合" "%1$s:%2$s" "%1$s - %2$s:%3$s" "搜尋" + "搜尋…" "搜尋查詢" "清除查詢" "提交查詢" diff --git a/eclipse-compile/appcompat/res/values-zu/strings.xml b/eclipse-compile/appcompat/res/values-zu/strings.xml index a6a06ab2f1..48d586bba3 100644 --- a/eclipse-compile/appcompat/res/values-zu/strings.xml +++ b/eclipse-compile/appcompat/res/values-zu/strings.xml @@ -20,9 +20,11 @@ "Zulazulela ekhaya" "Zulazulela phezulu" "Izinketho eziningi" + "Goqa" "%1$s, %2$s" "%1$s, %2$s, %3$s" "Sesha" + "Iyasesha..." "Umbuzo wosesho" "Sula inkinga" "Hambisa umbuzo" diff --git a/eclipse-compile/appcompat/res/values/attrs.xml b/eclipse-compile/appcompat/res/values/attrs.xml index e2dbdea8af..62c608508c 100644 --- a/eclipse-compile/appcompat/res/values/attrs.xml +++ b/eclipse-compile/appcompat/res/values/attrs.xml @@ -42,6 +42,9 @@ in place of the usual title bar. --> + + + @@ -70,6 +73,7 @@ + @@ -161,6 +165,17 @@ + + + + + + + + + + + @@ -181,11 +196,9 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + @@ -626,6 +668,8 @@ + + @@ -655,6 +699,7 @@ + @@ -718,13 +763,6 @@ - - - - @@ -814,4 +852,12 @@ + + + + + + + + diff --git a/eclipse-compile/appcompat/res/values/colors_material.xml b/eclipse-compile/appcompat/res/values/colors_material.xml index 94448b5800..6b3cca5d48 100644 --- a/eclipse-compile/appcompat/res/values/colors_material.xml +++ b/eclipse-compile/appcompat/res/values/colors_material.xml @@ -23,12 +23,12 @@ #ffeeeeee #ff212121 - #ffbdbdbd + #ffefefef #ff000000 #ff757575 - #40ffffff - #40000000 + #4dffffff + #1f000000 @color/material_deep_teal_500 @color/material_deep_teal_200 @@ -38,6 +38,8 @@ #ffbdbdbd #fff1f1f1 + #ff616161 + #ffbdbdbd @android:color/white @android:color/black diff --git a/eclipse-compile/appcompat/res/values/config.xml b/eclipse-compile/appcompat/res/values/config.xml index a57f2e4a3f..be6a7a1a12 100644 --- a/eclipse-compile/appcompat/res/values/config.xml +++ b/eclipse-compile/appcompat/res/values/config.xml @@ -32,4 +32,10 @@ it should be disabled in that locale's resources. --> true + + 150 + 220 + + true + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/values/dimens.xml b/eclipse-compile/appcompat/res/values/dimens.xml index 54baac37d6..fb0d94caaf 100644 --- a/eclipse-compile/appcompat/res/values/dimens.xml +++ b/eclipse-compile/appcompat/res/values/dimens.xml @@ -58,9 +58,30 @@ (the screen is in landscape). This may be either a fraction or a dimension.--> 100% + 6dp + @dimen/abc_control_inset_material + + @dimen/abc_control_padding_material + 8dp + 4dp 4dp + + 2dp + + 4dp + 10dp + 7dp + + + 3dp + + 24dp + 18dp + + + 48dp diff --git a/eclipse-compile/appcompat/res/values/dimens_material.xml b/eclipse-compile/appcompat/res/values/dimens_material.xml index a620b31079..6f5f1f8f56 100644 --- a/eclipse-compile/appcompat/res/values/dimens_material.xml +++ b/eclipse-compile/appcompat/res/values/dimens_material.xml @@ -20,6 +20,8 @@ 56dp 4dp + + 16dp 16dp @@ -27,6 +29,17 @@ 5dp + + @dimen/abc_action_bar_content_inset_material + + + 0dp + + 6dp + + 10dp + 36dp 48dp 48dp @@ -50,4 +63,9 @@ 18sp 14sp + 16dp + + 0.26 + 0.30 + diff --git a/eclipse-compile/appcompat/res/values/strings.xml b/eclipse-compile/appcompat/res/values/strings.xml index 5080070da9..87cfd29cf9 100644 --- a/eclipse-compile/appcompat/res/values/strings.xml +++ b/eclipse-compile/appcompat/res/values/strings.xml @@ -41,6 +41,8 @@ Search + + Search… Search query diff --git a/eclipse-compile/appcompat/res/values/styles.xml b/eclipse-compile/appcompat/res/values/styles.xml index 1b8b53b809..13d910e23c 100644 --- a/eclipse-compile/appcompat/res/values/styles.xml +++ b/eclipse-compile/appcompat/res/values/styles.xml @@ -60,8 +60,7 @@ - - - - + + @@ -72,6 +74,7 @@ + + - - - - @@ -334,27 +334,49 @@ @drawable/abc_textfield_search_material @drawable/abc_ic_clear_mtrl_alpha @drawable/abc_ic_search_api_mtrl_alpha + @drawable/abc_ic_search_api_mtrl_alpha @drawable/abc_ic_go_search_api_mtrl_alpha @drawable/abc_ic_voice_search_api_mtrl_alpha @drawable/abc_ic_commit_search_api_mtrl_alpha @layout/abc_search_dropdown_item_icons_2line + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eclipse-compile/appcompat/res/values/styles_rtl.xml b/eclipse-compile/appcompat/res/values/styles_rtl.xml index fad129176f..53208008a5 100644 --- a/eclipse-compile/appcompat/res/values/styles_rtl.xml +++ b/eclipse-compile/appcompat/res/values/styles_rtl.xml @@ -47,14 +47,9 @@ 8dp - - + + \ No newline at end of file diff --git a/eclipse-compile/appcompat/res/values/themes.xml b/eclipse-compile/appcompat/res/values/themes.xml index 05b8657456..5742251ea2 100644 --- a/eclipse-compile/appcompat/res/values/themes.xml +++ b/eclipse-compile/appcompat/res/values/themes.xml @@ -36,12 +36,12 @@ + - - - @@ -337,8 +304,8 @@ 64dp 48dp 80dp - 16dip - 16dip + @dimen/abc_list_item_padding_horizontal_material + @dimen/abc_list_item_padding_horizontal_material @style/Widget.AppCompat.DropDownItem.Spinner @@ -359,7 +326,7 @@ @style/TextAppearance.AppCompat.SearchResult.Subtitle - @style/Widget.AppCompat.Light.ActivityChooserView + @style/Widget.AppCompat.ActivityChooserView @style/Widget.AppCompat.Toolbar @@ -368,6 +335,7 @@ @style/Widget.AppCompat.EditText @drawable/abc_edit_text_material ?android:attr/textColorPrimary + @style/Widget.AppCompat.AutoCompleteTextView @color/primary_dark_material_light @@ -377,11 +345,28 @@ ?android:attr/textColorSecondary ?attr/colorAccent @color/ripple_material_light - @color/switch_thumb_normal_material_light + @color/button_material_light + @color/switch_thumb_material_light @style/Widget.AppCompat.DrawerArrowToggle + @style/Widget.AppCompat.CompoundButton.CheckBox + @style/Widget.AppCompat.CompoundButton.RadioButton @style/Widget.AppCompat.CompoundButton.Switch + + @style/Widget.AppCompat.RatingBar + + + @style/Widget.AppCompat.Button + @style/Widget.AppCompat.Button.Small + @style/TextAppearance.AppCompat.Button + + @style/Widget.AppCompat.ButtonBar + @style/Widget.AppCompat.Button.ButtonBar.AlertDialog + + + @style/Theme.AppCompat.Light.Dialog + @dimen/abc_dialog_padding_material - - - @style/Widget.AppCompat.ActionMode - @drawable/abc_cab_background_top_material - ?attr/colorPrimaryDark - @drawable/abc_ic_ab_back_mtrl_am_alpha - @style/Widget.AppCompat.ActionButton.CloseMode + diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/ActionBar.java b/eclipse-compile/appcompat/src/android/support/v7/app/ActionBar.java new file mode 100644 index 0000000000..d48dd31772 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/ActionBar.java @@ -0,0 +1,1358 @@ +/* + * Copyright (C) 2012 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 android.support.v7.app; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.GravityCompat; +import android.support.v7.appcompat.R; +import android.support.v7.view.ActionMode; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.SpinnerAdapter; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A primary toolbar within the activity that may display the activity title, application-level + * navigation affordances, 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. + *

+ * + *

Beginning with Android L (API level 21), the action bar may be represented by any + * Toolbar widget within the application layout. The application may signal to the Activity + * which Toolbar should be treated as the Activity's action bar. Activities that use this + * feature should use one of the supplied .NoActionBar themes, set the + * {@link android.R.styleable#Theme_windowActionBar windowActionBar} attribute to false + * or otherwise not request the window feature.

+ * + *

By adjusting the window features requested by the theme and the layouts used for + * an Activity's content view, an app can use the standard system action bar on older platform + * releases and the newer inline toolbars on newer platform releases. The ActionBar + * object obtained from the Activity can be used to control either configuration transparently.

+ * + *

When using the Holo themes 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.

+ * + *

When using the Material themes (default in API 21 or newer) the navigation button + * (formerly "Home") takes over the space previously occupied by the application icon. + * Apps wishing to express a stronger branding should use their brand colors heavily + * in the action bar and other application chrome or use a {@link #setLogo(int) logo} + * in place of their standard title text.

+ * + *

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}.

+ * + *
+ *

Developer Guides

+ *

For information about how to use the action bar, including how to add action items, navigation + * modes and more, read the Action + * Bar developer guide.

+ *
+ */ +public abstract class ActionBar { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}) + public @interface NavigationMode {} + + /** + * 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + public static final int NAVIGATION_MODE_STANDARD = 0; + + /** + * 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + public static final int NAVIGATION_MODE_LIST = 1; + + /** + * Tab navigation mode. Instead of static title text this mode + * presents a series of tabs for navigation within the activity. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + public static final int NAVIGATION_MODE_TABS = 2; + + /** @hide */ + @IntDef(flag=true, value={ + DISPLAY_USE_LOGO, + DISPLAY_SHOW_HOME, + DISPLAY_HOME_AS_UP, + DISPLAY_SHOW_TITLE, + DISPLAY_SHOW_CUSTOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DisplayOptions {} + + /** + * 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 = 0x1; + + /** + * 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 = 0x2; + + /** + * 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 = 0x4; + + /** + * 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 = 0x8; + + /** + * 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 = 0x10; + + /** + * 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(@DrawableRes 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(@DrawableRes 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + public abstract int getSelectedNavigationIndex(); + + /** + * Get the number of navigation items present in the current navigation mode. + * + * @return Number of navigation items. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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(@StringRes 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(@DisplayOptions 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(@DisplayOptions int options, @DisplayOptions 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(@Nullable 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. + */ + @Nullable + 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. + */ + @Nullable + 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + @NavigationMode + 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 + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + public abstract void setNavigationMode(@NavigationMode int mode); + + /** + * @return The current set of display options. + */ + @DisplayOptions + 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) + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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 + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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 + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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 + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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 + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + public abstract void removeTabAt(int position); + + /** + * Remove all tabs from the action bar and deselect the current tab. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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 + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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 + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + @Nullable + public abstract Tab getSelectedTab(); + + /** + * Returns the tab at the specified index. + * + * @param index Index value in the range 0-get + * @return + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + public abstract Tab getTabAt(int index); + + /** + * Returns the number of tabs currently registered with the action bar. + * + * @return Tab count + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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. + * + *

If you are hiding the ActionBar through + * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}, + * you should not call this function directly. + */ + 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. + * + *

Instead of calling this function directly, you can also cause an + * ActionBar using the overlay feature to hide through + * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}. + * Hiding the ActionBar through this system UI flag allows you to more + * seamlessly hide it in conjunction with other screen decorations. + */ + 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; + } + + /** + * Returns true if the Title field has been truncated during layout for lack + * of available space. + * + * @return true if the Title field has been truncated + * @hide pending API approval + */ + public boolean isTitleTruncated() { return false; } + + /** + * Set an alternate drawable to display next to the icon/logo/title + * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using + * this mode to display an alternate selection for up navigation, such as a sliding drawer. + * + *

If you pass null to this method, the default drawable from the theme + * will be used.

+ * + *

If you implement alternate or intermediate behavior around Up, you should also + * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()} + * to provide a correct description of the action for accessibility support.

+ * + * @param indicator A drawable to use for the up indicator, or null to use the theme's default + * + * @see #setDisplayOptions(int, int) + * @see #setDisplayHomeAsUpEnabled(boolean) + * @see #setHomeActionContentDescription(int) + */ + public void setHomeAsUpIndicator(@Nullable Drawable indicator) {} + + /** + * Set an alternate drawable to display next to the icon/logo/title + * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using + * this mode to display an alternate selection for up navigation, such as a sliding drawer. + * + *

If you pass 0 to this method, the default drawable from the theme + * will be used.

+ * + *

If you implement alternate or intermediate behavior around Up, you should also + * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()} + * to provide a correct description of the action for accessibility support.

+ * + * @param resId Resource ID of a drawable to use for the up indicator, or 0 + * to use the theme's default + * + * @see #setDisplayOptions(int, int) + * @see #setDisplayHomeAsUpEnabled(boolean) + * @see #setHomeActionContentDescription(int) + */ + public void setHomeAsUpIndicator(@DrawableRes int resId) {} + + /** + * Set an alternate description for the Home/Up action, when enabled. + * + *

This description is commonly used for accessibility/screen readers when + * the Home action is enabled. (See {@link #setDisplayHomeAsUpEnabled(boolean)}.) + * Examples of this are, "Navigate Home" or "Navigate Up" depending on the + * {@link #DISPLAY_HOME_AS_UP} display option. If you have changed the home-as-up + * indicator using {@link #setHomeAsUpIndicator(int)} to indicate more specific + * functionality such as a sliding drawer, you should also set this to accurately + * describe the action.

+ * + *

Setting this to null will use the system default description.

+ * + * @param description New description for the Home action when enabled + * @see #setHomeAsUpIndicator(int) + * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable) + */ + public void setHomeActionContentDescription(@Nullable CharSequence description) {} + + /** + * Set an alternate description for the Home/Up action, when enabled. + * + *

This description is commonly used for accessibility/screen readers when + * the Home action is enabled. (See {@link #setDisplayHomeAsUpEnabled(boolean)}.) + * Examples of this are, "Navigate Home" or "Navigate Up" depending on the + * {@link #DISPLAY_HOME_AS_UP} display option. If you have changed the home-as-up + * indicator using {@link #setHomeAsUpIndicator(int)} to indicate more specific + * functionality such as a sliding drawer, you should also set this to accurately + * describe the action.

+ * + *

Setting this to 0 will use the system default description.

+ * + * @param resId Resource ID of a string to use as the new description + * for the Home action when enabled + * @see #setHomeAsUpIndicator(int) + * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable) + */ + public void setHomeActionContentDescription(@StringRes int resId) {} + + /** + * Enable hiding the action bar on content scroll. + * + *

If enabled, the action bar will scroll out of sight along with a + * {@link View#setNestedScrollingEnabled(boolean) nested scrolling child} view's content. + * The action bar must be in {@link Window#FEATURE_ACTION_BAR_OVERLAY overlay mode} + * to enable hiding on content scroll.

+ * + *

When partially scrolled off screen the action bar is considered + * {@link #hide() hidden}. A call to {@link #show() show} will cause it to return to full view. + *

+ * @param hideOnContentScroll true to enable hiding on content scroll. + */ + public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { + if (hideOnContentScroll) { + throw new UnsupportedOperationException("Hide on content scroll is not supported in " + + "this action bar configuration."); + } + } + + /** + * Return whether the action bar is configured to scroll out of sight along with + * a {@link View#setNestedScrollingEnabled(boolean) nested scrolling child}. + * + * @return true if hide-on-content-scroll is enabled + * @see #setHideOnContentScrollEnabled(boolean) + */ + public boolean isHideOnContentScrollEnabled() { + return false; + } + + /** + * Return the current vertical offset of the action bar. + * + *

The action bar's current hide offset is the distance that the action bar is currently + * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's + * current measured {@link #getHeight() height} (fully invisible).

+ * + * @return The action bar's offset toward its fully hidden state in pixels + */ + public int getHideOffset() { + return 0; + } + + /** + * Set the current hide offset of the action bar. + * + *

The action bar's current hide offset is the distance that the action bar is currently + * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's + * current measured {@link #getHeight() height} (fully invisible).

+ * + * @param offset The action bar's offset toward its fully hidden state in pixels. + */ + public void setHideOffset(int offset) { + if (offset != 0) { + throw new UnsupportedOperationException("Setting an explicit action bar hide offset " + + "is not supported in this action bar configuration."); + } + } + + /** + * Set the Z-axis elevation of the action bar in pixels. + * + *

The action bar's elevation is the distance it is placed from its parent surface. Higher + * values are closer to the user.

+ * + * @param elevation Elevation value in pixels + */ + public void setElevation(float elevation) { + if (elevation != 0) { + throw new UnsupportedOperationException("Setting a non-zero elevation is " + + "not supported in this action bar configuration."); + } + } + + /** + * Get the Z-axis elevation of the action bar in pixels. + * + *

The action bar's elevation is the distance it is placed from its parent surface. Higher + * values are closer to the user.

+ * + * @return Elevation value in pixels + */ + public float getElevation() { + return 0; + } + + /** @hide */ + public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) { + } + + /** @hide */ + public void setShowHideAnimationEnabled(boolean enabled) { + } + + /** @hide */ + public void onConfigurationChanged(Configuration config) { + } + + /** @hide */ + public void dispatchMenuVisibilityChanged(boolean visible) { + } + + /** @hide */ + public ActionMode startActionMode(ActionMode.Callback callback) { + return null; + } + + /** @hide */ + public boolean openOptionsMenu() { + return false; + } + + /** @hide */ + public boolean invalidateOptionsMenu() { + return false; + } + + /** @hide */ + public boolean onMenuKeyEvent(KeyEvent event) { + return false; + } + + /** @hide **/ + public boolean onKeyShortcut(int keyCode, KeyEvent ev) { + return false; + } + + /** @hide */ + public boolean collapseActionView() { + return false; + } + + /** @hide */ + public void setWindowTitle(CharSequence title) { + } + + /** + * Listener interface for ActionBar navigation events. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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(@DrawableRes 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. + * + * @deprecated Action bar navigation modes are deprecated and not supported by inline + * toolbar action bars. Consider using other + * common + * navigation patterns instead. + */ + 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. + */ + public static class LayoutParams extends ViewGroup.MarginLayoutParams { + /** + * Gravity for the view associated with these LayoutParams. + * + * @see android.view.Gravity + */ + public int gravity = Gravity.NO_GRAVITY; + + public LayoutParams(@NonNull Context c, AttributeSet attrs) { + super(c, attrs); + + TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ActionBarLayout); + gravity = a.getInt(R.styleable.ActionBarLayout_android_layout_gravity, Gravity.NO_GRAVITY); + a.recycle(); + } + + public LayoutParams(int width, int height) { + super(width, height); + this.gravity = Gravity.CENTER_VERTICAL | GravityCompat.START; + } + + public LayoutParams(int width, int height, int gravity) { + super(width, height); + this.gravity = gravity; + } + + public LayoutParams(int gravity) { + this(WRAP_CONTENT, MATCH_PARENT, gravity); + } + + public LayoutParams(LayoutParams source) { + super(source); + + this.gravity = source.gravity; + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarActivity.java b/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarActivity.java new file mode 100644 index 0000000000..1834681d80 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarActivity.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2012 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 android.support.v7.app; + +/** + * @deprecated Use {@link android.support.v7.app.AppCompatActivity} instead. + */ +@Deprecated +public class ActionBarActivity extends AppCompatActivity { +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java b/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java new file mode 100644 index 0000000000..93a79a63d3 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2014 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 android.support.v7.app; + +import android.app.Activity; +import android.app.ActionBar; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.support.v7.appcompat.R; + +/** + * This class provides a handy way to tie together the functionality of + * {@link android.support.v4.widget.DrawerLayout} and the framework ActionBar to + * implement the recommended design for navigation drawers. + * + *

To use ActionBarDrawerToggle, create one in your Activity and call through + * to the following methods corresponding to your Activity callbacks:

+ * + *
    + *
  • {@link android.app.Activity#onConfigurationChanged(android.content.res.Configuration) + * onConfigurationChanged} + *
  • {@link android.app.Activity#onOptionsItemSelected(android.view.MenuItem) + * onOptionsItemSelected}
  • + *
+ * + *

Call {@link #syncState()} from your Activity's + * {@link android.app.Activity#onPostCreate(android.os.Bundle) onPostCreate} to synchronize the + * indicator with the state of the linked DrawerLayout after onRestoreInstanceState + * has occurred.

+ * + *

ActionBarDrawerToggle can be used directly as a + * {@link android.support.v4.widget.DrawerLayout.DrawerListener}, or if you are already providing + * your own listener, call through to each of the listener methods from your own.

+ * + *

+ * You can customize the the animated toggle by defining the + * {@link android.support.v7.appcompat.R.styleable#DrawerArrowToggle drawerArrowStyle} in your + * ActionBar theme. + */ +public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { + + /** + * Allows an implementing Activity to return an {@link ActionBarDrawerToggle.Delegate} to use + * with ActionBarDrawerToggle. + */ + public interface DelegateProvider { + + /** + * @return Delegate to use for ActionBarDrawableToggles, or null if the Activity + * does not wish to override the default behavior. + */ + @Nullable + Delegate getDrawerToggleDelegate(); + } + + public interface Delegate { + + /** + * Set the Action Bar's up indicator drawable and content description. + * + * @param upDrawable - Drawable to set as up indicator + * @param contentDescRes - Content description to set + */ + void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes); + + /** + * Set the Action Bar's up indicator content description. + * + * @param contentDescRes - Content description to set + */ + void setActionBarDescription(@StringRes int contentDescRes); + + /** + * Returns the drawable to be set as up button when DrawerToggle is disabled + */ + Drawable getThemeUpIndicator(); + + /** + * Returns the context of ActionBar + */ + Context getActionBarThemedContext(); + + /** + * Returns whether navigation icon is visible or not. + * Used to print warning messages in case developer forgets to set displayHomeAsUp to true + */ + boolean isNavigationVisible(); + } + + private final Delegate mActivityImpl; + private final DrawerLayout mDrawerLayout; + + private DrawerToggle mSlider; + private Drawable mHomeAsUpIndicator; + private boolean mDrawerIndicatorEnabled = true; + private boolean mHasCustomUpIndicator; + private final int mOpenDrawerContentDescRes; + private final int mCloseDrawerContentDescRes; + // used in toolbar mode when DrawerToggle is disabled + private View.OnClickListener mToolbarNavigationClickListener; + // If developer does not set displayHomeAsUp, DrawerToggle won't show up. + // DrawerToggle logs a warning if this case is detected + private boolean mWarnedForDisplayHomeAsUp = false; + + /** + * Construct a new ActionBarDrawerToggle. + * + *

The given {@link Activity} will be linked to the specified {@link DrawerLayout} and + * its Actionbar's Up button will be set to a custom drawable. + *

This drawable shows a Hamburger icon when drawer is closed and an arrow when drawer + * is open. It animates between these two states as the drawer opens.

+ * + *

String resources must be provided to describe the open/close drawer actions for + * accessibility services.

+ * + * @param activity The Activity hosting the drawer. Should have an ActionBar. + * @param drawerLayout The DrawerLayout to link to the given Activity's ActionBar + * @param openDrawerContentDescRes A String resource to describe the "open drawer" action + * for accessibility + * @param closeDrawerContentDescRes A String resource to describe the "close drawer" action + * for accessibility + */ + public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, + @StringRes int openDrawerContentDescRes, + @StringRes int closeDrawerContentDescRes) { + this(activity, null, drawerLayout, null, openDrawerContentDescRes, + closeDrawerContentDescRes); + } + + /** + * Construct a new ActionBarDrawerToggle with a Toolbar. + *

+ * The given {@link Activity} will be linked to the specified {@link DrawerLayout} and + * the Toolbar's navigation icon will be set to a custom drawable. Using this constructor + * will set Toolbar's navigation click listener to toggle the drawer when it is clicked. + *

+ * This drawable shows a Hamburger icon when drawer is closed and an arrow when drawer + * is open. It animates between these two states as the drawer opens. + *

+ * String resources must be provided to describe the open/close drawer actions for + * accessibility services. + *

+ * Please use {@link #ActionBarDrawerToggle(Activity, DrawerLayout, int, int)} if you are + * setting the Toolbar as the ActionBar of your activity. + * + * @param activity The Activity hosting the drawer. + * @param toolbar The toolbar to use if you have an independent Toolbar. + * @param drawerLayout The DrawerLayout to link to the given Activity's ActionBar + * @param openDrawerContentDescRes A String resource to describe the "open drawer" action + * for accessibility + * @param closeDrawerContentDescRes A String resource to describe the "close drawer" action + * for accessibility + */ + public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, + Toolbar toolbar, @StringRes int openDrawerContentDescRes, + @StringRes int closeDrawerContentDescRes) { + this(activity, toolbar, drawerLayout, null, openDrawerContentDescRes, + closeDrawerContentDescRes); + } + + /** + * In the future, we can make this constructor public if we want to let developers customize + * the + * animation. + */ + ActionBarDrawerToggle(Activity activity, Toolbar toolbar, + DrawerLayout drawerLayout, T slider, + @StringRes int openDrawerContentDescRes, + @StringRes int closeDrawerContentDescRes) { + if (toolbar != null) { + mActivityImpl = new ToolbarCompatDelegate(toolbar); + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mDrawerIndicatorEnabled) { + toggle(); + } else if (mToolbarNavigationClickListener != null) { + mToolbarNavigationClickListener.onClick(v); + } + } + }); + } else if (activity instanceof DelegateProvider) { // Allow the Activity to provide an impl + mActivityImpl = ((DelegateProvider) activity).getDrawerToggleDelegate(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mActivityImpl = new JellybeanMr2Delegate(activity); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mActivityImpl = new HoneycombDelegate(activity); + } else { + mActivityImpl = new DummyDelegate(activity); + } + + mDrawerLayout = drawerLayout; + mOpenDrawerContentDescRes = openDrawerContentDescRes; + mCloseDrawerContentDescRes = closeDrawerContentDescRes; + if (slider == null) { + mSlider = new DrawerArrowDrawableToggle(activity, + mActivityImpl.getActionBarThemedContext()); + } else { + mSlider = slider; + } + + mHomeAsUpIndicator = getThemeUpIndicator(); + } + + /** + * Synchronize the state of the drawer indicator/affordance with the linked DrawerLayout. + * + *

This should be called from your Activity's + * {@link Activity#onPostCreate(android.os.Bundle) onPostCreate} method to synchronize after + * the DrawerLayout's instance state has been restored, and any other time when the state + * may have diverged in such a way that the ActionBarDrawerToggle was not notified. + * (For example, if you stop forwarding appropriate drawer events for a period of time.)

+ */ + public void syncState() { + if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) { + mSlider.setPosition(1); + } else { + mSlider.setPosition(0); + } + if (mDrawerIndicatorEnabled) { + setActionBarUpIndicator((Drawable) mSlider, + mDrawerLayout.isDrawerOpen(GravityCompat.START) ? + mCloseDrawerContentDescRes : mOpenDrawerContentDescRes); + } + } + + /** + * This method should always be called by your Activity's + * {@link Activity#onConfigurationChanged(android.content.res.Configuration) + * onConfigurationChanged} + * method. + * + * @param newConfig The new configuration + */ + public void onConfigurationChanged(Configuration newConfig) { + // Reload drawables that can change with configuration + if (!mHasCustomUpIndicator) { + mHomeAsUpIndicator = getThemeUpIndicator(); + } + syncState(); + } + + /** + * This method should be called by your Activity's + * {@link Activity#onOptionsItemSelected(android.view.MenuItem) onOptionsItemSelected} method. + * If it returns true, your onOptionsItemSelected method should return true and + * skip further processing. + * + * @param item the MenuItem instance representing the selected menu item + * @return true if the event was handled and further processing should not occur + */ + public boolean onOptionsItemSelected(MenuItem item) { + if (item != null && item.getItemId() == android.R.id.home && mDrawerIndicatorEnabled) { + toggle(); + return true; + } + return false; + } + + private void toggle() { + if (mDrawerLayout.isDrawerVisible(GravityCompat.START)) { + mDrawerLayout.closeDrawer(GravityCompat.START); + } else { + mDrawerLayout.openDrawer(GravityCompat.START); + } + } + + /** + * Set the up indicator to display when the drawer indicator is not + * enabled. + *

+ * If you pass null to this method, the default drawable from + * the theme will be used. + * + * @param indicator A drawable to use for the up indicator, or null to use + * the theme's default + * @see #setDrawerIndicatorEnabled(boolean) + */ + public void setHomeAsUpIndicator(Drawable indicator) { + if (indicator == null) { + mHomeAsUpIndicator = getThemeUpIndicator(); + mHasCustomUpIndicator = false; + } else { + mHomeAsUpIndicator = indicator; + mHasCustomUpIndicator = true; + } + + if (!mDrawerIndicatorEnabled) { + setActionBarUpIndicator(mHomeAsUpIndicator, 0); + } + } + + /** + * Set the up indicator to display when the drawer indicator is not + * enabled. + *

+ * If you pass 0 to this method, the default drawable from the theme will + * be used. + * + * @param resId Resource ID of a drawable to use for the up indicator, or 0 + * to use the theme's default + * @see #setDrawerIndicatorEnabled(boolean) + */ + public void setHomeAsUpIndicator(int resId) { + Drawable indicator = null; + if (resId != 0) { + indicator = mDrawerLayout.getResources().getDrawable(resId); + } + setHomeAsUpIndicator(indicator); + } + + /** + * @return true if the enhanced drawer indicator is enabled, false otherwise + * @see #setDrawerIndicatorEnabled(boolean) + */ + public boolean isDrawerIndicatorEnabled() { + return mDrawerIndicatorEnabled; + } + + /** + * Enable or disable the drawer indicator. The indicator defaults to enabled. + * + *

When the indicator is disabled, the ActionBar will revert to displaying + * the home-as-up indicator provided by the Activity's theme in the + * android.R.attr.homeAsUpIndicator attribute instead of the animated + * drawer glyph.

+ * + * @param enable true to enable, false to disable + */ + public void setDrawerIndicatorEnabled(boolean enable) { + if (enable != mDrawerIndicatorEnabled) { + if (enable) { + setActionBarUpIndicator((Drawable) mSlider, + mDrawerLayout.isDrawerOpen(GravityCompat.START) ? + mCloseDrawerContentDescRes : mOpenDrawerContentDescRes); + } else { + setActionBarUpIndicator(mHomeAsUpIndicator, 0); + } + mDrawerIndicatorEnabled = enable; + } + } + + + /** + * {@link DrawerLayout.DrawerListener} callback method. If you do not use your + * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call + * through to this method from your own listener object. + * + * @param drawerView The child view that was moved + * @param slideOffset The new offset of this drawer within its range, from 0-1 + */ + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + mSlider.setPosition(Math.min(1f, Math.max(0, slideOffset))); + } + + /** + * {@link DrawerLayout.DrawerListener} callback method. If you do not use your + * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call + * through to this method from your own listener object. + * + * @param drawerView Drawer view that is now open + */ + @Override + public void onDrawerOpened(View drawerView) { + mSlider.setPosition(1); + if (mDrawerIndicatorEnabled) { + setActionBarDescription(mCloseDrawerContentDescRes); + } + } + + /** + * {@link DrawerLayout.DrawerListener} callback method. If you do not use your + * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call + * through to this method from your own listener object. + * + * @param drawerView Drawer view that is now closed + */ + @Override + public void onDrawerClosed(View drawerView) { + mSlider.setPosition(0); + if (mDrawerIndicatorEnabled) { + setActionBarDescription(mOpenDrawerContentDescRes); + } + } + + /** + * {@link DrawerLayout.DrawerListener} callback method. If you do not use your + * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call + * through to this method from your own listener object. + * + * @param newState The new drawer motion state + */ + @Override + public void onDrawerStateChanged(int newState) { + } + + /** + * Returns the fallback listener for Navigation icon click events. + * + * @return The click listener which receives Navigation click events from Toolbar when + * drawer indicator is disabled. + * @see #setToolbarNavigationClickListener(android.view.View.OnClickListener) + * @see #setDrawerIndicatorEnabled(boolean) + * @see #isDrawerIndicatorEnabled() + */ + public View.OnClickListener getToolbarNavigationClickListener() { + return mToolbarNavigationClickListener; + } + + /** + * When DrawerToggle is constructed with a Toolbar, it sets the click listener on + * the Navigation icon. If you want to listen for clicks on the Navigation icon when + * DrawerToggle is disabled ({@link #setDrawerIndicatorEnabled(boolean)}, you should call this + * method with your listener and DrawerToggle will forward click events to that listener + * when drawer indicator is disabled. + * + * @see #setDrawerIndicatorEnabled(boolean) + */ + public void setToolbarNavigationClickListener( + View.OnClickListener onToolbarNavigationClickListener) { + mToolbarNavigationClickListener = onToolbarNavigationClickListener; + } + + void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { + if (!mWarnedForDisplayHomeAsUp && !mActivityImpl.isNavigationVisible()) { + Log.w("ActionBarDrawerToggle", "DrawerToggle may not show up because NavigationIcon" + + " is not visible. You may need to call " + + "actionbar.setDisplayHomeAsUpEnabled(true);"); + mWarnedForDisplayHomeAsUp = true; + } + mActivityImpl.setActionBarUpIndicator(upDrawable, contentDescRes); + } + + void setActionBarDescription(int contentDescRes) { + mActivityImpl.setActionBarDescription(contentDescRes); + } + + Drawable getThemeUpIndicator() { + return mActivityImpl.getThemeUpIndicator(); + } + + static class DrawerArrowDrawableToggle extends DrawerArrowDrawable + implements DrawerToggle { + + private final Activity mActivity; + + public DrawerArrowDrawableToggle(Activity activity, Context themedContext) { + super(themedContext); + mActivity = activity; + } + + public void setPosition(float position) { + if (position == 1f) { + setVerticalMirror(true); + } else if (position == 0f) { + setVerticalMirror(false); + } + super.setProgress(position); + } + + @Override + boolean isLayoutRtl() { + return ViewCompat.getLayoutDirection(mActivity.getWindow().getDecorView()) + == ViewCompat.LAYOUT_DIRECTION_RTL; + } + + public float getPosition() { + return super.getProgress(); + } + } + + /** + * Interface for toggle drawables. Can be public in the future + */ + static interface DrawerToggle { + + public void setPosition(float position); + + public float getPosition(); + } + + /** + * Delegate if SDK version is between honeycomb and JBMR2 + */ + private static class HoneycombDelegate implements Delegate { + + final Activity mActivity; + ActionBarDrawerToggleHoneycomb.SetIndicatorInfo mSetIndicatorInfo; + + private HoneycombDelegate(Activity activity) { + mActivity = activity; + } + + @Override + public Drawable getThemeUpIndicator() { + return ActionBarDrawerToggleHoneycomb.getThemeUpIndicator(mActivity); + } + + @Override + public Context getActionBarThemedContext() { + final ActionBar actionBar = mActivity.getActionBar(); + final Context context; + if (actionBar != null) { + context = actionBar.getThemedContext(); + } else { + context = mActivity; + } + return context; + } + + @Override + public boolean isNavigationVisible() { + final ActionBar actionBar = mActivity.getActionBar(); + return actionBar != null + && (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; + } + + @Override + public void setActionBarUpIndicator(Drawable themeImage, int contentDescRes) { + mActivity.getActionBar().setDisplayShowHomeEnabled(true); + mSetIndicatorInfo = ActionBarDrawerToggleHoneycomb.setActionBarUpIndicator( + mSetIndicatorInfo, mActivity, themeImage, contentDescRes); + mActivity.getActionBar().setDisplayShowHomeEnabled(false); + } + + @Override + public void setActionBarDescription(int contentDescRes) { + mSetIndicatorInfo = ActionBarDrawerToggleHoneycomb.setActionBarDescription( + mSetIndicatorInfo, mActivity, contentDescRes); + } + } + + /** + * Delegate if SDK version is JB MR2 or newer + */ + private static class JellybeanMr2Delegate implements Delegate { + + final Activity mActivity; + + private JellybeanMr2Delegate(Activity activity) { + mActivity = activity; + } + + @Override + public Drawable getThemeUpIndicator() { + final TypedArray a = getActionBarThemedContext().obtainStyledAttributes(null, + new int[]{android.R.attr.homeAsUpIndicator}, android.R.attr.actionBarStyle, 0); + final Drawable result = a.getDrawable(0); + a.recycle(); + return result; + } + + @Override + public Context getActionBarThemedContext() { + final ActionBar actionBar = mActivity.getActionBar(); + final Context context; + if (actionBar != null) { + context = actionBar.getThemedContext(); + } else { + context = mActivity; + } + return context; + } + + @Override + public boolean isNavigationVisible() { + final ActionBar actionBar = mActivity.getActionBar(); + return actionBar != null && + (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; + } + + @Override + public void setActionBarUpIndicator(Drawable drawable, int contentDescRes) { + final ActionBar actionBar = mActivity.getActionBar(); + if (actionBar != null) { + actionBar.setHomeAsUpIndicator(drawable); + actionBar.setHomeActionContentDescription(contentDescRes); + } + } + + @Override + public void setActionBarDescription(int contentDescRes) { + final ActionBar actionBar = mActivity.getActionBar(); + if (actionBar != null) { + actionBar.setHomeActionContentDescription(contentDescRes); + } + } + } + + /** + * Used when DrawerToggle is initialized with a Toolbar + */ + static class ToolbarCompatDelegate implements Delegate { + + final Toolbar mToolbar; + final Drawable mDefaultUpIndicator; + final CharSequence mDefaultContentDescription; + + ToolbarCompatDelegate(Toolbar toolbar) { + mToolbar = toolbar; + mDefaultUpIndicator = toolbar.getNavigationIcon(); + mDefaultContentDescription = toolbar.getNavigationContentDescription(); + } + + @Override + public void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes) { + mToolbar.setNavigationIcon(upDrawable); + setActionBarDescription(contentDescRes); + } + + @Override + public void setActionBarDescription(@StringRes int contentDescRes) { + if (contentDescRes == 0) { + mToolbar.setNavigationContentDescription(mDefaultContentDescription); + } else { + mToolbar.setNavigationContentDescription(contentDescRes); + } + } + + @Override + public Drawable getThemeUpIndicator() { + return mDefaultUpIndicator; + } + + @Override + public Context getActionBarThemedContext() { + return mToolbar.getContext(); + } + + @Override + public boolean isNavigationVisible() { + return true; + } + } + + /** + * Fallback delegate + */ + static class DummyDelegate implements Delegate { + final Activity mActivity; + + DummyDelegate(Activity activity) { + mActivity = activity; + } + + @Override + public void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes) { + + } + + @Override + public void setActionBarDescription(@StringRes int contentDescRes) { + + } + + @Override + public Drawable getThemeUpIndicator() { + return null; + } + + @Override + public Context getActionBarThemedContext() { + return mActivity; + } + + @Override + public boolean isNavigationVisible() { + return true; + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarDrawerToggleHoneycomb.java b/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarDrawerToggleHoneycomb.java new file mode 100644 index 0000000000..f25365ee1d --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/ActionBarDrawerToggleHoneycomb.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014 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 android.support.v7.app; + +import android.R; +import android.app.ActionBar; +import android.app.Activity; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import java.lang.reflect.Method; + +/** + * This class encapsulates some awful hacks. + * + * Before JB-MR2 (API 18) it was not possible to change the home-as-up indicator glyph + * in an action bar without some really gross hacks. Since the MR2 SDK is not published as of + * this writing, the new API is accessed via reflection here if available. + * + * Moved from Support-v4 + */ +class ActionBarDrawerToggleHoneycomb { + private static final String TAG = "ActionBarDrawerToggleHoneycomb"; + + private static final int[] THEME_ATTRS = new int[] { + R.attr.homeAsUpIndicator + }; + + public static SetIndicatorInfo setActionBarUpIndicator(SetIndicatorInfo info, Activity activity, + Drawable drawable, int contentDescRes) { + if (true || info == null) { + info = new SetIndicatorInfo(activity); + } + if (info.setHomeAsUpIndicator != null) { + try { + final ActionBar actionBar = activity.getActionBar(); + info.setHomeAsUpIndicator.invoke(actionBar, drawable); + info.setHomeActionContentDescription.invoke(actionBar, contentDescRes); + } catch (Exception e) { + Log.w(TAG, "Couldn't set home-as-up indicator via JB-MR2 API", e); + } + } else if (info.upIndicatorView != null) { + info.upIndicatorView.setImageDrawable(drawable); + } else { + Log.w(TAG, "Couldn't set home-as-up indicator"); + } + return info; + } + + public static SetIndicatorInfo setActionBarDescription(SetIndicatorInfo info, Activity activity, + int contentDescRes) { + if (info == null) { + info = new SetIndicatorInfo(activity); + } + if (info.setHomeAsUpIndicator != null) { + try { + final ActionBar actionBar = activity.getActionBar(); + info.setHomeActionContentDescription.invoke(actionBar, contentDescRes); + if (Build.VERSION.SDK_INT <= 19) { + // For API 19 and earlier, we need to manually force the + // action bar to generate a new content description. + actionBar.setSubtitle(actionBar.getSubtitle()); + } + } catch (Exception e) { + Log.w(TAG, "Couldn't set content description via JB-MR2 API", e); + } + } + return info; + } + + public static Drawable getThemeUpIndicator(Activity activity) { + final TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS); + final Drawable result = a.getDrawable(0); + a.recycle(); + return result; + } + + static class SetIndicatorInfo { + public Method setHomeAsUpIndicator; + public Method setHomeActionContentDescription; + public ImageView upIndicatorView; + + SetIndicatorInfo(Activity activity) { + try { + setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod("setHomeAsUpIndicator", + Drawable.class); + setHomeActionContentDescription = ActionBar.class.getDeclaredMethod( + "setHomeActionContentDescription", Integer.TYPE); + + // If we got the method we won't need the stuff below. + return; + } catch (NoSuchMethodException e) { + // Oh well. We'll use the other mechanism below instead. + } + + final View home = activity.findViewById(android.R.id.home); + if (home == null) { + // Action bar doesn't have a known configuration, an OEM messed with things. + return; + } + + final ViewGroup parent = (ViewGroup) home.getParent(); + final int childCount = parent.getChildCount(); + if (childCount != 2) { + // No idea which one will be the right one, an OEM messed with things. + return; + } + + final View first = parent.getChildAt(0); + final View second = parent.getChildAt(1); + final View up = first.getId() == android.R.id.home ? second : first; + + if (up instanceof ImageView) { + // Jackpot! (Probably...) + upIndicatorView = (ImageView) up; + } + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatActivity.java b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatActivity.java new file mode 100644 index 0000000000..5be3d3ddf3 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatActivity.java @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2015 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 android.support.v7.app; + +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.NavUtils; +import android.support.v4.app.TaskStackBuilder; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * Base class for activities that use the + * support library action bar features. + * + *

You can add an {@link android.support.v7.app.ActionBar} to your activity when running on API level 7 or higher + * by extending this class for your activity and setting the activity theme to + * {@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} or a similar theme. + * + *

+ *

Developer Guides

+ * + *

For information about how to use the action bar, including how to add action items, navigation + * modes and more, read the Action + * Bar API guide.

+ *
+ */ +public class AppCompatActivity extends FragmentActivity implements AppCompatCallback, + TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + super.onCreate(savedInstanceState); + getDelegate().onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + /** + * Support library version of {@link android.app.Activity#getActionBar}. + * + *

Retrieve a reference to this activity's ActionBar. + * + * @return The Activity's ActionBar, or null if it does not have one. + */ + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + /** + * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link android.support.v7.app.ActionBar} for this + * Activity window. + * + *

When set to a non-null value the {@link #getActionBar()} method will return + * an {@link android.support.v7.app.ActionBar} object that can be used to control the given toolbar as if it were + * a traditional window decor action bar. The toolbar's menu will be populated with the + * Activity's options menu and the navigation button will be wired through the standard + * {@link android.R.id#home home} menu select action.

+ * + *

In order to use a Toolbar within the Activity's window content the application + * must not request the window feature {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.

+ * + * @param toolbar Toolbar to set as the Activity's action bar + */ + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) { + if (super.onMenuItemSelected(featureId, item)) { + return true; + } + + final ActionBar ab = getSupportActionBar(); + if (item.getItemId() == android.R.id.home && ab != null && + (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + return onSupportNavigateUp(); + } + return false; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + /** + * Enable extended support library window features. + *

+ * This is a convenience for calling + * {@link android.view.Window#requestFeature getWindow().requestFeature()}. + *

+ * + * @param featureId The desired feature as defined in + * {@link android.view.Window} or {@link android.support.v4.view.WindowCompat}. + * @return Returns true if the requested feature is supported and now enabled. + * + * @see android.app.Activity#requestWindowFeature + * @see android.view.Window#requestFeature + */ + public boolean supportRequestWindowFeature(int featureId) { + return getDelegate().requestWindowFeature(featureId); + } + + @Override + public void supportInvalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + /** + * @hide + */ + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + /** + * Notifies the Activity that a support action mode has been started. + * Activity subclasses overriding this method should call the superclass implementation. + * + * @param mode The new action mode. + */ + public void onSupportActionModeStarted(ActionMode mode) { + } + + /** + * Notifies the activity that a support action mode has finished. + * Activity subclasses overriding this method should call the superclass implementation. + * + * @param mode The action mode that just finished. + */ + public void onSupportActionModeFinished(ActionMode mode) { + } + + public ActionMode startSupportActionMode(ActionMode.Callback callback) { + return getDelegate().startSupportActionMode(callback); + } + + /** + * @deprecated Progress bars are no longer provided in AppCompat. + */ + @Deprecated + public void setSupportProgressBarVisibility(boolean visible) { + } + + /** + * @deprecated Progress bars are no longer provided in AppCompat. + */ + @Deprecated + public void setSupportProgressBarIndeterminateVisibility(boolean visible) { + } + + /** + * @deprecated Progress bars are no longer provided in AppCompat. + */ + @Deprecated + public void setSupportProgressBarIndeterminate(boolean indeterminate) { + } + + /** + * @deprecated Progress bars are no longer provided in AppCompat. + */ + @Deprecated + public void setSupportProgress(int progress) { + } + + /** + * Support version of {@link #onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)}. + * This method will be called on all platform versions. + * + * Define the synthetic task stack that will be generated during Up navigation from + * a different task. + * + *

The default implementation of this method adds the parent chain of this activity + * as specified in the manifest to the supplied {@link android.support.v4.app.TaskStackBuilder}. Applications + * may choose to override this method to construct the desired task stack in a different + * way.

+ * + *

This method will be invoked by the default implementation of {@link #onNavigateUp()} + * if {@link #shouldUpRecreateTask(android.content.Intent)} returns true when supplied with the intent + * returned by {@link #getParentActivityIntent()}.

+ * + *

Applications that wish to supply extra Intent parameters to the parent stack defined + * by the manifest should override + * {@link #onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}.

+ * + * @param builder An empty TaskStackBuilder - the application should add intents representing + * the desired task stack + */ + public void onCreateSupportNavigateUpTaskStack(TaskStackBuilder builder) { + builder.addParentStack(this); + } + + /** + * Support version of {@link #onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder)}. + * This method will be called on all platform versions. + * + * Prepare the synthetic task stack that will be generated during Up navigation + * from a different task. + * + *

This method receives the {@link android.support.v4.app.TaskStackBuilder} with the constructed series of + * Intents as generated by {@link #onCreateSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}. + * If any extra data should be added to these intents before launching the new task, + * the application should override this method and add that data here.

+ * + * @param builder A TaskStackBuilder that has been populated with Intents by + * onCreateNavigateUpTaskStack. + */ + public void onPrepareSupportNavigateUpTaskStack(TaskStackBuilder builder) { + } + + /** + * This method is called whenever the user chooses to navigate Up within your application's + * activity hierarchy from the action bar. + * + *

If a parent was specified in the manifest for this activity or an activity-alias to it, + * default Up navigation will be handled automatically. See + * {@link #getSupportParentActivityIntent()} for how to specify the parent. If any activity + * along the parent chain requires extra Intent arguments, the Activity subclass + * should override the method {@link #onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)} + * to supply those arguments.

+ * + *

See Tasks and + * Back Stack from the developer guide and + * Navigation from the design guide + * for more information about navigating within your app.

+ * + *

See the {@link android.support.v4.app.TaskStackBuilder} class and the Activity methods + * {@link #getSupportParentActivityIntent()}, {@link #supportShouldUpRecreateTask(android.content.Intent)}, and + * {@link #supportNavigateUpTo(android.content.Intent)} for help implementing custom Up navigation.

+ * + * @return true if Up navigation completed successfully and this Activity was finished, + * false otherwise. + */ + public boolean onSupportNavigateUp() { + Intent upIntent = getSupportParentActivityIntent(); + + if (upIntent != null) { + if (supportShouldUpRecreateTask(upIntent)) { + TaskStackBuilder b = TaskStackBuilder.create(this); + onCreateSupportNavigateUpTaskStack(b); + onPrepareSupportNavigateUpTaskStack(b); + b.startActivities(); + + try { + ActivityCompat.finishAffinity(this); + } catch (IllegalStateException e) { + // This can only happen on 4.1+, when we don't have a parent or a result set. + // In that case we should just finish(). + finish(); + } + } else { + // This activity is part of the application's task, so simply + // navigate up to the hierarchical parent activity. + supportNavigateUpTo(upIntent); + } + return true; + } + return false; + } + + /** + * Obtain an {@link android.content.Intent} that will launch an explicit target activity + * specified by sourceActivity's {@link android.support.v4.app.NavUtils#PARENT_ACTIVITY} <meta-data> + * element in the application's manifest. If the device is running + * Jellybean or newer, the android:parentActivityName attribute will be preferred + * if it is present. + * + * @return a new Intent targeting the defined parent activity of sourceActivity + */ + public Intent getSupportParentActivityIntent() { + return NavUtils.getParentActivityIntent(this); + } + + /** + * Returns true if sourceActivity should recreate the task when navigating 'up' + * by using targetIntent. + * + *

If this method returns false the app can trivially call + * {@link #supportNavigateUpTo(android.content.Intent)} using the same parameters to correctly perform + * up navigation. If this method returns false, the app should synthesize a new task stack + * by using {@link android.support.v4.app.TaskStackBuilder} or another similar mechanism to perform up navigation.

+ * + * @param targetIntent An intent representing the target destination for up navigation + * @return true if navigating up should recreate a new task stack, false if the same task + * should be used for the destination + */ + public boolean supportShouldUpRecreateTask(Intent targetIntent) { + return NavUtils.shouldUpRecreateTask(this, targetIntent); + } + + /** + * Navigate from sourceActivity to the activity specified by upIntent, finishing sourceActivity + * in the process. upIntent will have the flag {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP} set + * by this method, along with any others required for proper up navigation as outlined + * in the Android Design Guide. + * + *

This method should be used when performing up navigation from within the same task + * as the destination. If up navigation should cross tasks in some cases, see + * {@link #supportShouldUpRecreateTask(android.content.Intent)}.

+ * + * @param upIntent An intent representing the target destination for up navigation + */ + public void supportNavigateUpTo(Intent upIntent) { + NavUtils.navigateUpTo(this, upIntent); + } + + @Override + public void onContentChanged() { + // Call onSupportContentChanged() for legacy reasons + onSupportContentChanged(); + } + + /** + * @deprecated Use {@link #onContentChanged()} instead. + */ + @Deprecated + public void onSupportContentChanged() { + } + + @Nullable + @Override + public ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { + return getDelegate().getDrawerToggleDelegate(); + } + + /** + * @return The {@link AppCompatDelegate} being used by this Activity. + */ + public AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, this); + } + return mDelegate; + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatCallback.java b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatCallback.java new file mode 100644 index 0000000000..dba77a2445 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatCallback.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 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 android.support.v7.app; + +import android.support.v7.view.ActionMode; + +/** + * Implemented this in order for AppCompat to be able to callback in certain situations. + *

+ * This should be provided to + * {@link AppCompatDelegate#create(android.app.Activity, AppCompatCallback)}. + */ +public interface AppCompatCallback { + + /** + * Called when a support action mode has been started. + * + * @param mode The new action mode. + */ + void onSupportActionModeStarted(ActionMode mode); + + /** + * Called when a support action mode has finished. + * + * @param mode The action mode that just finished. + */ + void onSupportActionModeFinished(ActionMode mode); + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegate.java b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegate.java new file mode 100644 index 0000000000..ecb1178484 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegate.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2014 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 android.support.v7.app; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.FragmentActivity; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.Toolbar; +import android.util.AttributeSet; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * This class represents a delegate which you can use to extend AppCompat's support to any + * {@link android.app.Activity}. + *

+ * When using an {@link AppCompatDelegate}, you should any methods exposed in it rather than the + * {@link android.app.Activity} method of the same name. This applies to: + *

    + *
  • {@link #addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
  • + *
  • {@link #setContentView(int)}
  • + *
  • {@link #setContentView(android.view.View)}
  • + *
  • {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
  • + *
  • {@link #requestWindowFeature(int)}
  • + *
  • {@link #invalidateOptionsMenu()}
  • + *
  • {@link #startSupportActionMode(android.support.v7.view.ActionMode.Callback)}
  • + *
  • {@link #setSupportActionBar(android.support.v7.widget.Toolbar)}
  • + *
  • {@link #getSupportActionBar()}
  • + *
  • {@link #getMenuInflater()}
  • + *
+ * There also some Activity lifecycle methods which should be proxied to the delegate: + *
    + *
  • {@link #onCreate(android.os.Bundle)}
  • + *
  • {@link #onPostCreate(android.os.Bundle)}
  • + *
  • {@link #onConfigurationChanged(android.content.res.Configuration)}
  • + *
  • {@link #setTitle(CharSequence)}
  • + *
  • {@link #onStop()}
  • + *
  • {@link #onDestroy()}
  • + *
+ *

+ * An {@link Activity} can only be linked with one {@link AppCompatDelegate} instance, + * so the instance returned from {@link #create(Activity, AppCompatCallback)} should be kept + * until the Activity is destroyed. + */ +public abstract class AppCompatDelegate { + + static final String TAG = "AppCompatDelegate"; + + /** + * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}. + * + * @param callback An optional callback for AppCompat specific events + */ + public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + return new AppCompatDelegateImplV11(activity, activity.getWindow(), callback); + } else { + return new AppCompatDelegateImplV7(activity, activity.getWindow(), callback); + } + } + + /** + * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code dialog}. + * + * @param callback An optional callback for AppCompat specific events + */ + public static AppCompatDelegate create(Dialog dialog, AppCompatCallback callback) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + return new AppCompatDelegateImplV11(dialog.getContext(), dialog.getWindow(), callback); + } else { + return new AppCompatDelegateImplV7(dialog.getContext(), dialog.getWindow(), callback); + } + } + + /** + * Private constructor + */ + AppCompatDelegate() {} + + /** + * Support library version of {@link Activity#getActionBar}. + * + * @return AppCompat's action bar, or null if it does not have one. + */ + public abstract ActionBar getSupportActionBar(); + + /** + * Set a {@link Toolbar} to act as the {@link ActionBar} for this delegate. + * + *

When set to a non-null value the {@link #getSupportActionBar()} ()} method will return + * an {@link ActionBar} object that can be used to control the given toolbar as if it were + * a traditional window decor action bar. The toolbar's menu will be populated with the + * Activity's options menu and the navigation button will be wired through the standard + * {@link android.R.id#home home} menu select action.

+ * + *

In order to use a Toolbar within the Activity's window content the application + * must not request the window feature + * {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.

+ * + * @param toolbar Toolbar to set as the Activity's action bar + */ + public abstract void setSupportActionBar(Toolbar toolbar); + + /** + * Return the value of this call from your {@link Activity#getMenuInflater()} + */ + public abstract MenuInflater getMenuInflater(); + + /** + * Should be called from {@link Activity#onCreate Activity.onCreate()} + */ + public abstract void onCreate(Bundle savedInstanceState); + + /** + * Should be called from {@link Activity#onPostCreate(android.os.Bundle)} + */ + public abstract void onPostCreate(Bundle savedInstanceState); + + /** + * Should be called from + * {@link Activity#onConfigurationChanged} + */ + public abstract void onConfigurationChanged(Configuration newConfig); + + /** + * Should be called from {@link Activity#onStop Activity.onStop()} + */ + public abstract void onStop(); + + /** + * Should be called from {@link Activity#onPostResume()} + */ + public abstract void onPostResume(); + + /** + * Should be called instead of {@link Activity#setContentView(android.view.View)}} + */ + public abstract void setContentView(View v); + + /** + * Should be called instead of {@link Activity#setContentView(int)}} + */ + public abstract void setContentView(int resId); + + /** + * Should be called instead of + * {@link Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}} + */ + public abstract void setContentView(View v, ViewGroup.LayoutParams lp); + + /** + * Should be called instead of + * {@link Activity#addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}} + */ + public abstract void addContentView(View v, ViewGroup.LayoutParams lp); + + /** + * Should be called from {@link Activity#onTitleChanged(CharSequence, int)}} + */ + public abstract void setTitle(CharSequence title); + + /** + * Should be called from {@link Activity#invalidateOptionsMenu()}} or + * {@link FragmentActivity#supportInvalidateOptionsMenu()}. + */ + public abstract void invalidateOptionsMenu(); + + /** + * Should be called from {@link Activity#onDestroy()} + */ + public abstract void onDestroy(); + + /** + * Returns an {@link ActionBarDrawerToggle.Delegate} which can be returned from your Activity + * if it implements {@link ActionBarDrawerToggle.DelegateProvider}. + */ + public abstract ActionBarDrawerToggle.Delegate getDrawerToggleDelegate(); + + /** + * Enable extended window features. This should be called instead of + * {@link android.app.Activity#requestWindowFeature(int)} or + * {@link android.view.Window#requestFeature getWindow().requestFeature()}. + * + * @param featureId The desired feature as defined in {@link android.view.Window}. + * @return Returns true if the requested feature is supported and now + * enabled. + */ + public abstract boolean requestWindowFeature(int featureId); + + /** + * 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 + */ + public abstract ActionMode startSupportActionMode(ActionMode.Callback callback); + + /** + * Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace + * the framework widgets with compatible tinted versions. This should be called before + * {@code super.onCreate()} as so: + *
+     * protected void onCreate(Bundle savedInstanceState) {
+     *     getDelegate().installViewFactory();
+     *     super.onCreate(savedInstanceState);
+     *     getDelegate().onCreate(savedInstanceState);
+     *
+     *     // ...
+     * }
+     * 
+ * If you are using your own {@link android.view.LayoutInflater.Factory Factory} or + * {@link android.view.LayoutInflater.Factory2 Factory2} then you can omit this call, and instead call + * {@link #createView(android.view.View, String, android.content.Context, android.util.AttributeSet)} + * from your factory to return any compatible widgets. + */ + public abstract void installViewFactory(); + + /** + * This should be called from a + * {@link android.support.v4.view.LayoutInflaterFactory LayoutInflaterFactory} in order + * to return tint-aware widgets. + *

+ * This is only needed if you are using your own + * {@link android.view.LayoutInflater LayoutInflater} factory, and have therefore not + * installed the default factory via {@link #installViewFactory()}. + */ + public abstract View createView(View parent, String name, @NonNull Context context, + @NonNull AttributeSet attrs); + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java new file mode 100644 index 0000000000..fd03461246 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2013 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 android.support.v7.app; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.SupportMenuInflater; +import android.support.v7.internal.view.WindowCallbackWrapper; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.widget.TintTypedArray; +import android.support.v7.view.ActionMode; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.Window; + +abstract class AppCompatDelegateImplBase extends AppCompatDelegate { + + final Context mContext; + final Window mWindow; + final Window.Callback mOriginalWindowCallback; + final AppCompatCallback mAppCompatCallback; + + private ActionBar mActionBar; + private MenuInflater mMenuInflater; + + // true if this activity has an action bar. + boolean mHasActionBar; + // true if this activity's action bar overlays other activity content. + boolean mOverlayActionBar; + // true if this any action modes should overlay the activity content + boolean mOverlayActionMode; + // true if this activity is floating (e.g. Dialog) + boolean mIsFloating; + // true if this activity has no title + boolean mWindowNoTitle; + + private CharSequence mTitle; + + private boolean mIsDestroyed; + + AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) { + mContext = context; + mWindow = window; + mAppCompatCallback = callback; + + mOriginalWindowCallback = mWindow.getCallback(); + if (mOriginalWindowCallback instanceof AppCompatWindowCallback) { + throw new IllegalStateException( + "AppCompat has already installed itself into the Window"); + } + // Now install the new callback + mWindow.setCallback(new AppCompatWindowCallback(mOriginalWindowCallback)); + } + + abstract ActionBar createSupportActionBar(); + + @Override + public ActionBar getSupportActionBar() { + // The Action Bar should be lazily created as hasActionBar + // could change after onCreate + if (mHasActionBar) { + if (mActionBar == null) { + mActionBar = createSupportActionBar(); + } + } + return mActionBar; + } + + final ActionBar peekSupportActionBar() { + return mActionBar; + } + + final void setSupportActionBar(ActionBar actionBar) { + mActionBar = actionBar; + } + + @Override + public MenuInflater getMenuInflater() { + if (mMenuInflater == null) { + mMenuInflater = new SupportMenuInflater(getActionBarThemedContext()); + } + return mMenuInflater; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); + + if (!a.hasValue(R.styleable.Theme_windowActionBar)) { + a.recycle(); + throw new IllegalStateException( + "You need to use a Theme.AppCompat theme (or descendant) with this activity."); + } + + if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) { + mHasActionBar = true; + } + if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) { + mOverlayActionBar = true; + } + if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) { + mOverlayActionMode = true; + } + mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false); + mWindowNoTitle = a.getBoolean(R.styleable.Theme_windowNoTitle, false); + a.recycle(); + } + + // Methods used to create and respond to options menu + abstract boolean onPanelClosed(int featureId, Menu menu); + + abstract boolean onMenuOpened(int featureId, Menu menu); + + abstract boolean dispatchKeyEvent(KeyEvent event); + + abstract boolean onKeyShortcut(int keyCode, KeyEvent event); + + @Override + public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { + return new ActionBarDrawableToggleImpl(); + } + + final Context getActionBarThemedContext() { + Context context = null; + + // If we have an action bar, let it return a themed context + ActionBar ab = getSupportActionBar(); + if (ab != null) { + context = ab.getThemedContext(); + } + + if (context == null) { + context = mContext; + } + return context; + } + + private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate { + @Override + public Drawable getThemeUpIndicator() { + final TintTypedArray a = TintTypedArray.obtainStyledAttributes( + getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator }); + final Drawable result = a.getDrawable(0); + a.recycle(); + return result; + } + + @Override + public Context getActionBarThemedContext() { + return AppCompatDelegateImplBase.this.getActionBarThemedContext(); + } + + @Override + public boolean isNavigationVisible() { + final ActionBar ab = getSupportActionBar(); + return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; + } + + @Override + public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setHomeAsUpIndicator(upDrawable); + ab.setHomeActionContentDescription(contentDescRes); + } + } + + @Override + public void setActionBarDescription(int contentDescRes) { + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setHomeActionContentDescription(contentDescRes); + } + } + } + + abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback); + + @Override + public final void onDestroy() { + mIsDestroyed = true; + } + + final boolean isDestroyed() { + return mIsDestroyed; + } + + final Window.Callback getWindowCallback() { + return mWindow.getCallback(); + } + + @Override + public final void setTitle(CharSequence title) { + mTitle = title; + onTitleChanged(title); + } + + abstract void onTitleChanged(CharSequence title); + + final CharSequence getTitle() { + // If the original window callback is an Activity, we'll use it's title + if (mOriginalWindowCallback instanceof Activity) { + return ((Activity) mOriginalWindowCallback).getTitle(); + } + // Else, we'll return the title we have recorded ourselves + return mTitle; + } + + private class AppCompatWindowCallback extends WindowCallbackWrapper { + AppCompatWindowCallback(Window.Callback callback) { + super(callback); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (AppCompatDelegateImplBase.this.dispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { + // If this is an options menu but it's not an AppCompat menu, we eat the event + // and return false + return false; + } + return super.onCreatePanelMenu(featureId, menu); + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { + // If this is an options menu but it's not an AppCompat menu, we eat the event + // and return false + return false; + } + + if (featureId == Window.FEATURE_OPTIONS_PANEL && bypassPrepareOptionsPanelIfNeeded()) { + // If this is an options menu and we need to bypass onPreparePanel, do so + if (mOriginalWindowCallback instanceof Activity) { + return ((Activity) mOriginalWindowCallback).onPrepareOptionsMenu(menu); + } else if (mOriginalWindowCallback instanceof Dialog) { + return ((Dialog) mOriginalWindowCallback).onPrepareOptionsMenu(menu); + } + return false; + } + + // Else, defer to the default handling + return super.onPreparePanel(featureId, view, menu); + } + + @Override + public boolean onMenuOpened(int featureId, Menu menu) { + if (AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu)) { + return true; + } + return super.onMenuOpened(featureId, menu); + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + if (AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event)) { + return true; + } + return super.dispatchKeyShortcutEvent(event); + } + + @Override + public void onContentChanged() { + // We purposely do not propagate this call as this is called when we install + // our sub-decor rather than the user's content + } + + @Override + public void onPanelClosed(int featureId, Menu menu) { + if (AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu)) { + return; + } + super.onPanelClosed(featureId, menu); + } + + /** + * For the options menu, we may need to call onPrepareOptionsMenu() directly, + * bypassing onPreparePanel(). This is because onPreparePanel() in certain situations + * calls menu.hasVisibleItems(), which interferes with any initial invisible items. + * + * @return true if onPrepareOptionsMenu should be called directly. + */ + private boolean bypassPrepareOptionsPanelIfNeeded() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN + && mOriginalWindowCallback instanceof Activity) { + // For Activities, we only need to bypass onPreparePanel if we're running pre-JB + return true; + } else if (mOriginalWindowCallback instanceof Dialog) { + // For Dialogs, we always need to bypass onPreparePanel + return true; + } + return false; + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplV11.java b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplV11.java new file mode 100644 index 0000000000..a480ee69cd --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplV11.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2013 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 android.support.v7.app; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.v7.internal.view.SupportActionModeWrapper; +import android.support.v7.internal.widget.NativeActionModeAwareLayout; +import android.util.AttributeSet; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; + +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +class AppCompatDelegateImplV11 extends AppCompatDelegateImplV7 + implements NativeActionModeAwareLayout.OnActionModeForChildListener { + + private NativeActionModeAwareLayout mNativeActionModeAwareLayout; + + AppCompatDelegateImplV11(Context context, Window window, AppCompatCallback callback) { + super(context, window, callback); + } + + @Override + void onSubDecorInstalled(ViewGroup subDecor) { + // NativeActionModeAwareLayout is used to notify us when a native Action Mode is started + mNativeActionModeAwareLayout = (NativeActionModeAwareLayout) + subDecor.findViewById(android.R.id.content); + + // Can be null when using FEATURE_ACTION_BAR_OVERLAY + if (mNativeActionModeAwareLayout != null) { + mNativeActionModeAwareLayout.setActionModeForChildListener(this); + } + } + + // From NativeActionModeAwareLayout.OnActionModeForChildListener + @Override + public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) { + Context context = originalView.getContext(); + + // Try and start a support action mode, wrapping the callback + final android.support.v7.view.ActionMode supportActionMode = startSupportActionMode( + new SupportActionModeWrapper.CallbackWrapper(context, callback)); + + if (supportActionMode != null) { + // If we received a support action mode, wrap and return it + return new SupportActionModeWrapper(mContext, supportActionMode); + } + return null; + } + + View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) { + // First let super have a try, this allows FragmentActivity to inflate any support + // fragments + final View view = super.callActivityOnCreateView(parent, name, context, attrs); + if (view != null) { + return view; + } + + // Now, let the Activity's LayoutInflater.Factory2 method try... + if (mOriginalWindowCallback instanceof LayoutInflater.Factory2) { + return ((LayoutInflater.Factory2) mOriginalWindowCallback) + .onCreateView(parent, name, context, attrs); + } + + return null; + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java new file mode 100644 index 0000000000..eac79f9aee --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java @@ -0,0 +1,1807 @@ +/* + * Copyright (C) 2013 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 android.support.v7.app; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.media.AudioManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.v4.app.NavUtils; +import android.support.v4.view.LayoutInflaterCompat; +import android.support.v4.view.LayoutInflaterFactory; +import android.support.v4.view.OnApplyWindowInsetsListener; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewConfigurationCompat; +import android.support.v4.view.WindowInsetsCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.app.TintViewInflater; +import android.support.v7.internal.app.ToolbarActionBar; +import android.support.v7.internal.app.WindowDecorActionBar; +import android.support.v7.internal.view.ContextThemeWrapper; +import android.support.v7.internal.view.StandaloneActionMode; +import android.support.v7.internal.view.renamemenu.ListMenuPresenter; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.support.v7.internal.view.renamemenu.MenuView; +import android.support.v7.internal.widget.ActionBarContextView; +import android.support.v7.internal.widget.DecorContentParent; +import android.support.v7.internal.widget.FitWindowsViewGroup; +import android.support.v7.internal.widget.TintManager; +import android.support.v7.internal.widget.ViewStubCompat; +import android.support.v7.internal.widget.ViewUtils; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.util.AndroidRuntimeException; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.widget.FrameLayout; +import android.widget.PopupWindow; +import android.widget.TextView; +import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR; +import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR_OVERLAY; +import static android.support.v4.view.WindowCompat.FEATURE_ACTION_MODE_OVERLAY; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.Window.FEATURE_OPTIONS_PANEL; + +class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase + implements MenuBuilder.Callback, LayoutInflaterFactory { + + private DecorContentParent mDecorContentParent; + private ActionMenuPresenterCallback mActionMenuPresenterCallback; + private PanelMenuPresenterCallback mPanelMenuPresenterCallback; + + ActionMode mActionMode; + ActionBarContextView mActionModeView; + PopupWindow mActionModePopup; + Runnable mShowActionModePopup; + + // true if we have installed a window sub-decor layout. + private boolean mSubDecorInstalled; + private ViewGroup mWindowDecor; + private ViewGroup mSubDecor; + + private TextView mTitleView; + private View mStatusGuard; + + // Used to keep track of Progress Bar Window features + private boolean mFeatureProgress, mFeatureIndeterminateProgress; + + // Used for emulating PanelFeatureState + private boolean mClosingActionMenu; + private PanelFeatureState[] mPanels; + private PanelFeatureState mPreparedPanel; + + private boolean mInvalidatePanelMenuPosted; + private int mInvalidatePanelMenuFeatures; + private final Runnable mInvalidatePanelMenuRunnable = new Runnable() { + @Override + public void run() { + if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) { + doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL); + } + if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_ACTION_BAR) != 0) { + doInvalidatePanelMenu(FEATURE_ACTION_BAR); + } + mInvalidatePanelMenuPosted = false; + mInvalidatePanelMenuFeatures = 0; + } + }; + + private boolean mEnableDefaultActionBarUp; + + private Rect mTempRect1; + private Rect mTempRect2; + + private TintViewInflater mTintViewInflater; + + AppCompatDelegateImplV7(Context context, Window window, AppCompatCallback callback) { + super(context, window, callback); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mWindowDecor = (ViewGroup) mWindow.getDecorView(); + + if (mOriginalWindowCallback instanceof Activity) { + if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) { + // Peek at the Action Bar and update it if it already exists + ActionBar ab = peekSupportActionBar(); + if (ab == null) { + mEnableDefaultActionBarUp = true; + } else { + ab.setDefaultDisplayHomeAsUpEnabled(true); + } + } + } + } + + @Override + public void onPostCreate(Bundle savedInstanceState) { + // Make sure that the sub decor is installed + ensureSubDecor(); + } + + @Override + public ActionBar createSupportActionBar() { + ensureSubDecor(); + ActionBar ab = null; + if (mOriginalWindowCallback instanceof Activity) { + ab = new WindowDecorActionBar((Activity) mOriginalWindowCallback, mOverlayActionBar); + } else if (mOriginalWindowCallback instanceof Dialog) { + ab = new WindowDecorActionBar((Dialog) mOriginalWindowCallback); + } + if (ab != null) { + ab.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); + } + return ab; + } + + @Override + public void setSupportActionBar(Toolbar toolbar) { + if (!(mOriginalWindowCallback instanceof Activity)) { + // Only Activities support custom Action Bars + return; + } + + final ActionBar ab = getSupportActionBar(); + if (ab instanceof WindowDecorActionBar) { + throw new IllegalStateException("This Activity already has an action bar supplied " + + "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " + + "windowActionBar to false in your theme to use a Toolbar instead."); + } + + ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mContext).getTitle(), + mWindow); + setSupportActionBar(tbab); + mWindow.setCallback(tbab.getWrappedWindowCallback()); + tbab.invalidateOptionsMenu(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // If this is called before sub-decor is installed, ActionBar will not + // be properly initialized. + if (mHasActionBar && mSubDecorInstalled) { + // Note: The action bar will need to access + // view changes from superclass. + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.onConfigurationChanged(newConfig); + } + } + } + + @Override + public void onStop() { + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setShowHideAnimationEnabled(false); + } + } + + @Override + public void onPostResume() { + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setShowHideAnimationEnabled(true); + } + } + + @Override + public void setContentView(View v) { + ensureSubDecor(); + ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); + contentParent.removeAllViews(); + contentParent.addView(v); + mOriginalWindowCallback.onContentChanged(); + } + + @Override + public void setContentView(int resId) { + ensureSubDecor(); + ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); + contentParent.removeAllViews(); + LayoutInflater.from(mContext).inflate(resId, contentParent); + mOriginalWindowCallback.onContentChanged(); + } + + @Override + public void setContentView(View v, ViewGroup.LayoutParams lp) { + ensureSubDecor(); + ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); + contentParent.removeAllViews(); + contentParent.addView(v, lp); + mOriginalWindowCallback.onContentChanged(); + } + + @Override + public void addContentView(View v, ViewGroup.LayoutParams lp) { + ensureSubDecor(); + ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); + contentParent.addView(v, lp); + mOriginalWindowCallback.onContentChanged(); + } + + private void ensureSubDecor() { + if (!mSubDecorInstalled) { + final LayoutInflater inflater = LayoutInflater.from(mContext); + + if (!mWindowNoTitle) { + if (mIsFloating) { + // If we're floating, inflate the dialog title decor + mSubDecor = (ViewGroup) inflater.inflate( + R.layout.abc_dialog_title_material, null); + } else if (mHasActionBar) { + /** + * This needs some explanation. As we can not use the android:theme attribute + * pre-L, we emulate it by manually creating a LayoutInflater using a + * ContextThemeWrapper pointing to actionBarTheme. + */ + TypedValue outValue = new TypedValue(); + mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true); + + Context themedContext; + if (outValue.resourceId != 0) { + themedContext = new ContextThemeWrapper(mContext, outValue.resourceId); + } else { + themedContext = mContext; + } + + // Now inflate the view using the themed context and set it as the content view + mSubDecor = (ViewGroup) LayoutInflater.from(themedContext) + .inflate(R.layout.abc_screen_toolbar, null); + + mDecorContentParent = (DecorContentParent) mSubDecor + .findViewById(R.id.decor_content_parent); + mDecorContentParent.setWindowCallback(getWindowCallback()); + + /** + * Propagate features to DecorContentParent + */ + if (mOverlayActionBar) { + mDecorContentParent.initFeature(FEATURE_ACTION_BAR_OVERLAY); + } + if (mFeatureProgress) { + mDecorContentParent.initFeature(Window.FEATURE_PROGRESS); + } + if (mFeatureIndeterminateProgress) { + mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + } + } + } else { + if (mOverlayActionMode) { + mSubDecor = (ViewGroup) inflater.inflate( + R.layout.abc_screen_simple_overlay_action_mode, null); + } else { + mSubDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null); + } + + if (Build.VERSION.SDK_INT >= 21) { + // If we're running on L or above, we can rely on ViewCompat's + // setOnApplyWindowInsetsListener + ViewCompat.setOnApplyWindowInsetsListener(mSubDecor, + new OnApplyWindowInsetsListener() { + @Override + public WindowInsetsCompat onApplyWindowInsets(View v, + WindowInsetsCompat insets) { + final int top = insets.getSystemWindowInsetTop(); + final int newTop = updateStatusGuard(top); + + if (top != newTop) { + insets = insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + newTop, + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); + } + + // Now apply the insets on our view + return ViewCompat.onApplyWindowInsets(v, insets); + } + }); + } else { + // Else, we need to use our own FitWindowsViewGroup handling + ((FitWindowsViewGroup) mSubDecor).setOnFitSystemWindowsListener( + new FitWindowsViewGroup.OnFitSystemWindowsListener() { + @Override + public void onFitSystemWindows(Rect insets) { + insets.top = updateStatusGuard(insets.top); + } + }); + } + } + + if (mSubDecor == null) { + throw new IllegalArgumentException( + "AppCompat does not support the current theme features"); + } + + if (mDecorContentParent == null) { + mTitleView = (TextView) mSubDecor.findViewById(R.id.title); + } + + // Make the decor optionally fit system windows, like the window's decor + ViewUtils.makeOptionalFitsSystemWindows(mSubDecor); + + final ViewGroup decorContent = (ViewGroup) mWindow.findViewById(android.R.id.content); + final ViewGroup abcContent = (ViewGroup) mSubDecor.findViewById( + R.id.action_bar_activity_content); + + // There might be Views already added to the Window's content view so we need to + // migrate them to our content view + while (decorContent.getChildCount() > 0) { + final View child = decorContent.getChildAt(0); + decorContent.removeViewAt(0); + abcContent.addView(child); + } + + // Now set the Window's content view with the decor + mWindow.setContentView(mSubDecor); + + // Change our content FrameLayout to use the android.R.id.content id. + // Useful for fragments. + decorContent.setId(View.NO_ID); + abcContent.setId(android.R.id.content); + + // The decorContent may have a foreground drawable set (windowContentOverlay). + // Remove this as we handle it ourselves + if (decorContent instanceof FrameLayout) { + ((FrameLayout) decorContent).setForeground(null); + } + + // If a title was set before we installed the decor, propogate it now + CharSequence title = getTitle(); + if (!TextUtils.isEmpty(title)) { + onTitleChanged(title); + } + + applyFixedSizeWindow(); + + onSubDecorInstalled(mSubDecor); + + mSubDecorInstalled = true; + + // Invalidate if the panel menu hasn't been created before this. + // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu + // being called in the middle of onCreate or similar. + // A pending invalidation will typically be resolved before the posted message + // would run normally in order to satisfy instance state restoration. + PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); + if (!isDestroyed() && (st == null || st.menu == null)) { + invalidatePanelMenu(FEATURE_ACTION_BAR); + } + } + } + + void onSubDecorInstalled(ViewGroup subDecor) {} + + private void applyFixedSizeWindow() { + TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); + + TypedValue mFixedWidthMajor = null; + TypedValue mFixedWidthMinor = null; + TypedValue mFixedHeightMajor = null; + TypedValue mFixedHeightMinor = null; + + if (a.hasValue(R.styleable.Theme_windowFixedWidthMajor)) { + if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue(); + a.getValue(R.styleable.Theme_windowFixedWidthMajor, mFixedWidthMajor); + } + if (a.hasValue(R.styleable.Theme_windowFixedWidthMinor)) { + if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue(); + a.getValue(R.styleable.Theme_windowFixedWidthMinor, mFixedWidthMinor); + } + if (a.hasValue(R.styleable.Theme_windowFixedHeightMajor)) { + if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue(); + a.getValue(R.styleable.Theme_windowFixedHeightMajor, mFixedHeightMajor); + } + if (a.hasValue(R.styleable.Theme_windowFixedHeightMinor)) { + if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue(); + a.getValue(R.styleable.Theme_windowFixedHeightMinor, mFixedHeightMinor); + } + + final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); + final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; + int w = ViewGroup.LayoutParams.MATCH_PARENT; + int h = ViewGroup.LayoutParams.MATCH_PARENT; + + final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor; + if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { + if (tvw.type == TypedValue.TYPE_DIMENSION) { + w = (int) tvw.getDimension(metrics); + } else if (tvw.type == TypedValue.TYPE_FRACTION) { + w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); + } + } + + final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor; + if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { + if (tvh.type == TypedValue.TYPE_DIMENSION) { + h = (int) tvh.getDimension(metrics); + } else if (tvh.type == TypedValue.TYPE_FRACTION) { + h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels); + } + } + + if (w != ViewGroup.LayoutParams.MATCH_PARENT || h != ViewGroup.LayoutParams.MATCH_PARENT) { + mWindow.setLayout(w, h); + } + + a.recycle(); + } + + @Override + public boolean requestWindowFeature(int featureId) { + switch (featureId) { + case FEATURE_ACTION_BAR: + throwFeatureRequestIfSubDecorInstalled(); + mHasActionBar = true; + return true; + case FEATURE_ACTION_BAR_OVERLAY: + throwFeatureRequestIfSubDecorInstalled(); + mOverlayActionBar = true; + return true; + case FEATURE_ACTION_MODE_OVERLAY: + throwFeatureRequestIfSubDecorInstalled(); + mOverlayActionMode = true; + return true; + case Window.FEATURE_PROGRESS: + throwFeatureRequestIfSubDecorInstalled(); + mFeatureProgress = true; + return true; + case Window.FEATURE_INDETERMINATE_PROGRESS: + throwFeatureRequestIfSubDecorInstalled(); + mFeatureIndeterminateProgress = true; + return true; + } + + return mWindow.requestFeature(featureId); + } + + @Override + void onTitleChanged(CharSequence title) { + if (mDecorContentParent != null) { + mDecorContentParent.setWindowTitle(title); + } else if (getSupportActionBar() != null) { + getSupportActionBar().setWindowTitle(title); + } else if (mTitleView != null) { + mTitleView.setText(title); + } + } + + @Override + boolean onPanelClosed(final int featureId, Menu menu) { + if (featureId == FEATURE_ACTION_BAR) { + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.dispatchMenuVisibilityChanged(false); + } + return true; + } else if (featureId == FEATURE_OPTIONS_PANEL) { + // Make sure that the options panel is closed. This is mainly used when we're using a + // ToolbarActionBar + PanelFeatureState st = getPanelState(featureId, true); + if (st.isOpen) { + closePanel(st, false); + } + } + return false; + } + + @Override + boolean onMenuOpened(final int featureId, Menu menu) { + if (featureId == FEATURE_ACTION_BAR) { + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.dispatchMenuVisibilityChanged(true); + } + return true; + } + return false; + } + + @Override + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + final Window.Callback cb = getWindowCallback(); + if (cb != null && !isDestroyed()) { + final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); + if (panel != null) { + return cb.onMenuItemSelected(panel.featureId, item); + } + } + return false; + } + + @Override + public void onMenuModeChange(MenuBuilder menu) { + reopenMenu(menu, true); + } + + @Override + public ActionMode startSupportActionMode(ActionMode.Callback callback) { + if (callback == null) { + throw new IllegalArgumentException("ActionMode callback can not be null."); + } + + if (mActionMode != null) { + mActionMode.finish(); + } + + final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); + + ActionBar ab = getSupportActionBar(); + if (ab != null) { + mActionMode = ab.startActionMode(wrappedCallback); + if (mActionMode != null && mAppCompatCallback != null) { + mAppCompatCallback.onSupportActionModeStarted(mActionMode); + } + } + + if (mActionMode == null) { + // If the action bar didn't provide an action mode, start the emulated window one + mActionMode = startSupportActionModeFromWindow(wrappedCallback); + } + + return mActionMode; + } + + @Override + public void invalidateOptionsMenu() { + final ActionBar ab = getSupportActionBar(); + if (ab != null && ab.invalidateOptionsMenu()) return; + + invalidatePanelMenu(FEATURE_OPTIONS_PANEL); + } + + @Override + ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback) { + if (mActionMode != null) { + mActionMode.finish(); + } + + final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); + final Context context = getActionBarThemedContext(); + + if (mActionModeView == null) { + if (mIsFloating) { + mActionModeView = new ActionBarContextView(context); + mActionModePopup = new PopupWindow(context, null, + R.attr.actionModePopupWindowStyle); + mActionModePopup.setContentView(mActionModeView); + mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); + + TypedValue heightValue = new TypedValue(); + mContext.getTheme().resolveAttribute(R.attr.actionBarSize, heightValue, true); + final int height = TypedValue.complexToDimensionPixelSize(heightValue.data, + mContext.getResources().getDisplayMetrics()); + mActionModeView.setContentHeight(height); + mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mShowActionModePopup = new Runnable() { + public void run() { + mActionModePopup.showAtLocation( + mActionModeView, + Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); + } + }; + } else { + ViewStubCompat stub = (ViewStubCompat) mSubDecor + .findViewById(R.id.action_mode_bar_stub); + if (stub != null) { + // Set the layout inflater so that it is inflated with the action bar's context + stub.setLayoutInflater(LayoutInflater.from(context)); + mActionModeView = (ActionBarContextView) stub.inflate(); + } + } + } + + if (mActionModeView != null) { + mActionModeView.killMode(); + ActionMode mode = new StandaloneActionMode(context, mActionModeView, wrappedCallback, + mActionModePopup == null); + if (callback.onCreateActionMode(mode, mode.getMenu())) { + mode.invalidate(); + mActionModeView.initForMode(mode); + mActionModeView.setVisibility(View.VISIBLE); + mActionMode = mode; + if (mActionModePopup != null) { + mWindow.getDecorView().post(mShowActionModePopup); + } + mActionModeView.sendAccessibilityEvent( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + + if (mActionModeView.getParent() != null) { + ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); + } + } else { + mActionMode = null; + } + } + if (mActionMode != null && mAppCompatCallback != null) { + mAppCompatCallback.onSupportActionModeStarted(mActionMode); + } + return mActionMode; + } + + boolean onBackPressed() { + // Back cancels action modes first. + if (mActionMode != null) { + mActionMode.finish(); + return true; + } + + // Next collapse any expanded action views. + ActionBar ab = getSupportActionBar(); + if (ab != null && ab.collapseActionView()) { + return true; + } + + // Let the call through... + return false; + } + + @Override + boolean onKeyShortcut(int keyCode, KeyEvent ev) { + // Let the Action Bar have a chance at handling the shortcut + ActionBar ab = getSupportActionBar(); + if (ab != null && ab.onKeyShortcut(keyCode, ev)) { + return true; + } + + // If the panel is already prepared, then perform the shortcut using it. + boolean handled; + if (mPreparedPanel != null) { + handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, + Menu.FLAG_PERFORM_NO_CLOSE); + if (handled) { + if (mPreparedPanel != null) { + mPreparedPanel.isHandled = true; + } + return true; + } + } + + // If the panel is not prepared, then we may be trying to handle a shortcut key + // combination such as Control+C. Temporarily prepare the panel then mark it + // unprepared again when finished to ensure that the panel will again be prepared + // the next time it is shown for real. + if (mPreparedPanel == null) { + PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); + preparePanel(st, ev); + handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); + st.isPrepared = false; + if (handled) { + return true; + } + } + return false; + } + + @Override + boolean dispatchKeyEvent(KeyEvent event) { + final int keyCode = event.getKeyCode(); + final int action = event.getAction(); + final boolean isDown = action == KeyEvent.ACTION_DOWN; + + return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event); + } + + boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_MENU: + onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event); + return true; + case KeyEvent.KEYCODE_BACK: + PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); + if (st != null && st.isOpen) { + closePanel(st, true); + return true; + } + if (onBackPressed()) { + return true; + } + break; + } + return false; + } + + boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_MENU: + onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event); + return true; + } + + // On API v7-10 we need to manually call onKeyShortcut() as this is not called + // from the Activity + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + return onKeyShortcut(keyCode, event); + } + return false; + } + + @Override + public View createView(View parent, final String name, @NonNull Context context, + @NonNull AttributeSet attrs) { + final boolean isPre21 = Build.VERSION.SDK_INT < 21; + + if (mTintViewInflater == null) { + mTintViewInflater = new TintViewInflater(mContext); + } + + // We only want the View to inherit it's context from the parent if it is from the + // apps content, and not part of our sub-decor + final boolean inheritContext = isPre21 && mSubDecorInstalled && parent != null + && parent.getId() != android.R.id.content; + + return mTintViewInflater.createView(parent, name, context, attrs, + inheritContext, isPre21); + } + + @Override + public void installViewFactory() { + LayoutInflater layoutInflater = LayoutInflater.from(mContext); + if (layoutInflater.getFactory() == null) { + LayoutInflaterCompat.setFactory(layoutInflater, this); + } else { + Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + + " so we can not install AppCompat's"); + } + } + + /** + * From {@link android.support.v4.view.LayoutInflaterFactory} + */ + @Override + public final View onCreateView(View parent, String name, + Context context, AttributeSet attrs) { + // First let the Activity's Factory try and inflate the view + final View view = callActivityOnCreateView(parent, name, context, attrs); + if (view != null) { + return view; + } + + // If the Factory didn't handle it, let our createView() method try + return createView(parent, name, context, attrs); + } + + View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) { + // Let the Activity's LayoutInflater.Factory try and handle it + if (mOriginalWindowCallback instanceof LayoutInflater.Factory) { + final View result = ((LayoutInflater.Factory) mOriginalWindowCallback) + .onCreateView(name, context, attrs); + if (result != null) { + return result; + } + } + return null; + } + + private void openPanel(final PanelFeatureState st, KeyEvent event) { + // Already open, return + if (st.isOpen || isDestroyed()) { + return; + } + + // Don't open an options panel for honeycomb apps on xlarge devices. + // (The app should be using an action bar for menu items.) + if (st.featureId == FEATURE_OPTIONS_PANEL) { + Context context = mContext; + Configuration config = context.getResources().getConfiguration(); + boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == + Configuration.SCREENLAYOUT_SIZE_XLARGE; + boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >= + android.os.Build.VERSION_CODES.HONEYCOMB; + + if (isXLarge && isHoneycombApp) { + return; + } + } + + Window.Callback cb = getWindowCallback(); + if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { + // Callback doesn't want the menu to open, reset any state + closePanel(st, true); + return; + } + + final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + if (wm == null) { + return; + } + + // Prepare panel (should have been done before, but just in case) + if (!preparePanel(st, event)) { + return; + } + + int width = WRAP_CONTENT; + if (st.decorView == null || st.refreshDecorView) { + if (st.decorView == null) { + // Initialize the panel decor, this will populate st.decorView + if (!initializePanelDecor(st) || (st.decorView == null)) + return; + } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { + // Decor needs refreshing, so remove its views + st.decorView.removeAllViews(); + } + + // This will populate st.shownPanelView + if (!initializePanelContent(st) || !st.hasPanelItems()) { + return; + } + + ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); + if (lp == null) { + lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); + } + + int backgroundResId = st.background; + st.decorView.setBackgroundResource(backgroundResId); + + ViewParent shownPanelParent = st.shownPanelView.getParent(); + if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) { + ((ViewGroup) shownPanelParent).removeView(st.shownPanelView); + } + st.decorView.addView(st.shownPanelView, lp); + + /* + * Give focus to the view, if it or one of its children does not + * already have it. + */ + if (!st.shownPanelView.hasFocus()) { + st.shownPanelView.requestFocus(); + } + } else if (st.createdPanelView != null) { + // If we already had a panel view, carry width=MATCH_PARENT through + // as we did above when it was created. + ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); + if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { + width = MATCH_PARENT; + } + } + + st.isHandled = false; + + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + width, WRAP_CONTENT, + st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSLUCENT); + + lp.gravity = st.gravity; + lp.windowAnimations = st.windowAnimations; + + wm.addView(st.decorView, lp); + st.isOpen = true; + } + + private boolean initializePanelDecor(PanelFeatureState st) { + st.setStyle(getActionBarThemedContext()); + st.decorView = new ListMenuDecorView(st.listPresenterContext); + st.gravity = Gravity.CENTER | Gravity.BOTTOM; + return true; + } + + private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { + if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && + (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext)) || + mDecorContentParent.isOverflowMenuShowPending())) { + + final Window.Callback cb = getWindowCallback(); + + if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { + if (cb != null && !isDestroyed()) { + // If we have a menu invalidation pending, do it now. + if (mInvalidatePanelMenuPosted && + (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { + mWindowDecor.removeCallbacks(mInvalidatePanelMenuRunnable); + mInvalidatePanelMenuRunnable.run(); + } + + final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); + + // If we don't have a menu or we're waiting for a full content refresh, + // forget it. This is a lingering event that no longer matters. + if (st.menu != null && !st.refreshMenuContent && + cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { + cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu); + mDecorContentParent.showOverflowMenu(); + } + } + } else { + mDecorContentParent.hideOverflowMenu(); + if (!isDestroyed()) { + final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); + cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu); + } + } + return; + } + + PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); + + st.refreshDecorView = true; + closePanel(st, false); + + openPanel(st, null); + } + + private boolean initializePanelMenu(final PanelFeatureState st) { + Context context = mContext; + + // If we have an action bar, initialize the menu with the right theme. + if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) && + mDecorContentParent != null) { + final TypedValue outValue = new TypedValue(); + final Resources.Theme baseTheme = context.getTheme(); + baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); + + Resources.Theme widgetTheme = null; + if (outValue.resourceId != 0) { + widgetTheme = context.getResources().newTheme(); + widgetTheme.setTo(baseTheme); + widgetTheme.applyStyle(outValue.resourceId, true); + widgetTheme.resolveAttribute( + R.attr.actionBarWidgetTheme, outValue, true); + } else { + baseTheme.resolveAttribute( + R.attr.actionBarWidgetTheme, outValue, true); + } + + if (outValue.resourceId != 0) { + if (widgetTheme == null) { + widgetTheme = context.getResources().newTheme(); + widgetTheme.setTo(baseTheme); + } + widgetTheme.applyStyle(outValue.resourceId, true); + } + + if (widgetTheme != null) { + context = new ContextThemeWrapper(context, 0); + context.getTheme().setTo(widgetTheme); + } + } + + final MenuBuilder menu = new MenuBuilder(context); + menu.setCallback(this); + st.setMenu(menu); + + return true; + } + + private boolean initializePanelContent(PanelFeatureState st) { + if (st.createdPanelView != null) { + st.shownPanelView = st.createdPanelView; + return true; + } + + if (st.menu == null) { + return false; + } + + if (mPanelMenuPresenterCallback == null) { + mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); + } + + MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback); + + st.shownPanelView = (View) menuView; + + return st.shownPanelView != null; + } + + private boolean preparePanel(PanelFeatureState st, KeyEvent event) { + if (isDestroyed()) { + return false; + } + + // Already prepared (isPrepared will be reset to false later) + if (st.isPrepared) { + return true; + } + + if ((mPreparedPanel != null) && (mPreparedPanel != st)) { + // Another Panel is prepared and possibly open, so close it + closePanel(mPreparedPanel, false); + } + + final Window.Callback cb = getWindowCallback(); + + if (cb != null) { + st.createdPanelView = cb.onCreatePanelView(st.featureId); + } + + final boolean isActionBarMenu = + (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR); + + if (isActionBarMenu && mDecorContentParent != null) { + // Enforce ordering guarantees around events so that the action bar never + // dispatches menu-related events before the panel is prepared. + mDecorContentParent.setMenuPrepared(); + } + + if (st.createdPanelView == null) { + // Init the panel state's menu--return false if init failed + if (st.menu == null || st.refreshMenuContent) { + if (st.menu == null) { + if (!initializePanelMenu(st) || (st.menu == null)) { + return false; + } + } + + if (isActionBarMenu && mDecorContentParent != null) { + if (mActionMenuPresenterCallback == null) { + mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); + } + mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback); + } + + // Creating the panel menu will involve a lot of manipulation; + // don't dispatch change events to presenters until we're done. + st.menu.stopDispatchingItemsChanged(); + if (!cb.onCreatePanelMenu(st.featureId, st.menu)) { + // Ditch the menu created above + st.setMenu(null); + + if (isActionBarMenu && mDecorContentParent != null) { + // Don't show it in the action bar either + mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); + } + + return false; + } + + st.refreshMenuContent = false; + } + + // Preparing the panel menu can involve a lot of manipulation; + // don't dispatch change events to presenters until we're done. + st.menu.stopDispatchingItemsChanged(); + + // Restore action view state before we prepare. This gives apps + // an opportunity to override frozen/restored state in onPrepare. + if (st.frozenActionViewState != null) { + st.menu.restoreActionViewStates(st.frozenActionViewState); + st.frozenActionViewState = null; + } + + // Callback and return if the callback does not want to show the menu + if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { + if (isActionBarMenu && mDecorContentParent != null) { + // The app didn't want to show the menu for now but it still exists. + // Clear it out of the action bar. + mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); + } + st.menu.startDispatchingItemsChanged(); + return false; + } + + // Set the proper keymap + KeyCharacterMap kmap = KeyCharacterMap.load( + event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); + st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; + st.menu.setQwertyMode(st.qwertyMode); + st.menu.startDispatchingItemsChanged(); + } + + // Set other state + st.isPrepared = true; + st.isHandled = false; + mPreparedPanel = st; + + return true; + } + + private void checkCloseActionMenu(MenuBuilder menu) { + if (mClosingActionMenu) { + return; + } + + mClosingActionMenu = true; + mDecorContentParent.dismissPopups(); + Window.Callback cb = getWindowCallback(); + if (cb != null && !isDestroyed()) { + cb.onPanelClosed(FEATURE_ACTION_BAR, menu); + } + mClosingActionMenu = false; + } + + private void closePanel(int featureId) { + closePanel(getPanelState(featureId, true), true); + } + + private void closePanel(PanelFeatureState st, boolean doCallback) { + if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && + mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) { + checkCloseActionMenu(st.menu); + return; + } + + final boolean wasOpen = st.isOpen; + + final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + if (wm != null && wasOpen && st.decorView != null) { + wm.removeView(st.decorView); + } + + st.isPrepared = false; + st.isHandled = false; + st.isOpen = false; + + if (wasOpen && doCallback) { + // If the panel was open and we should callback, do so. This should be done after + // isOpen is updated to ensure that we do not get into an infinite recursion + callOnPanelClosed(st.featureId, st, null); + } + + // This view is no longer shown, so null it out + st.shownPanelView = null; + + // Next time the menu opens, it should not be in expanded mode, so + // force a refresh of the decor + st.refreshDecorView = true; + + if (mPreparedPanel == st) { + mPreparedPanel = null; + } + } + + private boolean onKeyDownPanel(int featureId, KeyEvent event) { + if (event.getRepeatCount() == 0) { + PanelFeatureState st = getPanelState(featureId, true); + if (!st.isOpen) { + return preparePanel(st, event); + } + } + + return false; + } + + private void onKeyUpPanel(int featureId, KeyEvent event) { + if (mActionMode != null) { + return; + } + + boolean playSoundEffect = false; + final PanelFeatureState st = getPanelState(featureId, true); + if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && + mDecorContentParent.canShowOverflowMenu() && + !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext))) { + if (!mDecorContentParent.isOverflowMenuShowing()) { + if (!isDestroyed() && preparePanel(st, event)) { + playSoundEffect = mDecorContentParent.showOverflowMenu(); + } + } else { + playSoundEffect = mDecorContentParent.hideOverflowMenu(); + } + } else { + if (st.isOpen || st.isHandled) { + + // Play the sound effect if the user closed an open menu (and not if + // they just released a menu shortcut) + playSoundEffect = st.isOpen; + + // Close menu + closePanel(st, true); + + } else if (st.isPrepared) { + boolean show = true; + if (st.refreshMenuContent) { + // Something may have invalidated the menu since we prepared it. + // Re-prepare it to refresh. + st.isPrepared = false; + show = preparePanel(st, event); + } + + if (show) { + // Show menu + openPanel(st, event); + + playSoundEffect = true; + } + } + } + + if (playSoundEffect) { + AudioManager audioManager = (AudioManager) mContext.getSystemService( + Context.AUDIO_SERVICE); + if (audioManager != null) { + audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); + } else { + Log.w(TAG, "Couldn't get audio manager"); + } + } + } + + private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { + // Try to get a menu + if (menu == null) { + // Need a panel to grab the menu, so try to get that + if (panel == null) { + if ((featureId >= 0) && (featureId < mPanels.length)) { + panel = mPanels[featureId]; + } + } + + if (panel != null) { + // menu still may be null, which is okay--we tried our best + menu = panel.menu; + } + } + + // If the panel is not open, do not callback + if ((panel != null) && (!panel.isOpen)) + return; + + Window.Callback cb = getWindowCallback(); + if (cb != null) { + cb.onPanelClosed(featureId, menu); + } + } + + private PanelFeatureState findMenuPanel(Menu menu) { + final PanelFeatureState[] panels = mPanels; + final int N = panels != null ? panels.length : 0; + for (int i = 0; i < N; i++) { + final PanelFeatureState panel = panels[i]; + if (panel != null && panel.menu == menu) { + return panel; + } + } + return null; + } + + private PanelFeatureState getPanelState(int featureId, boolean required) { + PanelFeatureState[] ar; + if ((ar = mPanels) == null || ar.length <= featureId) { + PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; + if (ar != null) { + System.arraycopy(ar, 0, nar, 0, ar.length); + } + mPanels = ar = nar; + } + + PanelFeatureState st = ar[featureId]; + if (st == null) { + ar[featureId] = st = new PanelFeatureState(featureId); + } + return st; + } + + private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, + int flags) { + if (event.isSystem()) { + return false; + } + + boolean handled = false; + + // Only try to perform menu shortcuts if preparePanel returned true (possible false + // return value from application not wanting to show the menu). + if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { + // The menu is prepared now, perform the shortcut on it + handled = st.menu.performShortcut(keyCode, event, flags); + } + + if (handled) { + // Only close down the menu if we don't have an action bar keeping it open. + if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { + closePanel(st, true); + } + } + + return handled; + } + + private void invalidatePanelMenu(int featureId) { + mInvalidatePanelMenuFeatures |= 1 << featureId; + + if (!mInvalidatePanelMenuPosted && mWindowDecor != null) { + ViewCompat.postOnAnimation(mWindowDecor, mInvalidatePanelMenuRunnable); + mInvalidatePanelMenuPosted = true; + } + } + + private void doInvalidatePanelMenu(int featureId) { + PanelFeatureState st = getPanelState(featureId, true); + Bundle savedActionViewStates = null; + if (st.menu != null) { + savedActionViewStates = new Bundle(); + st.menu.saveActionViewStates(savedActionViewStates); + if (savedActionViewStates.size() > 0) { + st.frozenActionViewState = savedActionViewStates; + } + // This will be started again when the panel is prepared. + st.menu.stopDispatchingItemsChanged(); + st.menu.clear(); + } + st.refreshMenuContent = true; + st.refreshDecorView = true; + + // Prepare the options panel if we have an action bar + if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) + && mDecorContentParent != null) { + st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); + if (st != null) { + st.isPrepared = false; + preparePanel(st, null); + } + } + } + + /** + * Updates the status bar guard + * + * @param insetTop the current top system window inset + * @return the new top system window inset + */ + private int updateStatusGuard(int insetTop) { + boolean showStatusGuard = false; + // Show the status guard when the non-overlay contextual action bar is showing + if (mActionModeView != null) { + if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) + mActionModeView.getLayoutParams(); + boolean mlpChanged = false; + + if (mActionModeView.isShown()) { + if (mTempRect1 == null) { + mTempRect1 = new Rect(); + mTempRect2 = new Rect(); + } + final Rect insets = mTempRect1; + final Rect localInsets = mTempRect2; + insets.set(0, insetTop, 0, 0); + + ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets); + final int newMargin = localInsets.top == 0 ? insetTop : 0; + if (mlp.topMargin != newMargin) { + mlpChanged = true; + mlp.topMargin = insetTop; + + if (mStatusGuard == null) { + mStatusGuard = new View(mContext); + mStatusGuard.setBackgroundColor(mContext.getResources() + .getColor(R.color.abc_input_method_navigation_guard)); + mSubDecor.addView(mStatusGuard, -1, + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + insetTop)); + } else { + ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams(); + if (lp.height != insetTop) { + lp.height = insetTop; + mStatusGuard.setLayoutParams(lp); + } + } + } + + // The action mode's theme may differ from the app, so + // always show the status guard above it. + showStatusGuard = mStatusGuard != null; + + // We only need to consume the insets if the action + // mode is overlaid on the app content (e.g. it's + // sitting in a FrameLayout, see + // screen_simple_overlay_action_mode.xml). + if (!mOverlayActionMode && showStatusGuard) { + insetTop = 0; + } + } else { + // reset top margin + if (mlp.topMargin != 0) { + mlpChanged = true; + mlp.topMargin = 0; + } + } + if (mlpChanged) { + mActionModeView.setLayoutParams(mlp); + } + } + } + if (mStatusGuard != null) { + mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); + } + + return insetTop; + } + + private void throwFeatureRequestIfSubDecorInstalled() { + if (mSubDecorInstalled) { + throw new AndroidRuntimeException( + "Window feature must be requested before adding content"); + } + } + + ViewGroup getSubDecor() { + return mSubDecor; + } + + /** + * Clears out internal reference when the action mode is destroyed. + */ + private class ActionModeCallbackWrapper implements ActionMode.Callback { + private 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 (mActionModePopup != null) { + mWindow.getDecorView().removeCallbacks(mShowActionModePopup); + mActionModePopup.dismiss(); + } else if (mActionModeView != null) { + mActionModeView.setVisibility(View.GONE); + if (mActionModeView.getParent() != null) { + ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); + } + } + if (mActionModeView != null) { + mActionModeView.removeAllViews(); + } + if (mAppCompatCallback != null) { + mAppCompatCallback.onSupportActionModeFinished(mActionMode); + } + mActionMode = null; + } + } + + private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + final Menu parentMenu = menu.getRootMenu(); + final boolean isSubMenu = parentMenu != menu; + final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); + if (panel != null) { + if (isSubMenu) { + callOnPanelClosed(panel.featureId, panel, parentMenu); + closePanel(panel, true); + } else { + // Close the panel and only do the callback if the menu is being + // closed completely, not if opening a sub menu + closePanel(panel, allMenusAreClosing); + } + } + } + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + if (subMenu == null && mHasActionBar) { + Window.Callback cb = getWindowCallback(); + if (cb != null && !isDestroyed()) { + cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); + } + } + return true; + } + } + + private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + Window.Callback cb = getWindowCallback(); + if (cb != null) { + cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); + } + return true; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + checkCloseActionMenu(menu); + } + } + + private static final class PanelFeatureState { + + /** Feature ID for this panel. */ + int featureId; + + int background; + + int gravity; + + int x; + + int y; + + int windowAnimations; + + /** Dynamic state of the panel. */ + ViewGroup decorView; + + /** The panel that we are actually showing. */ + View shownPanelView; + + /** The panel that was returned by onCreatePanelView(). */ + View createdPanelView; + + /** Use {@link #setMenu} to set this. */ + MenuBuilder menu; + + ListMenuPresenter listMenuPresenter; + + Context listPresenterContext; + + /** + * Whether the panel has been prepared (see + * {@link #preparePanel}). + */ + boolean isPrepared; + + /** + * Whether an item's action has been performed. This happens in obvious + * scenarios (user clicks on menu item), but can also happen with + * chording menu+(shortcut key). + */ + boolean isHandled; + + boolean isOpen; + + public boolean qwertyMode; + + boolean refreshDecorView; + + boolean refreshMenuContent; + + boolean wasLastOpen; + + /** + * Contains the state of the menu when told to freeze. + */ + Bundle frozenMenuState; + + /** + * Contains the state of associated action views when told to freeze. + * These are saved across invalidations. + */ + Bundle frozenActionViewState; + + PanelFeatureState(int featureId) { + this.featureId = featureId; + + refreshDecorView = false; + } + + public boolean hasPanelItems() { + if (shownPanelView == null) return false; + if (createdPanelView != null) return true; + + return listMenuPresenter.getAdapter().getCount() > 0; + } + + /** + * Unregister and free attached MenuPresenters. They will be recreated as needed. + */ + public void clearMenuPresenters() { + if (menu != null) { + menu.removeMenuPresenter(listMenuPresenter); + } + listMenuPresenter = null; + } + + void setStyle(Context context) { + final TypedValue outValue = new TypedValue(); + final Resources.Theme widgetTheme = context.getResources().newTheme(); + widgetTheme.setTo(context.getTheme()); + + // First apply the actionBarPopupTheme + widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true); + if (outValue.resourceId != 0) { + widgetTheme.applyStyle(outValue.resourceId, true); + } + + // Now apply the panelMenuListTheme + widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true); + if (outValue.resourceId != 0) { + widgetTheme.applyStyle(outValue.resourceId, true); + } else { + widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true); + } + + context = new ContextThemeWrapper(context, 0); + context.getTheme().setTo(widgetTheme); + + listPresenterContext = context; + + TypedArray a = context.obtainStyledAttributes(R.styleable.Theme); + background = a.getResourceId( + R.styleable.Theme_panelBackground, 0); + windowAnimations = a.getResourceId( + R.styleable.Theme_android_windowAnimationStyle, 0); + a.recycle(); + } + + void setMenu(MenuBuilder menu) { + if (menu == this.menu) return; + + if (this.menu != null) { + this.menu.removeMenuPresenter(listMenuPresenter); + } + this.menu = menu; + if (menu != null) { + if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); + } + } + + MenuView getListMenuView(MenuPresenter.Callback cb) { + if (menu == null) return null; + + if (listMenuPresenter == null) { + listMenuPresenter = new ListMenuPresenter(listPresenterContext, + R.layout.abc_list_menu_item_layout); + listMenuPresenter.setCallback(cb); + menu.addMenuPresenter(listMenuPresenter); + } + + MenuView result = listMenuPresenter.getMenuView(decorView); + + return result; + } + + Parcelable onSaveInstanceState() { + SavedState savedState = new SavedState(); + savedState.featureId = featureId; + savedState.isOpen = isOpen; + + if (menu != null) { + savedState.menuState = new Bundle(); + menu.savePresenterStates(savedState.menuState); + } + + return savedState; + } + + void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState) state; + featureId = savedState.featureId; + wasLastOpen = savedState.isOpen; + frozenMenuState = savedState.menuState; + + shownPanelView = null; + decorView = null; + } + + void applyFrozenState() { + if (menu != null && frozenMenuState != null) { + menu.restorePresenterStates(frozenMenuState); + frozenMenuState = null; + } + } + + private static class SavedState implements Parcelable { + int featureId; + boolean isOpen; + Bundle menuState; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(featureId); + dest.writeInt(isOpen ? 1 : 0); + + if (isOpen) { + dest.writeBundle(menuState); + } + } + + private static SavedState readFromParcel(Parcel source) { + SavedState savedState = new SavedState(); + savedState.featureId = source.readInt(); + savedState.isOpen = source.readInt() == 1; + + if (savedState.isOpen) { + savedState.menuState = source.readBundle(); + } + + return savedState; + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return readFromParcel(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + } + + private class ListMenuDecorView extends FrameLayout { + public ListMenuDecorView(Context context) { + super(context); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return AppCompatDelegateImplV7.this.dispatchKeyEvent(event); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + if (isOutOfBounds(x, y)) { + closePanel(Window.FEATURE_OPTIONS_PANEL); + return true; + } + } + return super.onInterceptTouchEvent(event); + } + + @Override + public void setBackgroundResource(int resid) { + setBackgroundDrawable(TintManager.getDrawable(getContext(), resid)); + } + + private boolean isOutOfBounds(int x, int y) { + return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5); + } + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDialog.java b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDialog.java new file mode 100644 index 0000000000..90393425db --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/AppCompatDialog.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2015 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 android.support.v7.app; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.v7.appcompat.R; +import android.support.v7.view.ActionMode; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; + +/** + * Base class for AppCompat themed {@link android.app.Dialog}s. + */ +public class AppCompatDialog extends Dialog implements AppCompatCallback { + + private AppCompatDelegate mDelegate; + + public AppCompatDialog(Context context) { + this(context, 0); + } + + public AppCompatDialog(Context context, int theme) { + super(context, getThemeResId(context, theme)); + + // This is a bit weird, but Dialog's are typically created and setup before being shown, + // which means that we can't rely on onCreate() being called before a content view is set. + // To workaround this, we call onCreate(null) in the ctor, and then again as usual in + // onCreate(). + getDelegate().onCreate(null); + } + + protected AppCompatDialog(Context context, boolean cancelable, + OnCancelListener cancelListener) { + super(context, cancelable, cancelListener); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + super.onCreate(savedInstanceState); + getDelegate().onCreate(savedInstanceState); + } + + /** + * Support library version of {@link android.app.Dialog#getActionBar}. + * + *

Retrieve a reference to this dialog's ActionBar. + * + * @return The Dialog's ActionBar, or null if it does not have one. + */ + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void setTitle(CharSequence title) { + super.setTitle(title); + getDelegate().setTitle(title); + } + + @Override + public void setTitle(int titleId) { + super.setTitle(titleId); + getDelegate().setTitle(getContext().getString(titleId)); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + /** + * Enable extended support library window features. + *

+ * This is a convenience for calling + * {@link android.view.Window#requestFeature getWindow().requestFeature()}. + *

+ * + * @param featureId The desired feature as defined in {@link android.view.Window} or + * {@link android.support.v4.view.WindowCompat}. + * @return Returns true if the requested feature is supported and now enabled. + * + * @see android.app.Dialog#requestWindowFeature + * @see android.view.Window#requestFeature + */ + public boolean supportRequestWindowFeature(int featureId) { + return getDelegate().requestWindowFeature(featureId); + } + + /** + * @hide + */ + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + /** + * @return The {@link AppCompatDelegate} being used by this Dialog. + */ + public AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, this); + } + return mDelegate; + } + + private static int getThemeResId(Context context, int themeId) { + if (themeId == 0) { + // If the provided theme is 0, then retrieve the dialogTheme from our theme + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); + themeId = outValue.resourceId; + } + return themeId; + } + + @Override + public void onSupportActionModeStarted(ActionMode mode) { + } + + @Override + public void onSupportActionModeFinished(ActionMode mode) { + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java b/eclipse-compile/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java new file mode 100644 index 0000000000..06bb360232 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2014 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 android.support.v7.app; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.appcompat.R; + +/** + * A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate between them. + */ +abstract class DrawerArrowDrawable extends Drawable { + + private final Paint mPaint = new Paint(); + + // The angle in degress that the arrow head is inclined at. + private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45); + private final float mBarThickness; + // The length of top and bottom bars when they merge into an arrow + private final float mTopBottomArrowSize; + // The length of middle bar + private final float mBarSize; + // The length of the middle bar when arrow is shaped + private final float mMiddleArrowSize; + // The space between bars when they are parallel + private final float mBarGap; + // Whether bars should spin or not during progress + private final boolean mSpin; + // Use Path instead of canvas operations so that if color has transparency, overlapping sections + // wont look different + private final Path mPath = new Path(); + // The reported intrinsic size of the drawable. + private final int mSize; + // Whether we should mirror animation when animation is reversed. + private boolean mVerticalMirror = false; + // The interpolated version of the original progress + private float mProgress; + // the amount that overlaps w/ bar size when rotation is max + private float mMaxCutForBarSize; + // The distance of arrow's center from top when horizontal + private float mCenterOffset; + + /** + * @param context used to get the configuration for the drawable from + */ + DrawerArrowDrawable(Context context) { + final TypedArray typedArray = context.getTheme() + .obtainStyledAttributes(null, R.styleable.DrawerArrowToggle, + R.attr.drawerArrowStyle, + R.style.Base_Widget_AppCompat_DrawerArrowToggle); + mPaint.setAntiAlias(true); + mPaint.setColor(typedArray.getColor(R.styleable.DrawerArrowToggle_color, 0)); + mSize = typedArray.getDimensionPixelSize(R.styleable.DrawerArrowToggle_drawableSize, 0); + // round this because having this floating may cause bad measurements + mBarSize = Math.round(typedArray.getDimension(R.styleable.DrawerArrowToggle_barSize, 0)); + // round this because having this floating may cause bad measurements + mTopBottomArrowSize = Math.round(typedArray.getDimension( + R.styleable.DrawerArrowToggle_topBottomBarArrowSize, 0)); + mBarThickness = typedArray.getDimension(R.styleable.DrawerArrowToggle_thickness, 0); + // round this because having this floating may cause bad measurements + mBarGap = Math.round(typedArray.getDimension( + R.styleable.DrawerArrowToggle_gapBetweenBars, 0)); + mSpin = typedArray.getBoolean(R.styleable.DrawerArrowToggle_spinBars, true); + mMiddleArrowSize = typedArray + .getDimension(R.styleable.DrawerArrowToggle_middleBarArrowSize, 0); + final int remainingSpace = (int) (mSize - mBarThickness * 3 - mBarGap * 2); + mCenterOffset = (remainingSpace / 4) * 2; //making sure it is a multiple of 2. + mCenterOffset += mBarThickness * 1.5 + mBarGap; + typedArray.recycle(); + + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeJoin(Paint.Join.MITER); + mPaint.setStrokeCap(Paint.Cap.BUTT); + mPaint.setStrokeWidth(mBarThickness); + + mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE)); + } + + abstract boolean isLayoutRtl(); + + /** + * If set, canvas is flipped when progress reached to end and going back to start. + */ + protected void setVerticalMirror(boolean verticalMirror) { + mVerticalMirror = verticalMirror; + } + + @Override + public void draw(Canvas canvas) { + Rect bounds = getBounds(); + final boolean isRtl = isLayoutRtl(); + // Interpolated widths of arrow bars + final float arrowSize = lerp(mBarSize, mTopBottomArrowSize, mProgress); + final float middleBarSize = lerp(mBarSize, mMiddleArrowSize, mProgress); + // Interpolated size of middle bar + final float middleBarCut = Math.round(lerp(0, mMaxCutForBarSize, mProgress)); + // The rotation of the top and bottom bars (that make the arrow head) + final float rotation = lerp(0, ARROW_HEAD_ANGLE, mProgress); + + // The whole canvas rotates as the transition happens + final float canvasRotate = lerp(isRtl ? 0 : -180, isRtl ? 180 : 0, mProgress); + final float arrowWidth = Math.round(arrowSize * Math.cos(rotation)); + final float arrowHeight = Math.round(arrowSize * Math.sin(rotation)); + + + mPath.rewind(); + final float topBottomBarOffset = lerp(mBarGap + mBarThickness, -mMaxCutForBarSize, + mProgress); + + final float arrowEdge = -middleBarSize / 2; + // draw middle bar + mPath.moveTo(arrowEdge + middleBarCut, 0); + mPath.rLineTo(middleBarSize - middleBarCut * 2, 0); + + // bottom bar + mPath.moveTo(arrowEdge, topBottomBarOffset); + mPath.rLineTo(arrowWidth, arrowHeight); + + // top bar + mPath.moveTo(arrowEdge, -topBottomBarOffset); + mPath.rLineTo(arrowWidth, -arrowHeight); + + mPath.close(); + + canvas.save(); + // Rotate the whole canvas if spinning, if not, rotate it 180 to get + // the arrow pointing the other way for RTL. + canvas.translate(bounds.centerX(), mCenterOffset); + if (mSpin) { + canvas.rotate(canvasRotate * ((mVerticalMirror ^ isRtl) ? -1 : 1)); + } else if (isRtl) { + canvas.rotate(180); + } + canvas.drawPath(mPath, mPaint); + + canvas.restore(); + } + + @Override + public void setAlpha(int i) { + mPaint.setAlpha(i); + } + + // override + public boolean isAutoMirrored() { + // Draws rotated 180 degrees in RTL mode. + return true; + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mPaint.setColorFilter(colorFilter); + } + + @Override + public int getIntrinsicHeight() { + return mSize; + } + + @Override + public int getIntrinsicWidth() { + return mSize; + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + public float getProgress() { + return mProgress; + } + + public void setProgress(float progress) { + mProgress = progress; + invalidateSelf(); + } + + /** + * Linear interpolate between a and b with parameter t. + */ + private static float lerp(float a, float b, float t) { + return a + (b - a) * t; + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java new file mode 100644 index 0000000000..b97d07c809 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2014 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 android.support.v7.graphics.drawable; + +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.Drawable; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.view.View; + +/** + * Drawable which delegates all calls to it's wrapped {@link Drawable}. + *

+ * The wrapped {@link Drawable} must be fully released from any {@link View} + * before wrapping, otherwise internal {@link Drawable.Callback} may be dropped. + * + * @hide + */ +public class DrawableWrapper extends Drawable implements Drawable.Callback { + + private Drawable mDrawable; + + public DrawableWrapper(Drawable drawable) { + setWrappedDrawable(drawable); + } + + @Override + public void draw(Canvas canvas) { + mDrawable.draw(canvas); + } + + @Override + protected void onBoundsChange(Rect bounds) { + mDrawable.setBounds(bounds); + } + + @Override + public void setChangingConfigurations(int configs) { + mDrawable.setChangingConfigurations(configs); + } + + @Override + public int getChangingConfigurations() { + return mDrawable.getChangingConfigurations(); + } + + @Override + public void setDither(boolean dither) { + mDrawable.setDither(dither); + } + + @Override + public void setFilterBitmap(boolean filter) { + mDrawable.setFilterBitmap(filter); + } + + @Override + public void setAlpha(int alpha) { + mDrawable.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter cf) { + mDrawable.setColorFilter(cf); + } + + @Override + public boolean isStateful() { + return mDrawable.isStateful(); + } + + @Override + public boolean setState(final int[] stateSet) { + return mDrawable.setState(stateSet); + } + + @Override + public int[] getState() { + return mDrawable.getState(); + } + + public void jumpToCurrentState() { + DrawableCompat.jumpToCurrentState(mDrawable); + } + + @Override + public Drawable getCurrent() { + return mDrawable.getCurrent(); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart); + } + + @Override + public int getOpacity() { + return mDrawable.getOpacity(); + } + + @Override + public Region getTransparentRegion() { + return mDrawable.getTransparentRegion(); + } + + @Override + public int getIntrinsicWidth() { + return mDrawable.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mDrawable.getIntrinsicHeight(); + } + + @Override + public int getMinimumWidth() { + return mDrawable.getMinimumWidth(); + } + + @Override + public int getMinimumHeight() { + return mDrawable.getMinimumHeight(); + } + + @Override + public boolean getPadding(Rect padding) { + return mDrawable.getPadding(padding); + } + + /** + * {@inheritDoc} + */ + public void invalidateDrawable(Drawable who) { + invalidateSelf(); + } + + /** + * {@inheritDoc} + */ + public void scheduleDrawable(Drawable who, Runnable what, long when) { + scheduleSelf(what, when); + } + + /** + * {@inheritDoc} + */ + public void unscheduleDrawable(Drawable who, Runnable what) { + unscheduleSelf(what); + } + + @Override + protected boolean onLevelChange(int level) { + return mDrawable.setLevel(level); + } + + @Override + public void setAutoMirrored(boolean mirrored) { + DrawableCompat.setAutoMirrored(mDrawable, mirrored); + } + + @Override + public boolean isAutoMirrored() { + return DrawableCompat.isAutoMirrored(mDrawable); + } + + @Override + public void setTint(int tint) { + DrawableCompat.setTint(mDrawable, tint); + } + + @Override + public void setTintList(ColorStateList tint) { + DrawableCompat.setTintList(mDrawable, tint); + } + + @Override + public void setTintMode(PorterDuff.Mode tintMode) { + DrawableCompat.setTintMode(mDrawable, tintMode); + } + + @Override + public void setHotspot(float x, float y) { + DrawableCompat.setHotspot(mDrawable, x, y); + } + + @Override + public void setHotspotBounds(int left, int top, int right, int bottom) { + DrawableCompat.setHotspotBounds(mDrawable, left, top, right, bottom); + } + + public Drawable getWrappedDrawable() { + return mDrawable; + } + + public void setWrappedDrawable(Drawable drawable) { + if (mDrawable != null) { + mDrawable.setCallback(null); + } + + mDrawable = drawable; + + if (drawable != null) { + drawable.setCallback(this); + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/VersionUtils.java b/eclipse-compile/appcompat/src/android/support/v7/internal/VersionUtils.java new file mode 100644 index 0000000000..babd65c8b5 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/VersionUtils.java @@ -0,0 +1,16 @@ +package android.support.v7.internal; + +import android.os.Build; + +/** + * @hide + */ +public class VersionUtils { + + private VersionUtils() {} + + public static boolean isAtLeastL() { + return Build.VERSION.SDK_INT >= 21; + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/app/NavItemSelectedListener.java b/eclipse-compile/appcompat/src/android/support/v7/internal/app/NavItemSelectedListener.java new file mode 100644 index 0000000000..189260a5bf --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/app/NavItemSelectedListener.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.app; + +import android.support.v7.app.ActionBar; +import android.support.v7.internal.widget.AdapterViewCompat; +import android.view.View; + +/** + * Wrapper to adapt the ActionBar.OnNavigationListener in an AdapterView.OnItemSelectedListener + * for use in Spinner widgets. Used by action bar implementations. + * + * @hide + */ +class NavItemSelectedListener implements AdapterViewCompat.OnItemSelectedListener { + private final ActionBar.OnNavigationListener mListener; + + public NavItemSelectedListener(ActionBar.OnNavigationListener listener) { + mListener = listener; + } + + @Override + public void onItemSelected(AdapterViewCompat parent, View view, int position, long id) { + if (mListener != null) { + mListener.onNavigationItemSelected(position, id); + } + } + + @Override + public void onNothingSelected(AdapterViewCompat parent) { + // Do nothing + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/app/TintViewInflater.java b/eclipse-compile/appcompat/src/android/support/v7/internal/app/TintViewInflater.java new file mode 100644 index 0000000000..9f59256338 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/app/TintViewInflater.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.app; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.internal.widget.TintAutoCompleteTextView; +import android.support.v7.internal.widget.TintButton; +import android.support.v7.internal.widget.TintCheckBox; +import android.support.v7.internal.widget.TintCheckedTextView; +import android.support.v7.internal.widget.TintEditText; +import android.support.v7.internal.widget.TintMultiAutoCompleteTextView; +import android.support.v7.internal.widget.TintRadioButton; +import android.support.v7.internal.widget.TintRatingBar; +import android.support.v7.internal.widget.TintSpinner; +import android.support.v7.internal.widget.ViewUtils; +import android.util.AttributeSet; +import android.view.InflateException; +import android.view.View; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; + +/** + * This class is responsible for manually inflating our tinted widgets which are used on devices + * running {@link android.os.Build.VERSION_CODES#KITKAT KITKAT} or below. As such, this class + * should only be used when running on those devices. + *

This class two main responsibilities: the first is to 'inject' our tinted views in place of + * the framework versions in layout inflation; the second is backport the {@code android:theme} + * functionality for any inflated widgets. This include theme inheritance from it's parent. + * + * @hide + */ +public class TintViewInflater { + + static final Class[] sConstructorSignature = new Class[] { + Context.class, AttributeSet.class}; + + private static final Map> sConstructorMap = new HashMap<>(); + + private final Context mContext; + private final Object[] mConstructorArgs = new Object[2]; + + public TintViewInflater(Context context) { + mContext = context; + } + + public final View createView(View parent, final String name, @NonNull Context context, + @NonNull AttributeSet attrs, boolean inheritContext, boolean themeContext) { + final Context originalContext = context; + + // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy + // by using the parent's context + if (inheritContext && parent != null) { + context = parent.getContext(); + } + if (themeContext) { + // We then apply the theme on the context, if specified + context = ViewUtils.themifyContext(context, attrs, true, true); + } + + // We need to 'inject' our tint aware Views in place of the standard framework versions + switch (name) { + case "EditText": + return new TintEditText(context, attrs); + case "Spinner": + return new TintSpinner(context, attrs); + case "CheckBox": + return new TintCheckBox(context, attrs); + case "RadioButton": + return new TintRadioButton(context, attrs); + case "CheckedTextView": + return new TintCheckedTextView(context, attrs); + case "AutoCompleteTextView": + return new TintAutoCompleteTextView(context, attrs); + case "MultiAutoCompleteTextView": + return new TintMultiAutoCompleteTextView(context, attrs); + case "RatingBar": + return new TintRatingBar(context, attrs); + case "Button": + return new TintButton(context, attrs); + } + + if (originalContext != context) { + // If the original context does not equal our themed context, then we need to manually + // inflate it using the name so that app:theme takes effect. + return createViewFromTag(context, name, attrs); + } + + return null; + } + + private View createViewFromTag(Context context, String name, AttributeSet attrs) { + if (name.equals("view")) { + name = attrs.getAttributeValue(null, "class"); + } + + try { + mConstructorArgs[0] = context; + mConstructorArgs[1] = attrs; + + if (-1 == name.indexOf('.')) { + // try the android.widget prefix first... + return createView(name, "android.widget."); + } else { + return createView(name, null); + } + } catch (Exception e) { + // We do not want to catch these, lets return null and let the actual LayoutInflater + // try + return null; + } finally { + // Don't retain static reference on context. + mConstructorArgs[0] = null; + mConstructorArgs[1] = null; + } + } + + private View createView(String name, String prefix) + throws ClassNotFoundException, InflateException { + Constructor constructor = sConstructorMap.get(name); + + try { + if (constructor == null) { + // Class not found in the cache, see if it's real, and try to add it + Class clazz = mContext.getClassLoader().loadClass( + prefix != null ? (prefix + name) : name).asSubclass(View.class); + + constructor = clazz.getConstructor(sConstructorSignature); + sConstructorMap.put(name, constructor); + } + constructor.setAccessible(true); + return constructor.newInstance(mConstructorArgs); + } catch (Exception e) { + // We do not want to catch these, lets return null and let the actual LayoutInflater + // try + return null; + } + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java b/eclipse-compile/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java new file mode 100644 index 0000000000..2be46f0c7c --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.app; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.WindowCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.internal.view.WindowCallbackWrapper; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.renamemenu.ListMenuPresenter; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.support.v7.internal.widget.DecorToolbar; +import android.support.v7.internal.widget.ToolbarWidgetWrapper; +import android.support.v7.widget.Toolbar; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.SpinnerAdapter; + +import java.util.ArrayList; + +/** + * @hide + */ +public class ToolbarActionBar extends ActionBar { + private DecorToolbar mDecorToolbar; + private boolean mToolbarMenuPrepared; + private Window.Callback mWindowCallback; + private boolean mMenuCallbackSet; + + private boolean mLastMenuVisibility; + private ArrayList mMenuVisibilityListeners = + new ArrayList(); + + private Window mWindow; + private ListMenuPresenter mListMenuPresenter; + + private final Runnable mMenuInvalidator = new Runnable() { + @Override + public void run() { + populateOptionsMenu(); + } + }; + + private final Toolbar.OnMenuItemClickListener mMenuClicker = + new Toolbar.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); + } + }; + + public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window window) { + mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false); + mWindowCallback = new ToolbarCallbackWrapper(window.getCallback()); + mDecorToolbar.setWindowCallback(mWindowCallback); + toolbar.setOnMenuItemClickListener(mMenuClicker); + mDecorToolbar.setWindowTitle(title); + + mWindow = window; + } + + public Window.Callback getWrappedWindowCallback() { + return mWindowCallback; + } + + @Override + public void setCustomView(View view) { + setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + } + + @Override + public void setCustomView(View view, LayoutParams layoutParams) { + view.setLayoutParams(layoutParams); + mDecorToolbar.setCustomView(view); + } + + @Override + public void setCustomView(int resId) { + final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext()); + setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false)); + } + + @Override + public void setIcon(int resId) { + mDecorToolbar.setIcon(resId); + } + + @Override + public void setIcon(Drawable icon) { + mDecorToolbar.setIcon(icon); + } + + @Override + public void setLogo(int resId) { + mDecorToolbar.setLogo(resId); + } + + @Override + public void setLogo(Drawable logo) { + mDecorToolbar.setLogo(logo); + } + + @Override + public void setStackedBackgroundDrawable(Drawable d) { + // This space for rent (do nothing) + } + + @Override + public void setSplitBackgroundDrawable(Drawable d) { + // This space for rent (do nothing) + } + + @Override + public void setHomeButtonEnabled(boolean enabled) { + // If the nav button on a Toolbar is present, it's enabled. No-op. + } + + @Override + public void setElevation(float elevation) { + ViewCompat.setElevation(mDecorToolbar.getViewGroup(), elevation); + } + + @Override + public float getElevation() { + return ViewCompat.getElevation(mDecorToolbar.getViewGroup()); + } + + @Override + public Context getThemedContext() { + return mDecorToolbar.getContext(); + } + + @Override + public boolean isTitleTruncated() { + return super.isTitleTruncated(); + } + + @Override + public void setHomeAsUpIndicator(Drawable indicator) { + mDecorToolbar.setNavigationIcon(indicator); + } + + @Override + public void setHomeAsUpIndicator(int resId) { + mDecorToolbar.setNavigationIcon(resId); + } + + @Override + public void setHomeActionContentDescription(CharSequence description) { + mDecorToolbar.setNavigationContentDescription(description); + } + + @Override + public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) { + // Do nothing + } + + @Override + public void setHomeActionContentDescription(int resId) { + mDecorToolbar.setNavigationContentDescription(resId); + } + + @Override + public void setShowHideAnimationEnabled(boolean enabled) { + // This space for rent; no-op. + } + + @Override + public void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + } + + @Override + public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { + mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback)); + } + + @Override + public void setSelectedNavigationItem(int position) { + switch (mDecorToolbar.getNavigationMode()) { + case NAVIGATION_MODE_LIST: + mDecorToolbar.setDropdownSelectedPosition(position); + break; + default: + throw new IllegalStateException( + "setSelectedNavigationIndex not valid for current navigation mode"); + } + } + + @Override + public int getSelectedNavigationIndex() { + return -1; + } + + @Override + public int getNavigationItemCount() { + return 0; + } + + @Override + public void setTitle(CharSequence title) { + mDecorToolbar.setTitle(title); + } + + @Override + public void setTitle(int resId) { + mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); + } + + @Override + public void setWindowTitle(CharSequence title) { + mDecorToolbar.setWindowTitle(title); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mDecorToolbar.setSubtitle(subtitle); + } + + @Override + public void setSubtitle(int resId) { + mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); + } + + @Override + public void setDisplayOptions(@DisplayOptions int options) { + setDisplayOptions(options, 0xffffffff); + } + + @Override + public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) { + final int currentOptions = mDecorToolbar.getDisplayOptions(); + mDecorToolbar.setDisplayOptions(options & mask | currentOptions & ~mask); + } + + @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 setBackgroundDrawable(@Nullable Drawable d) { + mDecorToolbar.setBackgroundDrawable(d); + } + + @Override + public View getCustomView() { + return mDecorToolbar.getCustomView(); + } + + @Override + public CharSequence getTitle() { + return mDecorToolbar.getTitle(); + } + + @Override + public CharSequence getSubtitle() { + return mDecorToolbar.getSubtitle(); + } + + @Override + public int getNavigationMode() { + return NAVIGATION_MODE_STANDARD; + } + + @Override + public void setNavigationMode(@NavigationMode int mode) { + if (mode == ActionBar.NAVIGATION_MODE_TABS) { + throw new IllegalArgumentException("Tabs not supported in this configuration"); + } + mDecorToolbar.setNavigationMode(mode); + } + + @Override + public int getDisplayOptions() { + return mDecorToolbar.getDisplayOptions(); + } + + @Override + public Tab newTab() { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public void addTab(Tab tab) { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public void addTab(Tab tab, boolean setSelected) { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public void addTab(Tab tab, int position) { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public void addTab(Tab tab, int position, boolean setSelected) { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public void removeTab(Tab tab) { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public void removeTabAt(int position) { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public void removeAllTabs() { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public void selectTab(Tab tab) { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public Tab getSelectedTab() { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public Tab getTabAt(int index) { + throw new UnsupportedOperationException( + "Tabs are not supported in toolbar action bars"); + } + + @Override + public int getTabCount() { + return 0; + } + + @Override + public int getHeight() { + return mDecorToolbar.getHeight(); + } + + @Override + public void show() { + // TODO: Consider a better transition for this. + // Right now use no automatic transition so that the app can supply one if desired. + mDecorToolbar.setVisibility(View.VISIBLE); + } + + @Override + public void hide() { + // TODO: Consider a better transition for this. + // Right now use no automatic transition so that the app can supply one if desired. + mDecorToolbar.setVisibility(View.GONE); + } + + @Override + public boolean isShowing() { + return mDecorToolbar.getVisibility() == View.VISIBLE; + } + + @Override + public boolean openOptionsMenu() { + return mDecorToolbar.showOverflowMenu(); + } + + @Override + public boolean invalidateOptionsMenu() { + mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator); + ViewCompat.postOnAnimation(mDecorToolbar.getViewGroup(), mMenuInvalidator); + return true; + } + + @Override + public boolean collapseActionView() { + if (mDecorToolbar.hasExpandedActionView()) { + mDecorToolbar.collapseActionView(); + return true; + } + return false; + } + + void populateOptionsMenu() { + final Menu menu = getMenu(); + final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; + if (mb != null) { + mb.stopDispatchingItemsChanged(); + } + try { + menu.clear(); + if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) || + !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) { + menu.clear(); + } + } finally { + if (mb != null) { + mb.startDispatchingItemsChanged(); + } + } + } + + @Override + public boolean onMenuKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) { + openOptionsMenu(); + } + return true; + } + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent ev) { + Menu menu = getMenu(); + return menu != null ? menu.performShortcut(keyCode, ev, 0) : false; + } + + 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); + } + } + + private View getListMenuView(Menu menu) { + ensureListMenuPresenter(menu); + + if (menu == null || mListMenuPresenter == null) { + return null; + } + + if (mListMenuPresenter.getAdapter().getCount() > 0) { + return (View) mListMenuPresenter.getMenuView(mDecorToolbar.getViewGroup()); + } + return null; + } + + private void ensureListMenuPresenter(Menu menu) { + if (mListMenuPresenter == null && (menu instanceof MenuBuilder)) { + MenuBuilder mb = (MenuBuilder) menu; + + Context context = mDecorToolbar.getContext(); + final TypedValue outValue = new TypedValue(); + final Resources.Theme widgetTheme = context.getResources().newTheme(); + widgetTheme.setTo(context.getTheme()); + + // Apply the panelMenuListTheme + widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true); + if (outValue.resourceId != 0) { + widgetTheme.applyStyle(outValue.resourceId, true); + } else { + widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true); + } + + context = new ContextThemeWrapper(context, 0); + context.getTheme().setTo(widgetTheme); + + // Finally create the list menu presenter + mListMenuPresenter = new ListMenuPresenter(context, R.layout.abc_list_menu_item_layout); + mListMenuPresenter.setCallback(new PanelMenuPresenterCallback()); + mb.addMenuPresenter(mListMenuPresenter); + } + } + + private class ToolbarCallbackWrapper extends WindowCallbackWrapper { + public ToolbarCallbackWrapper(Window.Callback wrapped) { + super(wrapped); + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + final boolean result = super.onPreparePanel(featureId, view, menu); + if (result && !mToolbarMenuPrepared) { + mDecorToolbar.setMenuPrepared(); + mToolbarMenuPrepared = true; + } + return result; + } + + @Override + public View onCreatePanelView(int featureId) { + switch (featureId) { + case Window.FEATURE_OPTIONS_PANEL: + final Menu menu = mDecorToolbar.getMenu(); + if (onPreparePanel(featureId, null, menu) && onMenuOpened(featureId, menu)) { + return getListMenuView(menu); + } + break; + } + return super.onCreatePanelView(featureId); + } + } + + private Menu getMenu() { + if (!mMenuCallbackSet) { + mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(), + new MenuBuilderCallback()); + mMenuCallbackSet = true; + } + return mDecorToolbar.getMenu(); + } + + private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { + private boolean mClosingActionMenu; + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + if (mWindowCallback != null) { + mWindowCallback.onMenuOpened(WindowCompat.FEATURE_ACTION_BAR, subMenu); + return true; + } + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (mClosingActionMenu) { + return; + } + + mClosingActionMenu = true; + mDecorToolbar.dismissPopupMenus(); + if (mWindowCallback != null) { + mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu); + } + mClosingActionMenu = false; + } + } + + private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (mWindowCallback != null) { + mWindowCallback.onPanelClosed(Window.FEATURE_OPTIONS_PANEL, menu); + } + } + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + if (subMenu == null && mWindowCallback != null) { + mWindowCallback.onMenuOpened(Window.FEATURE_OPTIONS_PANEL, subMenu); + } + return true; + } + } + + private final class MenuBuilderCallback implements MenuBuilder.Callback { + + @Override + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + return false; + } + + @Override + public void onMenuModeChange(MenuBuilder menu) { + if (mWindowCallback != null) { + if (mDecorToolbar.isOverflowMenuShowing()) { + mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu); + } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, + null, menu)) { + mWindowCallback.onMenuOpened(WindowCompat.FEATURE_ACTION_BAR, menu); + } + } + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java b/eclipse-compile/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java new file mode 100644 index 0000000000..479ebb6dc9 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java @@ -0,0 +1,1369 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.app; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.support.v4.view.ViewPropertyAnimatorListener; +import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; +import android.support.v4.view.ViewPropertyAnimatorUpdateListener; +import android.support.v7.app.ActionBar; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.ActionBarPolicy; +import android.support.v7.internal.view.ViewPropertyAnimatorCompatSet; +import android.support.v7.internal.view.SupportMenuInflater; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuPopupHelper; +import android.support.v7.internal.view.renamemenu.SubMenuBuilder; +import android.support.v7.internal.widget.ActionBarContainer; +import android.support.v7.internal.widget.ActionBarContextView; +import android.support.v7.internal.widget.ActionBarOverlayLayout; +import android.support.v7.internal.widget.DecorToolbar; +import android.support.v7.internal.widget.TintManager; +import android.support.v7.internal.widget.ScrollingTabContainerView; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.Toolbar; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewParent; +import android.view.Window; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.AnimationUtils; +import android.widget.SpinnerAdapter; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * WindowDecorActionBar is the ActionBar implementation used + * by devices of all screen sizes as part of the window decor layout. + * 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. + * + * @hide + */ +public class WindowDecorActionBar extends ActionBar implements + ActionBarOverlayLayout.ActionBarVisibilityCallback { + private static final String TAG = "WindowDecorActionBar"; + + /** + * Only allow show/hide animations on ICS+, as that is what ViewPropertyAnimatorCompat supports + */ + private static final boolean ALLOW_SHOW_HIDE_ANIMATIONS = Build.VERSION.SDK_INT >= 14; + + private Context mContext; + private Context mThemedContext; + private Activity mActivity; + private Dialog mDialog; + + private ActionBarOverlayLayout mOverlayLayout; + private ActionBarContainer mContainerView; + private DecorToolbar mDecorToolbar; + private ActionBarContextView mContextView; + private ActionBarContainer mSplitView; + private View mContentView; + private ScrollingTabContainerView mTabScrollView; + + private ArrayList mTabs = new ArrayList(); + + private TabImpl mSelectedTab; + private int mSavedTabPosition = INVALID_POSITION; + + private boolean mDisplayHomeAsUpSet; + + 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; + + private int mCurWindowVisibility = View.VISIBLE; + + private boolean mContentAnimations = true; + private boolean mHiddenByApp; + private boolean mHiddenBySystem; + private boolean mShowingForMode; + + private boolean mNowShowing = true; + + private ViewPropertyAnimatorCompatSet mCurrentShowAnim; + private boolean mShowHideAnimationEnabled; + boolean mHideOnContentScroll; + + private TintManager mTintManager; + + final ViewPropertyAnimatorListener mHideListener = new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) { + if (mContentAnimations && mContentView != null) { + ViewCompat.setTranslationY(mContentView, 0f); + ViewCompat.setTranslationY(mContainerView, 0f); + } + if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { + mSplitView.setVisibility(View.GONE); + } + mContainerView.setVisibility(View.GONE); + mContainerView.setTransitioning(false); + mCurrentShowAnim = null; + completeDeferredDestroyActionMode(); + if (mOverlayLayout != null) { + ViewCompat.requestApplyInsets(mOverlayLayout); + } + } + }; + + final ViewPropertyAnimatorListener mShowListener = new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) { + mCurrentShowAnim = null; + mContainerView.requestLayout(); + } + }; + + final ViewPropertyAnimatorUpdateListener mUpdateListener = + new ViewPropertyAnimatorUpdateListener() { + @Override + public void onAnimationUpdate(View view) { + final ViewParent parent = mContainerView.getParent(); + ((View) parent).invalidate(); + } + }; + + public WindowDecorActionBar(Activity activity, boolean overlayMode) { + mActivity = activity; + Window window = activity.getWindow(); + View decor = window.getDecorView(); + init(decor); + if (!overlayMode) { + mContentView = decor.findViewById(android.R.id.content); + } + } + + public WindowDecorActionBar(Dialog dialog) { + mDialog = dialog; + init(dialog.getWindow().getDecorView()); + } + + /** + * Only for edit mode. + * @hide + */ + public WindowDecorActionBar(View layout) { + assert layout.isInEditMode(); + init(layout); + } + + private void init(View decor) { + mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(R.id.decor_content_parent); + if (mOverlayLayout != null) { + mOverlayLayout.setActionBarVisibilityCallback(this); + } + mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar)); + mContextView = (ActionBarContextView) decor.findViewById( + R.id.action_context_bar); + mContainerView = (ActionBarContainer) decor.findViewById( + R.id.action_bar_container); + + mSplitView = (ActionBarContainer) decor.findViewById(R.id.split_action_bar); + + if (mDecorToolbar == null || mContextView == null || mContainerView == null) { + throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + + "with a compatible window decor layout"); + } + + mContext = mDecorToolbar.getContext(); + mContextDisplayMode = mDecorToolbar.isSplit() ? + CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL; + + // This was initially read from the action bar style + final int current = mDecorToolbar.getDisplayOptions(); + final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0; + if (homeAsUp) { + mDisplayHomeAsUpSet = true; + } + + ActionBarPolicy abp = ActionBarPolicy.get(mContext); + setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp); + setHasEmbeddedTabs(abp.hasEmbeddedTabs()); + + final TypedArray a = mContext.obtainStyledAttributes(null, + R.styleable.ActionBar, + R.attr.actionBarStyle, 0); + if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) { + setHideOnContentScrollEnabled(true); + } + final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0); + if (elevation != 0) { + setElevation(elevation); + } + a.recycle(); + } + + private DecorToolbar getDecorToolbar(View view) { + if (view instanceof DecorToolbar) { + return (DecorToolbar) view; + } else if (view instanceof Toolbar) { + return ((Toolbar) view).getWrapper(); + } else { + throw new IllegalStateException("Can't make a decor toolbar out of " + + view.getClass().getSimpleName()); + } + } + + @Override + public void setElevation(float elevation) { + ViewCompat.setElevation(mContainerView, elevation); + if (mSplitView != null) { + ViewCompat.setElevation(mSplitView, elevation); + } + } + + @Override + public float getElevation() { + return ViewCompat.getElevation(mContainerView); + } + + public void onConfigurationChanged(Configuration newConfig) { + setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs()); + } + + private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) { + mHasEmbeddedTabs = hasEmbeddedTabs; + // Switch tab layout configuration if needed + if (!mHasEmbeddedTabs) { + mDecorToolbar.setEmbeddedTabView(null); + mContainerView.setTabContainer(mTabScrollView); + } else { + mContainerView.setTabContainer(null); + mDecorToolbar.setEmbeddedTabView(mTabScrollView); + } + final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS; + if (mTabScrollView != null) { + if (isInTabMode) { + mTabScrollView.setVisibility(View.VISIBLE); + if (mOverlayLayout != null) { + ViewCompat.requestApplyInsets(mOverlayLayout); + } + } else { + mTabScrollView.setVisibility(View.GONE); + } + } + mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode); + mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode); + } + + private void ensureTabsExist() { + if (mTabScrollView != null) { + return; + } + + ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext); + + if (mHasEmbeddedTabs) { + tabScroller.setVisibility(View.VISIBLE); + mDecorToolbar.setEmbeddedTabView(tabScroller); + } else { + if (getNavigationMode() == NAVIGATION_MODE_TABS) { + tabScroller.setVisibility(View.VISIBLE); + if (mOverlayLayout != null) { + ViewCompat.requestApplyInsets(mOverlayLayout); + } + } else { + tabScroller.setVisibility(View.GONE); + } + mContainerView.setTabContainer(tabScroller); + } + mTabScrollView = tabScroller; + } + + void completeDeferredDestroyActionMode() { + if (mDeferredModeDestroyCallback != null) { + mDeferredModeDestroyCallback.onDestroyActionMode(mDeferredDestroyActionMode); + mDeferredDestroyActionMode = null; + mDeferredModeDestroyCallback = null; + } + } + + public void onWindowVisibilityChanged(int visibility) { + mCurWindowVisibility = visibility; + } + + /** + * 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.cancel(); + } + } + + 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, + mDecorToolbar.getViewGroup(), 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) { + mDecorToolbar.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 (mDecorToolbar.getNavigationMode()) { + case NAVIGATION_MODE_TABS: + selectTab(mTabs.get(position)); + break; + case NAVIGATION_MODE_LIST: + mDecorToolbar.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) { + mDecorToolbar.setTitle(title); + } + + @Override + public void setWindowTitle(CharSequence title) { + mDecorToolbar.setWindowTitle(title); + } + + public void setSubtitle(CharSequence subtitle) { + mDecorToolbar.setSubtitle(subtitle); + } + + public void setDisplayOptions(int options) { + if ((options & DISPLAY_HOME_AS_UP) != 0) { + mDisplayHomeAsUpSet = true; + } + mDecorToolbar.setDisplayOptions(options); + } + + public void setDisplayOptions(int options, int mask) { + final int current = mDecorToolbar.getDisplayOptions(); + if ((mask & DISPLAY_HOME_AS_UP) != 0) { + mDisplayHomeAsUpSet = true; + } + mDecorToolbar.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 mDecorToolbar.getCustomView(); + } + + public CharSequence getTitle() { + return mDecorToolbar.getTitle(); + } + + public CharSequence getSubtitle() { + return mDecorToolbar.getSubtitle(); + } + + public int getNavigationMode() { + return mDecorToolbar.getNavigationMode(); + } + + public int getDisplayOptions() { + return mDecorToolbar.getDisplayOptions(); + } + + public ActionMode startActionMode(ActionMode.Callback callback) { + if (mActionMode != null) { + mActionMode.finish(); + } + + mOverlayLayout.setHideOnContentScrollEnabled(false); + mContextView.killMode(); + ActionModeImpl mode = new ActionModeImpl(mContextView.getContext(), callback); + if (mode.dispatchOnCreate()) { + mode.invalidate(); + mContextView.initForMode(mode); + animateToMode(true); + if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { + // TODO animate this + if (mSplitView.getVisibility() != View.VISIBLE) { + mSplitView.setVisibility(View.VISIBLE); + if (mOverlayLayout != null) { + ViewCompat.requestApplyInsets(mOverlayLayout); + } + } + } + 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; + } + + final FragmentTransaction trans; + if (mActivity instanceof FragmentActivity && !mDecorToolbar.getViewGroup().isInEditMode()) { + // If we're not in edit mode and our Activity is a FragmentActivity, start a tx + trans = ((FragmentActivity) mActivity).getSupportFragmentManager() + .beginTransaction().disallowAddToBackStack(); + } else { + trans = null; + } + + 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(); + } + + public void enableContentAnimations(boolean enabled) { + mContentAnimations = enabled; + } + + @Override + public void show() { + if (mHiddenByApp) { + mHiddenByApp = false; + updateVisibility(false); + } + } + + private void showForActionMode() { + if (!mShowingForMode) { + mShowingForMode = true; + if (mOverlayLayout != null) { + mOverlayLayout.setShowingForActionMode(true); + } + updateVisibility(false); + } + } + + public void showForSystem() { + if (mHiddenBySystem) { + mHiddenBySystem = false; + updateVisibility(true); + } + } + + @Override + public void hide() { + if (!mHiddenByApp) { + mHiddenByApp = true; + updateVisibility(false); + } + } + + private void hideForActionMode() { + if (mShowingForMode) { + mShowingForMode = false; + if (mOverlayLayout != null) { + mOverlayLayout.setShowingForActionMode(false); + } + updateVisibility(false); + } + } + + public void hideForSystem() { + if (!mHiddenBySystem) { + mHiddenBySystem = true; + updateVisibility(true); + } + } + + @Override + public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { + if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) { + throw new IllegalStateException("Action bar must be in overlay mode " + + "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll"); + } + mHideOnContentScroll = hideOnContentScroll; + mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll); + } + + @Override + public boolean isHideOnContentScrollEnabled() { + return mOverlayLayout.isHideOnContentScrollEnabled(); + } + + @Override + public int getHideOffset() { + return mOverlayLayout.getActionBarHideOffset(); + } + + @Override + public void setHideOffset(int offset) { + if (offset != 0 && !mOverlayLayout.isInOverlayMode()) { + throw new IllegalStateException("Action bar must be in overlay mode " + + "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset"); + } + mOverlayLayout.setActionBarHideOffset(offset); + } + + private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem, + boolean showingForMode) { + if (showingForMode) { + return true; + } else if (hiddenByApp || hiddenBySystem) { + return false; + } else { + return true; + } + } + + private void updateVisibility(boolean fromSystem) { + // Based on the current state, should we be hidden or shown? + final boolean shown = checkShowingFlags(mHiddenByApp, mHiddenBySystem, + mShowingForMode); + + if (shown) { + if (!mNowShowing) { + mNowShowing = true; + doShow(fromSystem); + } + } else { + if (mNowShowing) { + mNowShowing = false; + doHide(fromSystem); + } + } + } + + public void doShow(boolean fromSystem) { + if (mCurrentShowAnim != null) { + mCurrentShowAnim.cancel(); + } + mContainerView.setVisibility(View.VISIBLE); + + if (mCurWindowVisibility == View.VISIBLE && ALLOW_SHOW_HIDE_ANIMATIONS && + (mShowHideAnimationEnabled || fromSystem)) { + // because we're about to ask its window loc + ViewCompat.setTranslationY(mContainerView, 0f); + float startingY = -mContainerView.getHeight(); + if (fromSystem) { + int topLeft[] = {0, 0}; + mContainerView.getLocationInWindow(topLeft); + startingY -= topLeft[1]; + } + ViewCompat.setTranslationY(mContainerView, startingY); + ViewPropertyAnimatorCompatSet anim = new ViewPropertyAnimatorCompatSet(); + ViewPropertyAnimatorCompat a = ViewCompat.animate(mContainerView).translationY(0f); + a.setUpdateListener(mUpdateListener); + anim.play(a); + if (mContentAnimations && mContentView != null) { + ViewCompat.setTranslationY(mContentView, startingY); + anim.play(ViewCompat.animate(mContentView).translationY(0f)); + } + if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { + ViewCompat.setTranslationY(mSplitView, mSplitView.getHeight()); + mSplitView.setVisibility(View.VISIBLE); + anim.play(ViewCompat.animate(mSplitView).translationY(0f)); + } + anim.setInterpolator(AnimationUtils.loadInterpolator(mContext, + android.R.anim.decelerate_interpolator)); + anim.setDuration(250); + // If this is being shown from the system, add a small delay. + // This is because we will also be animating in the status bar, + // and these two elements can't be done in lock-step. So we give + // a little time for the status bar to start its animation before + // the action bar animates. (This corresponds to the corresponding + // case when hiding, where the status bar has a small delay before + // starting.) + anim.setListener(mShowListener); + mCurrentShowAnim = anim; + anim.start(); + } else { + ViewCompat.setAlpha(mContainerView, 1f); + ViewCompat.setTranslationY(mContainerView, 0); + if (mContentAnimations && mContentView != null) { + ViewCompat.setTranslationY(mContentView, 0); + } + if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { + ViewCompat.setAlpha(mSplitView, 1f); + ViewCompat.setTranslationY(mSplitView, 0); + mSplitView.setVisibility(View.VISIBLE); + } + mShowListener.onAnimationEnd(null); + } + if (mOverlayLayout != null) { + ViewCompat.requestApplyInsets(mOverlayLayout); + } + } + + public void doHide(boolean fromSystem) { + if (mCurrentShowAnim != null) { + mCurrentShowAnim.cancel(); + } + + if (mCurWindowVisibility == View.VISIBLE && ALLOW_SHOW_HIDE_ANIMATIONS && + (mShowHideAnimationEnabled || fromSystem)) { + ViewCompat.setAlpha(mContainerView, 1f); + mContainerView.setTransitioning(true); + ViewPropertyAnimatorCompatSet anim = new ViewPropertyAnimatorCompatSet(); + float endingY = -mContainerView.getHeight(); + if (fromSystem) { + int topLeft[] = {0, 0}; + mContainerView.getLocationInWindow(topLeft); + endingY -= topLeft[1]; + } + ViewPropertyAnimatorCompat a = ViewCompat.animate(mContainerView).translationY(endingY); + a.setUpdateListener(mUpdateListener); + anim.play(a); + if (mContentAnimations && mContentView != null) { + anim.play(ViewCompat.animate(mContentView).translationY(endingY)); + } + if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) { + ViewCompat.setAlpha(mSplitView, 1f); + anim.play(ViewCompat.animate(mSplitView).translationY(mSplitView.getHeight())); + } + anim.setInterpolator(AnimationUtils.loadInterpolator(mContext, + android.R.anim.accelerate_interpolator)); + anim.setDuration(250); + anim.setListener(mHideListener); + mCurrentShowAnim = anim; + anim.start(); + } else { + mHideListener.onAnimationEnd(null); + } + } + + public boolean isShowing() { + final int height = getHeight(); + // Take into account the case where the bar has a 0 height due to not being measured yet. + return mNowShowing && (height == 0 || getHideOffset() < height); + } + + public void animateToMode(boolean toActionMode) { + if (toActionMode) { + showForActionMode(); + } else { + hideForActionMode(); + } + + mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); + mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); + // mTabScrollView's visibility is not affected by action mode. + } + + 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) { + mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes); + } else { + mThemedContext = mContext; + } + } + return mThemedContext; + } + + @Override + public boolean isTitleTruncated() { + return mDecorToolbar != null && mDecorToolbar.isTitleTruncated(); + } + + @Override + public void setHomeAsUpIndicator(Drawable indicator) { + mDecorToolbar.setNavigationIcon(indicator); + } + + @Override + public void setHomeAsUpIndicator(int resId) { + mDecorToolbar.setNavigationIcon(resId); + } + + @Override + public void setHomeActionContentDescription(CharSequence description) { + mDecorToolbar.setNavigationContentDescription(description); + } + + @Override + public void setHomeActionContentDescription(int resId) { + mDecorToolbar.setNavigationContentDescription(resId); + } + + @Override + public void onContentScrollStarted() { + if (mCurrentShowAnim != null) { + mCurrentShowAnim.cancel(); + mCurrentShowAnim = null; + } + } + + @Override + public void onContentScrollStopped() { + } + + @Override + public boolean collapseActionView() { + if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) { + mDecorToolbar.collapseActionView(); + return true; + } + return false; + } + + /** + * @hide + */ + public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback { + private final Context mActionModeContext; + private final MenuBuilder mMenu; + + private ActionMode.Callback mCallback; + private WeakReference mCustomView; + + public ActionModeImpl(Context context, ActionMode.Callback callback) { + mActionModeContext = context; + mCallback = callback; + mMenu = new MenuBuilder(context) + .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + mMenu.setCallback(this); + } + + @Override + public MenuInflater getMenuInflater() { + return new SupportMenuInflater(mActionModeContext); + } + + @Override + public Menu getMenu() { + return mMenu; + } + + @Override + public void finish() { + if (mActionMode != this) { + // Not the active action mode - no-op + return; + } + + // If this change in state is going to cause the action bar + // to be hidden, 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 (!checkShowingFlags(mHiddenByApp, mHiddenBySystem, false)) { + // With the current state but the action bar hidden, our + // overall showing state is going to be false. + mDeferredDestroyActionMode = this; + mDeferredModeDestroyCallback = mCallback; + } else { + mCallback.onDestroyActionMode(this); + } + mCallback = null; + animateToMode(false); + + // Clear out the context mode views after the animation finishes + mContextView.closeMode(); + mDecorToolbar.getViewGroup().sendAccessibilityEvent( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll); + + mActionMode = null; + } + + @Override + public void invalidate() { + if (mActionMode != this) { + // Not the active action mode - no-op. It's possible we are + // currently deferring onDestroy, so the app doesn't yet know we + // are going away and is trying to use us. That's also a no-op. + return; + } + + 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 void setTitleOptionalHint(boolean titleOptional) { + super.setTitleOptionalHint(titleOptional); + mContextView.setTitleOptional(titleOptional); + } + + @Override + public boolean isTitleOptional() { + return mContextView.isTitleOptional(); + } + + @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(getTintManager().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) { + mDecorToolbar.setCustomView(view); + } + + @Override + public void setCustomView(View view, LayoutParams layoutParams) { + view.setLayoutParams(layoutParams); + mDecorToolbar.setCustomView(view); + } + + @Override + public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { + mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback)); + } + + @Override + public int getSelectedNavigationIndex() { + switch (mDecorToolbar.getNavigationMode()) { + case NAVIGATION_MODE_TABS: + return mSelectedTab != null ? mSelectedTab.getPosition() : -1; + case NAVIGATION_MODE_LIST: + return mDecorToolbar.getDropdownSelectedPosition(); + default: + return -1; + } + } + + @Override + public int getNavigationItemCount() { + switch (mDecorToolbar.getNavigationMode()) { + case NAVIGATION_MODE_TABS: + return mTabs.size(); + case NAVIGATION_MODE_LIST: + return mDecorToolbar.getDropdownItemCount(); + default: + return 0; + } + } + + @Override + public int getTabCount() { + return mTabs.size(); + } + + @Override + public void setNavigationMode(int mode) { + final int oldMode = mDecorToolbar.getNavigationMode(); + switch (oldMode) { + case NAVIGATION_MODE_TABS: + mSavedTabPosition = getSelectedNavigationIndex(); + selectTab(null); + mTabScrollView.setVisibility(View.GONE); + break; + } + if (oldMode != mode && !mHasEmbeddedTabs) { + if (mOverlayLayout != null) { + ViewCompat.requestApplyInsets(mOverlayLayout); + } + } + mDecorToolbar.setNavigationMode(mode); + switch (mode) { + case NAVIGATION_MODE_TABS: + ensureTabsExist(); + mTabScrollView.setVisibility(View.VISIBLE); + if (mSavedTabPosition != INVALID_POSITION) { + setSelectedNavigationItem(mSavedTabPosition); + mSavedTabPosition = INVALID_POSITION; + } + break; + } + mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); + mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); + } + + @Override + public Tab getTabAt(int index) { + return mTabs.get(index); + } + + + @Override + public void setIcon(int resId) { + mDecorToolbar.setIcon(resId); + } + + @Override + public void setIcon(Drawable icon) { + mDecorToolbar.setIcon(icon); + } + + public boolean hasIcon() { + return mDecorToolbar.hasIcon(); + } + + @Override + public void setLogo(int resId) { + mDecorToolbar.setLogo(resId); + } + + @Override + public void setLogo(Drawable logo) { + mDecorToolbar.setLogo(logo); + } + + public boolean hasLogo() { + return mDecorToolbar.hasLogo(); + } + + public void setDefaultDisplayHomeAsUpEnabled(boolean enable) { + if (!mDisplayHomeAsUpSet) { + setDisplayHomeAsUpEnabled(enable); + } + } + + TintManager getTintManager() { + if (mTintManager == null) { + mTintManager = new TintManager(mContext); + } + return mTintManager; + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/text/AllCapsTransformationMethod.java b/eclipse-compile/appcompat/src/android/support/v7/internal/text/AllCapsTransformationMethod.java new file mode 100644 index 0000000000..76d1aed398 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/text/AllCapsTransformationMethod.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.text; + +import android.content.Context; +import android.graphics.Rect; +import android.text.method.TransformationMethod; +import android.view.View; + +import java.util.Locale; + +/** + * @hide + */ +public class AllCapsTransformationMethod implements TransformationMethod { + private Locale mLocale; + + public AllCapsTransformationMethod(Context context) { + mLocale = context.getResources().getConfiguration().locale; + } + + @Override + public CharSequence getTransformation(CharSequence source, View view) { + return source != null ? source.toString().toUpperCase(mLocale) : null; + } + + @Override + public void onFocusChanged(View view, CharSequence sourceText, boolean focused, + int direction, Rect previouslyFocusedRect) { + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/transition/ActionBarTransition.java b/eclipse-compile/appcompat/src/android/support/v7/internal/transition/ActionBarTransition.java new file mode 100644 index 0000000000..bc4ad0451e --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/transition/ActionBarTransition.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.transition; + +import android.view.ViewGroup; + +/** + * @hide + */ +public class ActionBarTransition { + + private static final boolean TRANSITIONS_ENABLED = false; + + private static final int TRANSITION_DURATION = 120; // ms + +// private static final Transition sTransition; +// +// static { +// if (TRANSITIONS_ENABLED) { +//// final ChangeText tc = new ChangeText(); +//// tc.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN); +// final TransitionSet inner = new TransitionSet(); +// inner.addTransition(new ChangeBounds()); +// final TransitionSet tg = new TransitionSet(); +// tg.addTransition(new Fade(Fade.OUT)).addTransition(inner). +// addTransition(new Fade(Fade.IN)); +// tg.setOrdering(TransitionSet.ORDERING_SEQUENTIAL); +// tg.setDuration(TRANSITION_DURATION); +// sTransition = tg; +// } else { +// sTransition = null; +// } +// } + + public static void beginDelayedTransition(ViewGroup sceneRoot) { +// if (TRANSITIONS_ENABLED) { +// TransitionManager.beginDelayedTransition(sceneRoot, sTransition); +// } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/ActionBarPolicy.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/ActionBarPolicy.java new file mode 100644 index 0000000000..dc5c4f0b7f --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/ActionBarPolicy.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.view; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.Build; +import android.support.v4.view.ViewConfigurationCompat; +import android.support.v7.appcompat.R; +import android.view.ViewConfiguration; + +/** + * Allows components to query for various configuration policy decisions about how the action bar + * should lay out and behave on the current device. + * + * @hide + */ +public class ActionBarPolicy { + + private Context mContext; + + public static ActionBarPolicy get(Context context) { + return new ActionBarPolicy(context); + } + + private ActionBarPolicy(Context context) { + mContext = context; + } + + public int getMaxActionButtons() { + return mContext.getResources().getInteger(R.integer.abc_max_action_buttons); + } + + public boolean showsOverflowMenuButton() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + return true; + } else { + return !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext)); + } + } + + public int getEmbeddedMenuWidthLimit() { + return mContext.getResources().getDisplayMetrics().widthPixels / 2; + } + + public boolean hasEmbeddedTabs() { + final int targetSdk = mContext.getApplicationInfo().targetSdkVersion; + if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) { + return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs); + } + + // The embedded tabs policy changed in Jellybean; give older apps the old policy + // so they get what they expect. + return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs_pre_jb); + } + + public int getTabContainerHeight() { + TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar, + R.attr.actionBarStyle, 0); + int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0); + Resources r = mContext.getResources(); + if (!hasEmbeddedTabs()) { + // Stacked tabs; limit the height + height = Math.min(height, + r.getDimensionPixelSize(R.dimen.abc_action_bar_stacked_max_height)); + } + a.recycle(); + return height; + } + + public boolean enableHomeButtonByDefault() { + // Older apps get the home button interaction enabled by default. + // Newer apps need to enable it explicitly. + return mContext.getApplicationInfo().targetSdkVersion < + Build.VERSION_CODES.ICE_CREAM_SANDWICH; + } + + public int getStackedTabMaxWidth() { + return mContext.getResources().getDimensionPixelSize( + R.dimen.abc_action_bar_stacked_tab_max_width); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/ContextThemeWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/ContextThemeWrapper.java new file mode 100644 index 0000000000..e4142035a3 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/ContextThemeWrapper.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.view; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Resources; +import android.support.v7.appcompat.R; +import android.view.LayoutInflater; + +/** + * A ContextWrapper that allows you to modify the theme from what is in the + * wrapped context. + * + * @hide + */ +public class ContextThemeWrapper extends ContextWrapper { + private int mThemeResource; + private Resources.Theme mTheme; + private LayoutInflater mInflater; + + public ContextThemeWrapper(Context base, int themeres) { + super(base); + mThemeResource = themeres; + } + + @Override + public void setTheme(int resid) { + mThemeResource = resid; + initializeTheme(); + } + + public int getThemeResId() { + return mThemeResource; + } + + @Override + public Resources.Theme getTheme() { + if (mTheme != null) { + return mTheme; + } + + if (mThemeResource == 0) { + mThemeResource = R.style.Theme_AppCompat_Light; + } + initializeTheme(); + + return mTheme; + } + + @Override + public Object getSystemService(String name) { + if (LAYOUT_INFLATER_SERVICE.equals(name)) { + if (mInflater == null) { + mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); + } + return mInflater; + } + return getBaseContext().getSystemService(name); + } + + /** + * Called by {@link #setTheme} and {@link #getTheme} to apply a theme + * resource to the current Theme object. Can override to change the + * default (simple) behavior. This method will not be called in multiple + * threads simultaneously. + * + * @param theme The Theme object being modified. + * @param resid The theme style resource being applied to theme. + * @param first Set to true if this is the first time a style is being + * applied to theme. + */ + protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) { + theme.applyStyle(resid, true); + } + + private void initializeTheme() { + final boolean first = mTheme == null; + if (first) { + mTheme = getResources().newTheme(); + Resources.Theme theme = getBaseContext().getTheme(); + if (theme != null) { + mTheme.setTo(theme); + } + } + onApplyThemeResource(mTheme, mThemeResource, first); + } +} + diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/StandaloneActionMode.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/StandaloneActionMode.java new file mode 100644 index 0000000000..ef6a69c50c --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/StandaloneActionMode.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuPopupHelper; +import android.support.v7.internal.view.renamemenu.SubMenuBuilder; +import android.support.v7.internal.widget.ActionBarContextView; +import android.support.v7.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; + +import java.lang.ref.WeakReference; + +/** + * @hide + */ +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 setTitleOptionalHint(boolean titleOptional) { + super.setTitleOptionalHint(titleOptional); + mContextView.setTitleOptional(titleOptional); + } + + @Override + public boolean isTitleOptional() { + return mContextView.isTitleOptional(); + } + + @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; + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java new file mode 100644 index 0000000000..75aa66c848 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.v4.internal.view.SupportMenu; +import android.support.v4.internal.view.SupportMenuItem; +import android.support.v4.util.SimpleArrayMap; +import android.support.v7.internal.view.renamemenu.MenuWrapperFactory; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; + +/** + * Wraps a support {@link android.support.v7.view.ActionMode} as a framework + * {@link android.view.ActionMode}. + * + * @hide + */ +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +public class SupportActionModeWrapper extends ActionMode { + + final Context mContext; + final android.support.v7.view.ActionMode mWrappedObject; + + public SupportActionModeWrapper(Context context, + android.support.v7.view.ActionMode supportActionMode) { + mContext = context; + mWrappedObject = supportActionMode; + } + + @Override + public Object getTag() { + return mWrappedObject.getTag(); + } + + @Override + public void setTag(Object tag) { + mWrappedObject.setTag(tag); + } + + @Override + public void setTitle(CharSequence title) { + mWrappedObject.setTitle(title); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mWrappedObject.setSubtitle(subtitle); + } + + @Override + public void invalidate() { + mWrappedObject.invalidate(); + } + + @Override + public void finish() { + mWrappedObject.finish(); + } + + @Override + public Menu getMenu() { + return MenuWrapperFactory.wrapSupportMenu(mContext, (SupportMenu) mWrappedObject.getMenu()); + } + + @Override + public CharSequence getTitle() { + return mWrappedObject.getTitle(); + } + + @Override + public void setTitle(int resId) { + mWrappedObject.setTitle(resId); + } + + @Override + public CharSequence getSubtitle() { + return mWrappedObject.getSubtitle(); + } + + @Override + public void setSubtitle(int resId) { + mWrappedObject.setSubtitle(resId); + } + + @Override + public View getCustomView() { + return mWrappedObject.getCustomView(); + } + + @Override + public void setCustomView(View view) { + mWrappedObject.setCustomView(view); + } + + @Override + public MenuInflater getMenuInflater() { + return mWrappedObject.getMenuInflater(); + } + + @Override + public boolean getTitleOptionalHint() { + return mWrappedObject.getTitleOptionalHint(); + } + + @Override + public void setTitleOptionalHint(boolean titleOptional) { + mWrappedObject.setTitleOptionalHint(titleOptional); + } + + @Override + public boolean isTitleOptional() { + return mWrappedObject.isTitleOptional(); + } + + /** + * @hide + */ + public static class CallbackWrapper implements android.support.v7.view.ActionMode.Callback { + final Callback mWrappedCallback; + final Context mContext; + + final SimpleArrayMap + mActionModes; + final SimpleArrayMap mMenus; + + public CallbackWrapper(Context context, Callback supportCallback) { + mContext = context; + mWrappedCallback = supportCallback; + mActionModes = new SimpleArrayMap<>(); + mMenus = new SimpleArrayMap<>(); + } + + @Override + public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) { + return mWrappedCallback.onCreateActionMode(getActionModeWrapper(mode), + getMenuWrapper(menu)); + } + + @Override + public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) { + return mWrappedCallback.onPrepareActionMode(getActionModeWrapper(mode), + getMenuWrapper(menu)); + } + + @Override + public boolean onActionItemClicked(android.support.v7.view.ActionMode mode, + android.view.MenuItem item) { + return mWrappedCallback.onActionItemClicked(getActionModeWrapper(mode), + MenuWrapperFactory.wrapSupportMenuItem(mContext, (SupportMenuItem) item)); + } + + @Override + public void onDestroyActionMode(android.support.v7.view.ActionMode mode) { + mWrappedCallback.onDestroyActionMode(getActionModeWrapper(mode)); + } + + private Menu getMenuWrapper(Menu menu) { + Menu wrapper = mMenus.get(menu); + if (wrapper == null) { + wrapper = MenuWrapperFactory.wrapSupportMenu(mContext, (SupportMenu) menu); + mMenus.put(menu, wrapper); + } + return wrapper; + } + + private ActionMode getActionModeWrapper(android.support.v7.view.ActionMode mode) { + // First see if we already have a wrapper for this mode + SupportActionModeWrapper wrapper = mActionModes.get(mode); + if (wrapper != null) { + return wrapper; + } + + // If we reach here then we haven't seen this mode before. Create a new wrapper and + // add it to our collection + wrapper = new SupportActionModeWrapper(mContext, mode); + mActionModes.put(mode, wrapper); + return wrapper; + } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/SupportMenuInflater.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/SupportMenuInflater.java new file mode 100644 index 0000000000..1fcc846e95 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/SupportMenuInflater.java @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.view; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.support.v4.internal.view.SupportMenu; +import android.support.v4.view.ActionProvider; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.renamemenu.MenuItemImpl; +import android.support.v7.internal.view.renamemenu.MenuItemWrapperICS; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; +import android.view.InflateException; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * 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 SupportMenuInflater with an XmlPullParser over a plain XML file at runtime; + * it only works with an XmlPullParser returned from a compiled resource (R. + * something file.) + * + * @hide + */ +public class SupportMenuInflater extends MenuInflater { + private static final String LOG_TAG = "SupportMenuInflater"; + + /** 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; + private Object mRealOwner; + + /** + * Constructs a menu inflater. + * + * @see Activity#getMenuInflater() + */ + public SupportMenuInflater(Context context) { + super(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. + */ + @Override + public void inflate(int menuRes, Menu menu) { + // If we're not dealing with a SupportMenu instance, let super handle + if (!(menu instanceof SupportMenu)) { + super.inflate(menuRes, menu); + return; + } + + 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 Object getRealOwner() { + if (mRealOwner == null) { + mRealOwner = findRealOwner(mContext); + } + return mRealOwner; + } + + private Object findRealOwner(Object owner) { + if (owner instanceof Activity) { + return owner; + } + if (owner instanceof ContextWrapper) { + return findRealOwner(((ContextWrapper) owner).getBaseContext()); + } + return owner; + } + + private static class InflatedOnMenuItemClickListener + implements MenuItem.OnMenuItemClickListener { + private static final Class[] PARAM_TYPES = new Class[] { MenuItem.class }; + + private Object mRealOwner; + private Method mMethod; + + public InflatedOnMenuItemClickListener(Object realOwner, String methodName) { + mRealOwner = realOwner; + Class c = realOwner.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(mRealOwner, item); + } else { + mMethod.invoke(mRealOwner, 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.MenuGroup); + + groupId = a.getResourceId(R.styleable.MenuGroup_android_id, defaultGroupId); + groupCategory = a.getInt( + R.styleable.MenuGroup_android_menuCategory, defaultItemCategory); + groupOrder = a.getInt(R.styleable.MenuGroup_android_orderInCategory, defaultItemOrder); + groupCheckable = a.getInt( + R.styleable.MenuGroup_android_checkableBehavior, defaultItemCheckable); + groupVisible = a.getBoolean(R.styleable.MenuGroup_android_visible, defaultItemVisible); + groupEnabled = a.getBoolean(R.styleable.MenuGroup_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.MenuItem); + + // Inherit attributes from the group as default value + itemId = a.getResourceId(R.styleable.MenuItem_android_id, defaultItemId); + final int category = a.getInt(R.styleable.MenuItem_android_menuCategory, groupCategory); + final int order = a.getInt(R.styleable.MenuItem_android_orderInCategory, groupOrder); + itemCategoryOrder = (category & SupportMenu.CATEGORY_MASK) | + (order & SupportMenu.USER_MASK); + itemTitle = a.getText(R.styleable.MenuItem_android_title); + itemTitleCondensed = a.getText(R.styleable.MenuItem_android_titleCondensed); + itemIconResId = a.getResourceId(R.styleable.MenuItem_android_icon, 0); + itemAlphabeticShortcut = + getShortcut(a.getString(R.styleable.MenuItem_android_alphabeticShortcut)); + itemNumericShortcut = + getShortcut(a.getString(R.styleable.MenuItem_android_numericShortcut)); + if (a.hasValue(R.styleable.MenuItem_android_checkable)) { + // Item has attribute checkable, use it + itemCheckable = a.getBoolean(R.styleable.MenuItem_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.MenuItem_android_checked, defaultItemChecked); + itemVisible = a.getBoolean(R.styleable.MenuItem_android_visible, groupVisible); + itemEnabled = a.getBoolean(R.styleable.MenuItem_android_enabled, groupEnabled); + itemShowAsAction = a.getInt(R.styleable.MenuItem_showAsAction, -1); + itemListenerMethodName = a.getString(R.styleable.MenuItem_android_onClick); + itemActionViewLayout = a.getResourceId(R.styleable.MenuItem_actionLayout, 0); + itemActionViewClassName = a.getString(R.styleable.MenuItem_actionViewClass); + itemActionProviderClassName = a.getString(R.styleable.MenuItem_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) { + MenuItemCompat.setShowAsAction(item, 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(getRealOwner(), itemListenerMethodName)); + } + + final MenuItemImpl impl = item instanceof MenuItemImpl ? (MenuItemImpl) item : null; + if (itemCheckable >= 2) { + if (item instanceof MenuItemImpl) { + ((MenuItemImpl) item).setExclusiveCheckable(true); + } else if (item instanceof MenuItemWrapperICS) { + ((MenuItemWrapperICS) item).setExclusiveCheckable(true); + } + } + + boolean actionViewSpecified = false; + if (itemActionViewClassName != null) { + View actionView = (View) newInstance(itemActionViewClassName, + ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments); + MenuItemCompat.setActionView(item, actionView); + actionViewSpecified = true; + } + if (itemActionViewLayout > 0) { + if (!actionViewSpecified) { + MenuItemCompat.setActionView(item, itemActionViewLayout); + actionViewSpecified = true; + } else { + Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'." + + " Action view already specified."); + } + } + if (itemActionProvider != null) { + MenuItemCompat.setActionProvider(item, 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/eclipse-compile/appcompat/src/android/support/v7/internal/view/ViewPropertyAnimatorCompatSet.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/ViewPropertyAnimatorCompatSet.java new file mode 100644 index 0000000000..457e1b1674 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/ViewPropertyAnimatorCompatSet.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.view; + +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.support.v4.view.ViewPropertyAnimatorListener; +import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; +import android.view.View; +import android.view.animation.Interpolator; + +import java.util.ArrayList; + +/** + * A very naive implementation of a set of + * {@link android.support.v4.view.ViewPropertyAnimatorCompat}. + * + * @hide + */ +public class ViewPropertyAnimatorCompatSet { + + private final ArrayList mAnimators; + + private long mDuration = -1; + private Interpolator mInterpolator; + private ViewPropertyAnimatorListener mListener; + + private boolean mIsStarted; + + public ViewPropertyAnimatorCompatSet() { + mAnimators = new ArrayList(); + } + + public ViewPropertyAnimatorCompatSet play(ViewPropertyAnimatorCompat animator) { + if (!mIsStarted) { + mAnimators.add(animator); + } + return this; + } + + public void start() { + if (mIsStarted) return; + for (ViewPropertyAnimatorCompat animator : mAnimators) { + if (mDuration >= 0) { + animator.setDuration(mDuration); + } + if (mInterpolator != null) { + animator.setInterpolator(mInterpolator); + } + if (mListener != null) { + animator.setListener(mProxyListener); + } + animator.start(); + } + + mIsStarted = true; + } + + private void onAnimationsEnded() { + mIsStarted = false; + } + + public void cancel() { + if (!mIsStarted) { + return; + } + for (ViewPropertyAnimatorCompat animator : mAnimators) { + animator.cancel(); + } + mIsStarted = false; + } + + public ViewPropertyAnimatorCompatSet setDuration(long duration) { + if (!mIsStarted) { + mDuration = duration; + } + return this; + } + + public ViewPropertyAnimatorCompatSet setInterpolator(Interpolator interpolator) { + if (!mIsStarted) { + mInterpolator = interpolator; + } + return this; + } + + public ViewPropertyAnimatorCompatSet setListener(ViewPropertyAnimatorListener listener) { + if (!mIsStarted) { + mListener = listener; + } + return this; + } + + private final ViewPropertyAnimatorListenerAdapter mProxyListener + = new ViewPropertyAnimatorListenerAdapter() { + private boolean mProxyStarted = false; + private int mProxyEndCount = 0; + + @Override + public void onAnimationStart(View view) { + if (mProxyStarted) { + return; + } + mProxyStarted = true; + if (mListener != null) { + mListener.onAnimationStart(null); + } + } + + void onEnd() { + mProxyEndCount = 0; + mProxyStarted = false; + onAnimationsEnded(); + } + + @Override + public void onAnimationEnd(View view) { + if (++mProxyEndCount == mAnimators.size()) { + if (mListener != null) { + mListener.onAnimationEnd(null); + } + onEnd(); + } + } + }; +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/WindowCallbackWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/WindowCallbackWrapper.java new file mode 100644 index 0000000000..d799d00e95 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/WindowCallbackWrapper.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.view; + +import android.view.ActionMode; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; + +/** + * A simple decorator stub for Window.Callback that passes through any calls + * to the wrapped instance as a base implementation. Call super.foo() to call into + * the wrapped callback for any subclasses. + * + * @hide + */ +public class WindowCallbackWrapper implements Window.Callback { + + final Window.Callback mWrapped; + + public WindowCallbackWrapper(Window.Callback wrapped) { + if (wrapped == null) { + throw new IllegalArgumentException("Window callback may not be null"); + } + mWrapped = wrapped; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return mWrapped.dispatchKeyEvent(event); + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + return mWrapped.dispatchKeyShortcutEvent(event); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return mWrapped.dispatchTouchEvent(event); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + return mWrapped.dispatchTrackballEvent(event); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + return mWrapped.dispatchGenericMotionEvent(event); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + return mWrapped.dispatchPopulateAccessibilityEvent(event); + } + + @Override + public View onCreatePanelView(int featureId) { + return mWrapped.onCreatePanelView(featureId); + } + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + return mWrapped.onCreatePanelMenu(featureId, menu); + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + return mWrapped.onPreparePanel(featureId, view, menu); + } + + @Override + public boolean onMenuOpened(int featureId, Menu menu) { + return mWrapped.onMenuOpened(featureId, menu); + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + return mWrapped.onMenuItemSelected(featureId, item); + } + + @Override + public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) { + mWrapped.onWindowAttributesChanged(attrs); + } + + @Override + public void onContentChanged() { + mWrapped.onContentChanged(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + mWrapped.onWindowFocusChanged(hasFocus); + } + + @Override + public void onAttachedToWindow() { + mWrapped.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow() { + mWrapped.onDetachedFromWindow(); + } + + @Override + public void onPanelClosed(int featureId, Menu menu) { + mWrapped.onPanelClosed(featureId, menu); + } + + @Override + public boolean onSearchRequested() { + return mWrapped.onSearchRequested(); + } + + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { + return mWrapped.onWindowStartingActionMode(callback); + } + + @Override + public void onActionModeStarted(ActionMode mode) { + mWrapped.onActionModeStarted(mode); + } + + @Override + public void onActionModeFinished(ActionMode mode) { + mWrapped.onActionModeFinished(mode); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ActionMenuItem.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ActionMenuItem.java new file mode 100644 index 0000000000..b23f2a9c53 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ActionMenuItem.java @@ -0,0 +1,296 @@ +/* + * 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.ActionProvider; +import android.support.v4.internal.view.SupportMenuItem; +import android.support.v4.view.MenuItemCompat; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; + +/** + * @hide + */ +public class ActionMenuItem implements SupportMenuItem { + + 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; + + private Drawable mIconDrawable; + private int mIconResId = NO_ICON; + + private Context mContext; + + private SupportMenuItem.OnMenuItemClickListener mClickListener; + + 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; + 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 != null ? mTitleCondensed : mTitle; + } + + 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; + mIconResId = NO_ICON; + return this; + } + + public MenuItem setIcon(int iconRes) { + mIconResId = iconRes; + mIconDrawable = ContextCompat.getDrawable(mContext, 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 SupportMenuItem setActionView(View actionView) { + throw new UnsupportedOperationException(); + } + + public View getActionView() { + return null; + } + + @Override + public MenuItem setActionProvider(android.view.ActionProvider actionProvider) { + throw new UnsupportedOperationException(); + } + + @Override + public android.view.ActionProvider getActionProvider() { + throw new UnsupportedOperationException(); + } + + @Override + public SupportMenuItem setActionView(int resId) { + throw new UnsupportedOperationException(); + } + + @Override + public ActionProvider getSupportActionProvider() { + return null; + } + + @Override + public SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) { + throw new UnsupportedOperationException(); + } + + @Override + public SupportMenuItem 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(MenuItem.OnActionExpandListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public SupportMenuItem setSupportOnActionExpandListener(MenuItemCompat.OnActionExpandListener listener) { + // No need to save the listener; ActionMenuItem does not support collapsing items. + return this; + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ActionMenuItemView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ActionMenuItemView.java new file mode 100644 index 0000000000..534b2a5b91 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ActionMenuItemView.java @@ -0,0 +1,334 @@ +/* + * 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.ViewCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.text.AllCapsTransformationMethod; +import android.support.v7.internal.widget.CompatTextView; +import android.support.v7.widget.ActionMenuView; +import android.support.v7.widget.ListPopupWindow; +import android.text.TextUtils; +import android.text.method.TransformationMethod; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Toast; + +import java.util.Locale; + +/** + * @hide + */ +public class ActionMenuItemView extends CompatTextView + implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener, + ActionMenuView.ActionMenuChildView { + + private static final String TAG = "ActionMenuItemView"; + + private MenuItemImpl mItemData; + private CharSequence mTitle; + private Drawable mIcon; + private MenuBuilder.ItemInvoker mItemInvoker; + private ListPopupWindow.ForwardingListener mForwardingListener; + private PopupCallback mPopupCallback; + + private boolean mAllowTextWithIcon; + private boolean mExpandedFormat; + private int mMinWidth; + private int mSavedPaddingLeft; + + private static final int MAX_ICON_SIZE = 32; // dp + private int mMaxIconSize; + + 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) { + super(context, attrs, defStyle); + final Resources res = context.getResources(); + mAllowTextWithIcon = res.getBoolean( + R.bool.abc_config_allowActionMenuItemTextWithIcon); + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.ActionMenuItemView, defStyle, 0); + mMinWidth = a.getDimensionPixelSize( + R.styleable.ActionMenuItemView_android_minWidth, 0); + a.recycle(); + + final float density = res.getDisplayMetrics().density; + mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f); + + setOnClickListener(this); + setOnLongClickListener(this); + + mSavedPaddingLeft = -1; + } + + public void onConfigurationChanged(Configuration newConfig) { + if (Build.VERSION.SDK_INT >= 8) { + super.onConfigurationChanged(newConfig); + } + + mAllowTextWithIcon = getContext().getResources().getBoolean( + R.bool.abc_config_allowActionMenuItemTextWithIcon); + updateTextButtonVisibility(); + } + + @Override + public void setPadding(int l, int t, int r, int b) { + mSavedPaddingLeft = l; + super.setPadding(l, t, r, b); + } + + 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()); + if (itemData.hasSubMenu()) { + if (mForwardingListener == null) { + mForwardingListener = new ActionMenuItemForwardingListener(); + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + if (mItemData.hasSubMenu() && mForwardingListener != null + && mForwardingListener.onTouch(this, e)) { + return true; + } + return super.onTouchEvent(e); + } + + @Override + public void onClick(View v) { + if (mItemInvoker != null) { + mItemInvoker.invokeItem(mItemData); + } + } + + public void setItemInvoker(MenuBuilder.ItemInvoker invoker) { + mItemInvoker = invoker; + } + + public void setPopupCallback(PopupCallback popupCallback) { + mPopupCallback = popupCallback; + } + + 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(mTitle); + visible &= mIcon == null || + (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat)); + + setText(visible ? mTitle : null); + } + + public void setIcon(Drawable icon) { + mIcon = icon; + if (icon != null) { + int width = icon.getIntrinsicWidth(); + int height = icon.getIntrinsicHeight(); + if (width > mMaxIconSize) { + final float scale = (float) mMaxIconSize / width; + width = mMaxIconSize; + height *= scale; + } + if (height > mMaxIconSize) { + final float scale = (float) mMaxIconSize / height; + height = mMaxIconSize; + width *= scale; + } + icon.setBounds(0, 0, width, height); + } + setCompoundDrawables(icon, null, null, null); + + updateTextButtonVisibility(); + } + + public boolean hasText() { + return !TextUtils.isEmpty(getText()); + } + + public void setShortcut(boolean showShortcut, char shortcutKey) { + // Action buttons don't show text for shortcut keys. + } + + public void setTitle(CharSequence title) { + mTitle = title; + + setContentDescription(mTitle); + updateTextButtonVisibility(); + } + + 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; + int referenceX = screenPos[0] + width / 2; + if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) { + final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + referenceX = screenWidth - referenceX; // mirror + } + 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 | GravityCompat.END, referenceX, 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) { + final boolean textVisible = hasText(); + if (textVisible && mSavedPaddingLeft >= 0) { + super.setPadding(mSavedPaddingLeft, getPaddingTop(), + getPaddingRight(), getPaddingBottom()); + } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int oldMeasuredWidth = getMeasuredWidth(); + final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(widthSize, mMinWidth) + : mMinWidth; + + if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) { + // Remeasure at exactly the minimum width. + super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), + heightMeasureSpec); + } + + if (!textVisible && mIcon != null) { + // TextView won't center compound drawables in both dimensions without + // a little coercion. Pad in to center the icon after we've measured. + final int w = getMeasuredWidth(); + final int dw = mIcon.getBounds().width(); + super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom()); + } + } + + private class ActionMenuItemForwardingListener extends ListPopupWindow.ForwardingListener { + public ActionMenuItemForwardingListener() { + super(ActionMenuItemView.this); + } + + @Override + public ListPopupWindow getPopup() { + if (mPopupCallback != null) { + return mPopupCallback.getPopup(); + } + return null; + } + + @Override + protected boolean onForwardingStarted() { + // Call the invoker, then check if the expected popup is showing. + if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) { + final ListPopupWindow popup = getPopup(); + return popup != null && popup.isShowing(); + } + return false; + } + + @Override + protected boolean onForwardingStopped() { + final ListPopupWindow popup = getPopup(); + if (popup != null) { + popup.dismiss(); + return true; + } + return false; + } + } + + public static abstract class PopupCallback { + public abstract ListPopupWindow getPopup(); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseMenuPresenter.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseMenuPresenter.java new file mode 100644 index 0000000000..fadb046829 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseMenuPresenter.java @@ -0,0 +1,237 @@ +/* + * 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.support.v4.view.ViewCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * 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. + * + * @hide + */ +public abstract class BaseMenuPresenter implements MenuPresenter { + + 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); + ViewCompat.jumpDrawablesToCurrentState(itemView); + } + 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; + } + + public Callback getCallback() { + return mCallback; + } + + /** + * 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/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseMenuWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseMenuWrapper.java new file mode 100644 index 0000000000..c4b03001b9 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseMenuWrapper.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.support.v4.internal.view.SupportMenuItem; +import android.support.v4.internal.view.SupportSubMenu; +import android.support.v4.util.ArrayMap; +import android.view.MenuItem; +import android.view.SubMenu; + +import java.util.Iterator; +import java.util.Map; + +abstract class BaseMenuWrapper extends BaseWrapper { + + final Context mContext; + + private Map mMenuItems; + private Map mSubMenus; + + BaseMenuWrapper(Context context, T object) { + super(object); + mContext = context; + } + + final MenuItem getMenuItemWrapper(final MenuItem menuItem) { + if (menuItem instanceof SupportMenuItem) { + final SupportMenuItem supportMenuItem = (SupportMenuItem) menuItem; + + // Instantiate Map if null + if (mMenuItems == null) { + mMenuItems = new ArrayMap<>(); + } + + // First check if we already have a wrapper for this item + MenuItem wrappedItem = mMenuItems.get(menuItem); + + if (null == wrappedItem) { + // ... if not, create one and add it to our map + wrappedItem = MenuWrapperFactory.wrapSupportMenuItem(mContext, supportMenuItem); + mMenuItems.put(supportMenuItem, wrappedItem); + } + + return wrappedItem; + } + return menuItem; + } + + final SubMenu getSubMenuWrapper(final SubMenu subMenu) { + if (subMenu instanceof SupportSubMenu) { + final SupportSubMenu supportSubMenu = (SupportSubMenu) subMenu; + + // Instantiate Map if null + if (mSubMenus == null) { + mSubMenus = new ArrayMap<>(); + } + + SubMenu wrappedMenu = mSubMenus.get(supportSubMenu); + + if (null == wrappedMenu) { + wrappedMenu = MenuWrapperFactory.wrapSupportSubMenu(mContext, supportSubMenu); + mSubMenus.put(supportSubMenu, wrappedMenu); + } + return wrappedMenu; + } + return subMenu; + } + + + final void internalClear() { + if (mMenuItems != null) { + mMenuItems.clear(); + } + if (mSubMenus != null) { + mSubMenus.clear(); + } + } + + final void internalRemoveGroup(final int groupId) { + if (mMenuItems == null) { + return; + } + + Iterator iterator = mMenuItems.keySet().iterator(); + android.view.MenuItem menuItem; + + while (iterator.hasNext()) { + menuItem = iterator.next(); + if (groupId == menuItem.getGroupId()) { + iterator.remove(); + } + } + } + + final void internalRemoveItem(final int id) { + if (mMenuItems == null) { + return; + } + + Iterator iterator = mMenuItems.keySet().iterator(); + android.view.MenuItem menuItem; + + while (iterator.hasNext()) { + menuItem = iterator.next(); + if (id == menuItem.getItemId()) { + iterator.remove(); + break; + } + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseWrapper.java new file mode 100644 index 0000000000..d20164b780 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/BaseWrapper.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.view.renamemenu; + +class BaseWrapper { + + final T mWrappedObject; + + BaseWrapper(T object) { + if (null == object) { + throw new IllegalArgumentException("Wrapped Object can not be null."); + } + mWrappedObject = object; + } + + public T getWrappedObject() { + return mWrappedObject; + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ExpandedMenuView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ExpandedMenuView.java new file mode 100644 index 0000000000..8dab7afe5f --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ExpandedMenuView.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuView; +import android.support.v7.internal.view.renamemenu.MenuBuilder.ItemInvoker; +import android.support.v7.internal.widget.TintTypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; + +/** + * The expanded menu view is a list-like menu with all of the available menu items. It is opened + * by the user clicking no the 'More' button on the icon menu view. + * + * @hide + */ +public final class ExpandedMenuView extends ListView + implements ItemInvoker, MenuView, OnItemClickListener { + + private static final int[] TINT_ATTRS = { + android.R.attr.background, + android.R.attr.divider + }; + + private MenuBuilder mMenu; + + /** Default animations for this menu */ + private int mAnimations; + + public ExpandedMenuView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.listViewStyle); + } + + public ExpandedMenuView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs); + setOnItemClickListener(this); + + TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS, + defStyleAttr, 0); + if (a.hasValue(0)) { + setBackgroundDrawable(a.getDrawable(0)); + } + if (a.hasValue(1)) { + setDivider(a.getDrawable(1)); + } + a.recycle(); + } + + @Override + public void initialize(MenuBuilder menu) { + mMenu = menu; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + // Clear the cached bitmaps of children + setChildrenDrawingCacheEnabled(false); + } + + @Override + public boolean invokeItem(MenuItemImpl item) { + return mMenu.performItemAction(item, 0); + } + + @Override + @SuppressWarnings("rawtypes") + public void onItemClick(AdapterView parent, View v, int position, long id) { + invokeItem((MenuItemImpl) getAdapter().getItem(position)); + } + + @Override + public int getWindowAnimations() { + return mAnimations; + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ListMenuItemView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ListMenuItemView.java new file mode 100644 index 0000000000..5bae59cd7c --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ListMenuItemView.java @@ -0,0 +1,282 @@ +/* + * 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.support.v7.appcompat.R; +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. + * + * @hide + */ +public class ListMenuItemView extends LinearLayout implements MenuView.ItemView { + + private static final String TAG = "ListMenuItemView"; + 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; + + private int mMenuType; + + private Context mContext; + private LayoutInflater mInflater; + + private boolean mForceShowIcon; + + public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs); + mContext = context; + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.MenuView, defStyle, 0); + + mBackground = a.getDrawable(R.styleable.MenuView_android_itemBackground); + mTextAppearance = a.getResourceId(R.styleable. + MenuView_android_itemTextAppearance, -1); + mPreserveIconSpacing = a.getBoolean( + R.styleable.MenuView_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.title); + if (mTextAppearance != -1) { + mTitleView.setTextAppearance(mTextAppearanceContext, + mTextAppearance); + } + + mShortcutView = (TextView) findViewById(R.id.shortcut); + } + + public void initialize(MenuItemImpl itemData, int menuType) { + mItemData = itemData; + 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; + } + + // 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()) { + if (mRadioButton == null) { + insertRadioButton(); + } + compoundButton = mRadioButton; + otherCompoundButton = mCheckBox; + } else { + if (mCheckBox == null) { + insertCheckBox(); + } + 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 != null && otherCompoundButton.getVisibility() != GONE) { + otherCompoundButton.setVisibility(GONE); + } + } else { + if (mCheckBox != null) { + mCheckBox.setVisibility(GONE); + } + if (mRadioButton != null) { + 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.abc_list_menu_item_icon, + this, false); + addView(mIconView, 0); + } + + private void insertRadioButton() { + LayoutInflater inflater = getInflater(); + mRadioButton = + (RadioButton) inflater.inflate(R.layout.abc_list_menu_item_radio, + this, false); + addView(mRadioButton); + } + + private void insertCheckBox() { + LayoutInflater inflater = getInflater(); + mCheckBox = + (CheckBox) inflater.inflate(R.layout.abc_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/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ListMenuPresenter.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ListMenuPresenter.java new file mode 100644 index 0000000000..6dce39ef9f --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/ListMenuPresenter.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.database.DataSetObserver; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v7.appcompat.R; +import android.util.SparseArray; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; + +import java.util.ArrayList; + +/** + * MenuPresenter for list-style menus. + * + * @hide + */ +public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener { + private static final String TAG = "ListMenuPresenter"; + + Context mContext; + LayoutInflater mInflater; + MenuBuilder mMenu; + + ExpandedMenuView mMenuView; + + private int mItemIndexOffset; + int mThemeRes; + int mItemLayoutRes; + + private Callback mCallback; + MenuAdapter mAdapter; + + private int mId; + + public static final String VIEWS_TAG = "android:menu:list"; + + /** + * Construct a new ListMenuPresenter. + * @param context Context to use for theming. This will supersede the context provided + * to initForMenu when this presenter is added. + * @param itemLayoutRes Layout resource for individual item views. + */ + public ListMenuPresenter(Context context, int itemLayoutRes) { + this(itemLayoutRes, 0); + mContext = context; + mInflater = LayoutInflater.from(mContext); + } + + /** + * Construct a new ListMenuPresenter. + * @param itemLayoutRes Layout resource for individual item views. + * @param themeRes Resource ID of a theme to use for views. + */ + public ListMenuPresenter(int itemLayoutRes, int themeRes) { + mItemLayoutRes = itemLayoutRes; + mThemeRes = themeRes; + } + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + if (mThemeRes != 0) { + mContext = new ContextThemeWrapper(context, mThemeRes); + mInflater = LayoutInflater.from(mContext); + } else if (mContext != null) { + mContext = context; + if (mInflater == null) { + mInflater = LayoutInflater.from(mContext); + } + } + mMenu = menu; + if (mAdapter != null) { + mAdapter.notifyDataSetChanged(); + } + } + + @Override + public MenuView getMenuView(ViewGroup root) { + if (mMenuView == null) { + mMenuView = (ExpandedMenuView) mInflater.inflate( + R.layout.abc_expanded_menu_layout, root, false); + if (mAdapter == null) { + mAdapter = new MenuAdapter(); + } + mMenuView.setAdapter(mAdapter); + mMenuView.setOnItemClickListener(this); + } + return mMenuView; + } + + /** + * Call this instead of getMenuView if you want to manage your own ListView. + * For proper operation, the ListView hosting this adapter should add + * this presenter as an OnItemClickListener. + * + * @return A ListAdapter containing the items in the menu. + */ + public ListAdapter getAdapter() { + if (mAdapter == null) { + mAdapter = new MenuAdapter(); + } + return mAdapter; + } + + @Override + public void updateMenuView(boolean cleared) { + if (mAdapter != null) mAdapter.notifyDataSetChanged(); + } + + @Override + public void setCallback(Callback cb) { + mCallback = cb; + } + + @Override + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + if (!subMenu.hasVisibleItems()) return false; + + // The window manager will give us a token. + new MenuDialogHelper(subMenu).show(null); + if (mCallback != null) { + mCallback.onOpenSubMenu(subMenu); + } + return true; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (mCallback != null) { + mCallback.onCloseMenu(menu, allMenusAreClosing); + } + } + + int getItemIndexOffset() { + return mItemIndexOffset; + } + + public void setItemIndexOffset(int offset) { + mItemIndexOffset = offset; + if (mMenuView != null) { + updateMenuView(false); + } + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + mMenu.performItemAction(mAdapter.getItem(position), this, 0); + } + + @Override + public boolean flagActionItems() { + return false; + } + + public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { + return false; + } + + public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { + return false; + } + + public void saveHierarchyState(Bundle outState) { + SparseArray viewStates = new SparseArray(); + if (mMenuView != null) { + ((View) mMenuView).saveHierarchyState(viewStates); + } + outState.putSparseParcelableArray(VIEWS_TAG, viewStates); + } + + public void restoreHierarchyState(Bundle inState) { + SparseArray viewStates = inState.getSparseParcelableArray(VIEWS_TAG); + if (viewStates != null) { + ((View) mMenuView).restoreHierarchyState(viewStates); + } + } + + public void setId(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + + @Override + public Parcelable onSaveInstanceState() { + if (mMenuView == null) { + return null; + } + + Bundle state = new Bundle(); + saveHierarchyState(state); + return state; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + restoreHierarchyState((Bundle) state); + } + + private class MenuAdapter extends BaseAdapter { + private int mExpandedIndex = -1; + + public MenuAdapter() { + findExpandedIndex(); + } + + public int getCount() { + ArrayList items = mMenu.getNonActionItems(); + int count = items.size() - mItemIndexOffset; + if (mExpandedIndex < 0) { + return count; + } + return count - 1; + } + + public MenuItemImpl getItem(int position) { + ArrayList items = mMenu.getNonActionItems(); + position += mItemIndexOffset; + 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(mItemLayoutRes, parent, false); + } + + MenuView.ItemView itemView = (MenuView.ItemView) convertView; + 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; + } + + @Override + public void notifyDataSetChanged() { + findExpandedIndex(); + super.notifyDataSetChanged(); + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuBuilder.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuBuilder.java new file mode 100644 index 0000000000..c7305fdefd --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuBuilder.java @@ -0,0 +1,1352 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.view.renamemenu; + +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.support.v4.content.ContextCompat; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.appcompat.R; +import android.support.v4.view.ActionProvider; +import android.support.v4.internal.view.SupportMenu; +import android.support.v4.internal.view.SupportMenuItem; +import android.util.SparseArray; +import android.view.ContextMenu; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Implementation of the {@link android.support.v4.internal.view.SupportMenu} interface for creating a + * standard menu UI. + * + * @hide + */ +public class MenuBuilder implements SupportMenu { + + 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 = SupportMenuItem.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 ContextMenu.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. + */ + 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. + * @hide + */ + 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 + * @hide + */ + 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) { + addMenuPresenter(presenter, mContext); + } + + /** + * Add a presenter to this menu that uses an alternate context for + * inflating menu items. 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 + * @param menuContext The context used to inflate menu items + */ + public void addMenuPresenter(MenuPresenter presenter, Context menuContext) { + mPresenters.add(new WeakReference(presenter)); + presenter.initForMenu(menuContext, 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, + MenuPresenter preferredPresenter) { + if (mPresenters.isEmpty()) return false; + + boolean result = false; + + // Try the preferred presenter first. + if (preferredPresenter != null) { + result = preferredPresenter.onSubMenuSelected(subMenu); + } + + 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 = MenuItemCompat.getActionView(item); + if (v != null && v.getId() != View.NO_ID) { + if (viewStates == null) { + viewStates = new SparseArray(); + } + v.saveHierarchyState(viewStates); + if (MenuItemCompat.isActionViewExpanded(item)) { + 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 = MenuItemCompat.getActionView(item); + 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) { + MenuItemCompat.expandActionView(itemToExpand); + } + } + } + + 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 = createNewMenuItem(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; + } + + // Layoutlib overrides this method to return its custom implementation of MenuItemImpl + private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering, + CharSequence title, int defaultShowAsAction) { + return new MenuItemImpl(this, group, id, categoryOrder, ordering, title, + defaultShowAsAction); + } + + public MenuItem add(CharSequence title) { + return addInternal(0, 0, 0, title); + } + + @Override + public MenuItem add(int titleRes) { + return addInternal(0, 0, 0, mResources.getString(titleRes)); + } + + @Override + public MenuItem add(int group, int id, int categoryOrder, CharSequence title) { + return addInternal(group, id, categoryOrder, title); + } + + @Override + public MenuItem add(int group, int id, int categoryOrder, int title) { + return addInternal(group, id, categoryOrder, mResources.getString(title)); + } + + @Override + public SubMenu addSubMenu(CharSequence title) { + return addSubMenu(0, 0, 0, title); + } + + @Override + public SubMenu addSubMenu(int titleRes) { + return addSubMenu(0, 0, 0, mResources.getString(titleRes)); + } + + @Override + 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; + } + + @Override + public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) { + return addSubMenu(group, id, categoryOrder, mResources.getString(title)); + } + + @Override + 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 < N; i++) { + final ResolveInfo ri = lri.get(i); + Intent rintent = new Intent( + ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]); + rintent.setComponent(new ComponentName( + ri.activityInfo.applicationInfo.packageName, + ri.activityInfo.name)); + final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm)) + .setIcon(ri.loadIcon(pm)) + .setIntent(rintent); + if (outSpecificItems != null && ri.specificIndex >= 0) { + outSpecificItems[ri.specificIndex] = item; + } + } + + return N; + } + + @Override + public void removeItem(int id) { + removeItemAtInt(findItemIndex(id), true); + } + + @Override + 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); + } + + @Override + 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); + } + } + } + + @Override + 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); + } + } + } + + @Override + 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); + } + + @Override + 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); + } + } + } + + @Override + 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; + } + + @Override + 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; + } + + @Override + public int size() { + return mItems.size(); + } + + @Override + public MenuItem getItem(int index) { + return mItems.get(index); + } + + @Override + public boolean isShortcutKey(int keyCode, KeyEvent event) { + return findItemWithShortcutForKey(keyCode, event) != null; + } + + @Override + 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.abc_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; + } + + @Override + 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; + } + + @Override + 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) { + return performItemAction(item, null, flags); + } + + public boolean performItemAction(MenuItem item, MenuPresenter preferredPresenter, int flags) { + MenuItemImpl itemImpl = (MenuItemImpl) item; + + if (itemImpl == null || !itemImpl.isEnabled()) { + return false; + } + + boolean invoked = itemImpl.invoke(); + + final ActionProvider provider = itemImpl.getSupportActionProvider(); + final boolean providerHasSubMenu = provider != null && provider.hasSubMenu(); + if (itemImpl.hasCollapsibleActionView()) { + invoked |= itemImpl.expandActionView(); + if (invoked) close(true); + } else if (itemImpl.hasSubMenu() || providerHasSubMenu) { + close(false); + + if (!itemImpl.hasSubMenu()) { + itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl)); + } + + final SubMenuBuilder subMenu = (SubMenuBuilder) itemImpl.getSubMenu(); + if (providerHasSubMenu) { + provider.onPrepareSubMenu(subMenu); + } + invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter); + 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. + */ + public 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; + } + + @Override + 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.) + */ + public 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); + } + + public 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() { + // Important side effect: if getVisibleItems is stale it may refresh, + // which can affect action items staleness. + final ArrayList visibleItems = getVisibleItems(); + + 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(); + 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; + } + + public ArrayList getActionItems() { + flagActionItems(); + return mActionItems; + } + + public 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 = ContextCompat.getDrawable(getContext(), 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(ContextMenu.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; + } +} + diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuDialogHelper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuDialogHelper.java new file mode 100644 index 0000000000..2b5f04865f --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuDialogHelper.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.view.renamemenu; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.IBinder; +import android.support.v7.appcompat.R; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +/** + * Helper for menus that appear as Dialogs (context and submenus). + * + * @hide + */ +public class MenuDialogHelper implements DialogInterface.OnKeyListener, + DialogInterface.OnClickListener, + DialogInterface.OnDismissListener, + MenuPresenter.Callback { + private MenuBuilder mMenu; + private AlertDialog mDialog; + ListMenuPresenter mPresenter; + private MenuPresenter.Callback mPresenterCallback; + + public MenuDialogHelper(MenuBuilder menu) { + mMenu = menu; + } + + /** + * Shows menu as a dialog. + * + * @param windowToken Optional token to assign to the window. + */ + public void show(IBinder windowToken) { + // Many references to mMenu, create local reference + final MenuBuilder menu = mMenu; + + // Get the builder for the dialog + final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext()); + + // Need to force Light Menu theme as list_menu_item_layout is usually against a dark bg and + // AlertDialog's bg is white + mPresenter = new ListMenuPresenter(R.layout.abc_list_menu_item_layout, + R.style.Theme_AppCompat_CompactMenu); + + mPresenter.setCallback(this); + mMenu.addMenuPresenter(mPresenter); + builder.setAdapter(mPresenter.getAdapter(), this); + + // Set the title + final View headerView = menu.getHeaderView(); + if (headerView != null) { + // Menu's client has given a custom header view, use it + builder.setCustomTitle(headerView); + } else { + // Otherwise use the (text) title and icon + builder.setIcon(menu.getHeaderIcon()).setTitle(menu.getHeaderTitle()); + } + + // Set the key listener + builder.setOnKeyListener(this); + + // Show the menu + mDialog = builder.create(); + mDialog.setOnDismissListener(this); + + WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes(); + lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + if (windowToken != null) { + lp.token = windowToken; + } + lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + + mDialog.show(); + } + + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_BACK) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + Window win = mDialog.getWindow(); + if (win != null) { + View decor = win.getDecorView(); + if (decor != null) { + KeyEvent.DispatcherState ds = decor.getKeyDispatcherState(); + if (ds != null) { + ds.startTracking(event, this); + return true; + } + } + } + } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()) { + Window win = mDialog.getWindow(); + if (win != null) { + View decor = win.getDecorView(); + if (decor != null) { + KeyEvent.DispatcherState ds = decor.getKeyDispatcherState(); + if (ds != null && ds.isTracking(event)) { + mMenu.close(true); + dialog.dismiss(); + return true; + } + } + } + } + } + + // Menu shortcut matching + return mMenu.performShortcut(keyCode, event, 0); + + } + + public void setPresenterCallback(MenuPresenter.Callback cb) { + mPresenterCallback = cb; + } + + /** + * Dismisses the menu's dialog. + * + * @see Dialog#dismiss() + */ + public void dismiss() { + if (mDialog != null) { + mDialog.dismiss(); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + mPresenter.onCloseMenu(mMenu, true); + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (allMenusAreClosing || menu == mMenu) { + dismiss(); + } + if (mPresenterCallback != null) { + mPresenterCallback.onCloseMenu(menu, allMenusAreClosing); + } + } + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + if (mPresenterCallback != null) { + return mPresenterCallback.onOpenSubMenu(subMenu); + } + return false; + } + + public void onClick(DialogInterface dialog, int which) { + mMenu.performItemAction((MenuItemImpl) mPresenter.getAdapter().getItem(which), 0); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemImpl.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemImpl.java new file mode 100644 index 0000000000..26de7c7b99 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemImpl.java @@ -0,0 +1,743 @@ +/* + * 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 android.support.v7.internal.view.renamemenu; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.ActionProvider; +import android.support.v4.internal.view.SupportMenuItem; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.internal.widget.TintManager; +import android.util.Log; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.ViewDebug; +import android.widget.LinearLayout; + +/** + * @hide + */ +public final class MenuItemImpl implements SupportMenuItem { + + 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 SupportMenuItem.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 MenuItemCompat.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) { + + /*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; + } + + @Override + public boolean isEnabled() { + return (mFlags & ENABLED) != 0; + } + + @Override + public MenuItem setEnabled(boolean enabled) { + if (enabled) { + mFlags |= ENABLED; + } else { + mFlags &= ~ENABLED; + } + + mMenu.onItemsChanged(false); + + return this; + } + + @Override + public int getGroupId() { + return mGroup; + } + + @Override + @ViewDebug.CapturedViewProperty + public int getItemId() { + return mId; + } + + @Override + public int getOrder() { + return mCategoryOrder; + } + + public int getOrdering() { + return mOrdering; + } + + @Override + public Intent getIntent() { + return mIntent; + } + + @Override + public MenuItem setIntent(Intent intent) { + mIntent = intent; + return this; + } + + Runnable getCallback() { + return mItemCallback; + } + + public MenuItem setCallback(Runnable callback) { + mItemCallback = callback; + return this; + } + + @Override + public char getAlphabeticShortcut() { + return mShortcutAlphabeticChar; + } + + @Override + public MenuItem setAlphabeticShortcut(char alphaChar) { + if (mShortcutAlphabeticChar == alphaChar) { + return this; + } + + mShortcutAlphabeticChar = Character.toLowerCase(alphaChar); + + mMenu.onItemsChanged(false); + + return this; + } + + @Override + public char getNumericShortcut() { + return mShortcutNumericChar; + } + + @Override + public MenuItem setNumericShortcut(char numericChar) { + if (mShortcutNumericChar == numericChar) { + return this; + } + + mShortcutNumericChar = numericChar; + + mMenu.onItemsChanged(false); + + return this; + } + + @Override + 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); + } + + @Override + public SubMenu getSubMenu() { + return mSubMenu; + } + + @Override + public boolean hasSubMenu() { + return mSubMenu != null; + } + + void setSubMenu(SubMenuBuilder subMenu) { + mSubMenu = subMenu; + + subMenu.setHeaderTitle(getTitle()); + } + + @Override + @ViewDebug.CapturedViewProperty + public CharSequence getTitle() { + return mTitle; + } + + /** + * Gets the title for a particular {@link MenuView.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(); + } + + @Override + public MenuItem setTitle(CharSequence title) { + mTitle = title; + + mMenu.onItemsChanged(false); + + if (mSubMenu != null) { + mSubMenu.setHeaderTitle(title); + } + + return this; + } + + @Override + public MenuItem setTitle(int title) { + return setTitle(mMenu.getContext().getString(title)); + } + + @Override + public CharSequence getTitleCondensed() { + final CharSequence ctitle = mTitleCondensed != null ? mTitleCondensed : mTitle; + + if (Build.VERSION.SDK_INT < 18 && ctitle != null && !(ctitle instanceof String)) { + // For devices pre-JB-MR2, where we have a non-String CharSequence, we need to + // convert this to a String so that EventLog.writeEvent() does not throw an exception + // in Activity.onMenuItemSelected() + return ctitle.toString(); + } else { + // Else, we just return the condensed title + return ctitle; + } + } + + @Override + 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; + } + + @Override + public Drawable getIcon() { + if (mIconDrawable != null) { + return mIconDrawable; + } + + if (mIconResId != NO_ICON) { + Drawable icon = TintManager.getDrawable(mMenu.getContext(), mIconResId); + mIconResId = NO_ICON; + mIconDrawable = icon; + return icon; + } + + return null; + } + + @Override + public MenuItem setIcon(Drawable icon) { + mIconResId = NO_ICON; + mIconDrawable = icon; + mMenu.onItemsChanged(false); + + return this; + } + + @Override + 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; + } + + @Override + public boolean isCheckable() { + return (mFlags & CHECKABLE) == CHECKABLE; + } + + @Override + 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; + } + + @Override + public boolean isChecked() { + return (mFlags & CHECKED) == CHECKED; + } + + @Override + 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); + } + } + + @Override + public boolean isVisible() { + if (mActionProvider != null && mActionProvider.overridesItemVisibility()) { + return (mFlags & HIDDEN) == 0 && mActionProvider.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; + } + + @Override + 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; + } + + @Override + public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) { + mClickListener = clickListener; + return this; + } + + @Override + public String toString() { + return mTitle.toString(); + } + + void setMenuInfo(ContextMenuInfo menuInfo) { + mMenuInfo = menuInfo; + } + + @Override + 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; + } + + @Override + 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); + } + + @Override + public SupportMenuItem 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; + } + + @Override + public SupportMenuItem setActionView(int resId) { + final Context context = mMenu.getContext(); + final LayoutInflater inflater = LayoutInflater.from(context); + setActionView(inflater.inflate(resId, new LinearLayout(context), false)); + return this; + } + + @Override + public View getActionView() { + if (mActionView != null) { + return mActionView; + } else if (mActionProvider != null) { + mActionView = mActionProvider.onCreateActionView(this); + return mActionView; + } else { + return null; + } + } + + @Override + public MenuItem setActionProvider(android.view.ActionProvider actionProvider) { + throw new UnsupportedOperationException( + "This is not supported, use MenuItemCompat.setActionProvider()"); + } + + @Override + public android.view.ActionProvider getActionProvider() { + throw new UnsupportedOperationException( + "This is not supported, use MenuItemCompat.getActionProvider()"); + } + + @Override + public ActionProvider getSupportActionProvider() { + return mActionProvider; + } + + @Override + public SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) { + if (mActionProvider != null) { + mActionProvider.setVisibilityListener(null); + } + mActionView = null; + mActionProvider = actionProvider; + mMenu.onItemsChanged(true); // Measurement can be changed + if (mActionProvider != null) { + mActionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() { + @Override + public void onActionProviderVisibilityChanged(boolean isVisible) { + mMenu.onItemVisibleChanged(MenuItemImpl.this); + } + }); + } + return this; + } + + @Override + public SupportMenuItem setShowAsActionFlags(int actionEnum) { + setShowAsAction(actionEnum); + return this; + } + + @Override + public boolean expandActionView() { + if (!hasCollapsibleActionView()) { + 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 SupportMenuItem setSupportOnActionExpandListener( + MenuItemCompat.OnActionExpandListener listener) { + mOnActionExpandListener = listener; + return this; + } + + public boolean hasCollapsibleActionView() { + if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0) { + if (mActionView == null && mActionProvider != null) { + mActionView = mActionProvider.onCreateActionView(this); + } + return mActionView != null; + } + return false; + } + + public void setActionViewExpanded(boolean isExpanded) { + mIsActionViewExpanded = isExpanded; + mMenu.onItemsChanged(false); + } + + @Override + public boolean isActionViewExpanded() { + return mIsActionViewExpanded; + } + + @Override + public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) { + throw new UnsupportedOperationException( + "This is not supported, use MenuItemCompat.setOnActionExpandListener()"); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemWrapperICS.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemWrapperICS.java new file mode 100644 index 0000000000..8ac355fdf3 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemWrapperICS.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.view.renamemenu; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.internal.view.SupportMenuItem; +import android.support.v4.view.ActionProvider; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.view.CollapsibleActionView; +import android.util.Log; +import android.view.ContextMenu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.widget.FrameLayout; + +import java.lang.reflect.Method; + +/** + * Wraps a support {@link SupportMenuItem} as a framework {@link android.view.MenuItem} + * @hide + */ +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) +public class MenuItemWrapperICS extends BaseMenuWrapper implements MenuItem { + static final String LOG_TAG = "MenuItemWrapper"; + + // Reflection Method to call setExclusiveCheckable + private Method mSetExclusiveCheckableMethod; + + MenuItemWrapperICS(Context context, SupportMenuItem object) { + super(context, object); + } + + @Override + public int getItemId() { + return mWrappedObject.getItemId(); + } + + @Override + public int getGroupId() { + return mWrappedObject.getGroupId(); + } + + @Override + public int getOrder() { + return mWrappedObject.getOrder(); + } + + @Override + public MenuItem setTitle(CharSequence title) { + mWrappedObject.setTitle(title); + return this; + } + + @Override + public MenuItem setTitle(int title) { + mWrappedObject.setTitle(title); + return this; + } + + @Override + public CharSequence getTitle() { + return mWrappedObject.getTitle(); + } + + @Override + public MenuItem setTitleCondensed(CharSequence title) { + mWrappedObject.setTitleCondensed(title); + return this; + } + + @Override + public CharSequence getTitleCondensed() { + return mWrappedObject.getTitleCondensed(); + } + + @Override + public MenuItem setIcon(Drawable icon) { + mWrappedObject.setIcon(icon); + return this; + } + + @Override + public MenuItem setIcon(int iconRes) { + mWrappedObject.setIcon(iconRes); + return this; + } + + @Override + public Drawable getIcon() { + return mWrappedObject.getIcon(); + } + + @Override + public MenuItem setIntent(Intent intent) { + mWrappedObject.setIntent(intent); + return this; + } + + @Override + public Intent getIntent() { + return mWrappedObject.getIntent(); + } + + @Override + public MenuItem setShortcut(char numericChar, char alphaChar) { + mWrappedObject.setShortcut(numericChar, alphaChar); + return this; + } + + @Override + public MenuItem setNumericShortcut(char numericChar) { + mWrappedObject.setNumericShortcut(numericChar); + return this; + } + + @Override + public char getNumericShortcut() { + return mWrappedObject.getNumericShortcut(); + } + + @Override + public MenuItem setAlphabeticShortcut(char alphaChar) { + mWrappedObject.setAlphabeticShortcut(alphaChar); + return this; + } + + @Override + public char getAlphabeticShortcut() { + return mWrappedObject.getAlphabeticShortcut(); + } + + @Override + public MenuItem setCheckable(boolean checkable) { + mWrappedObject.setCheckable(checkable); + return this; + } + + @Override + public boolean isCheckable() { + return mWrappedObject.isCheckable(); + } + + @Override + public MenuItem setChecked(boolean checked) { + mWrappedObject.setChecked(checked); + return this; + } + + @Override + public boolean isChecked() { + return mWrappedObject.isChecked(); + } + + @Override + public MenuItem setVisible(boolean visible) { + return mWrappedObject.setVisible(visible); + } + + @Override + public boolean isVisible() { + return mWrappedObject.isVisible(); + } + + @Override + public MenuItem setEnabled(boolean enabled) { + mWrappedObject.setEnabled(enabled); + return this; + } + + @Override + public boolean isEnabled() { + return mWrappedObject.isEnabled(); + } + + @Override + public boolean hasSubMenu() { + return mWrappedObject.hasSubMenu(); + } + + @Override + public SubMenu getSubMenu() { + return getSubMenuWrapper(mWrappedObject.getSubMenu()); + } + + @Override + public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) { + mWrappedObject.setOnMenuItemClickListener(menuItemClickListener != null ? + new OnMenuItemClickListenerWrapper(menuItemClickListener) : null); + return this; + } + + @Override + public ContextMenu.ContextMenuInfo getMenuInfo() { + return mWrappedObject.getMenuInfo(); + } + + @Override + public void setShowAsAction(int actionEnum) { + mWrappedObject.setShowAsAction(actionEnum); + } + + @Override + public MenuItem setShowAsActionFlags(int actionEnum) { + mWrappedObject.setShowAsActionFlags(actionEnum); + return this; + } + + @Override + public MenuItem setActionView(View view) { + if (view instanceof android.view.CollapsibleActionView) { + view = new CollapsibleActionViewWrapper(view); + } + mWrappedObject.setActionView(view); + return this; + } + + @Override + public MenuItem setActionView(int resId) { + // Make framework menu item inflate the view + mWrappedObject.setActionView(resId); + + View actionView = mWrappedObject.getActionView(); + if (actionView instanceof android.view.CollapsibleActionView) { + // If the inflated Action View is support-collapsible, wrap it + mWrappedObject.setActionView(new CollapsibleActionViewWrapper(actionView)); + } + return this; + } + + @Override + public View getActionView() { + View actionView = mWrappedObject.getActionView(); + if (actionView instanceof CollapsibleActionViewWrapper) { + return ((CollapsibleActionViewWrapper) actionView).getWrappedView(); + } + return actionView; + } + + @Override + public MenuItem setActionProvider(android.view.ActionProvider provider) { + mWrappedObject.setSupportActionProvider( + provider != null ? createActionProviderWrapper(provider) : null); + return this; + } + + @Override + public android.view.ActionProvider getActionProvider() { + ActionProvider provider = mWrappedObject.getSupportActionProvider(); + if (provider instanceof ActionProviderWrapper) { + return ((ActionProviderWrapper) provider).mInner; + } + return null; + } + + @Override + public boolean expandActionView() { + return mWrappedObject.expandActionView(); + } + + @Override + public boolean collapseActionView() { + return mWrappedObject.collapseActionView(); + } + + @Override + public boolean isActionViewExpanded() { + return mWrappedObject.isActionViewExpanded(); + } + + @Override + public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) { + mWrappedObject.setSupportOnActionExpandListener(listener != null ? + new OnActionExpandListenerWrapper(listener) : null); + return this; + } + + public void setExclusiveCheckable(boolean checkable) { + try { + if (mSetExclusiveCheckableMethod == null) { + mSetExclusiveCheckableMethod = mWrappedObject.getClass() + .getDeclaredMethod("setExclusiveCheckable", Boolean.TYPE); + } + mSetExclusiveCheckableMethod.invoke(mWrappedObject, checkable); + } catch (Exception e) { + Log.w(LOG_TAG, "Error while calling setExclusiveCheckable", e); + } + } + + ActionProviderWrapper createActionProviderWrapper(android.view.ActionProvider provider) { + return new ActionProviderWrapper(mContext, provider); + } + + private class OnMenuItemClickListenerWrapper extends BaseWrapper + implements android.view.MenuItem.OnMenuItemClickListener { + + OnMenuItemClickListenerWrapper(OnMenuItemClickListener object) { + super(object); + } + + @Override + public boolean onMenuItemClick(android.view.MenuItem item) { + return mWrappedObject.onMenuItemClick(getMenuItemWrapper(item)); + } + } + + private class OnActionExpandListenerWrapper extends BaseWrapper + implements MenuItemCompat.OnActionExpandListener { + + OnActionExpandListenerWrapper(MenuItem.OnActionExpandListener object) { + super(object); + } + + @Override + public boolean onMenuItemActionExpand(android.view.MenuItem item) { + return mWrappedObject.onMenuItemActionExpand(getMenuItemWrapper(item)); + } + + @Override + public boolean onMenuItemActionCollapse(android.view.MenuItem item) { + return mWrappedObject.onMenuItemActionCollapse(getMenuItemWrapper(item)); + } + } + + class ActionProviderWrapper extends android.support.v4.view.ActionProvider { + final android.view.ActionProvider mInner; + + public ActionProviderWrapper(Context context, android.view.ActionProvider inner) { + super(context); + mInner = inner; + } + + @Override + public View onCreateActionView() { + return mInner.onCreateActionView(); + } + + @Override + public boolean onPerformDefaultAction() { + return mInner.onPerformDefaultAction(); + } + + @Override + public boolean hasSubMenu() { + return mInner.hasSubMenu(); + } + + @Override + public void onPrepareSubMenu(android.view.SubMenu subMenu) { + mInner.onPrepareSubMenu(getSubMenuWrapper(subMenu)); + } + } + + /** + * Wrap a support {@link android.support.v7.view.CollapsibleActionView} into a framework + * {@link android.view.CollapsibleActionView}. + */ + static class CollapsibleActionViewWrapper extends FrameLayout + implements CollapsibleActionView { + + final android.view.CollapsibleActionView mWrappedView; + + CollapsibleActionViewWrapper(View actionView) { + super(actionView.getContext()); + mWrappedView = (android.view.CollapsibleActionView) actionView; + addView(actionView); + } + + @Override + public void onActionViewExpanded() { + mWrappedView.onActionViewExpanded(); + } + + @Override + public void onActionViewCollapsed() { + mWrappedView.onActionViewCollapsed(); + } + + View getWrappedView() { + return (View) mWrappedView; + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemWrapperJB.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemWrapperJB.java new file mode 100644 index 0000000000..e1cc932487 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuItemWrapperJB.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.view.renamemenu; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.v4.internal.view.SupportMenuItem; +import android.support.v4.view.ActionProvider; +import android.view.MenuItem; +import android.view.View; + +/** + * Wraps a support {@link SupportMenuItem} as a framework {@link android.view.MenuItem} + * @hide + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +class MenuItemWrapperJB extends MenuItemWrapperICS { + + MenuItemWrapperJB(Context context, SupportMenuItem object) { + super(context, object); + } + + @Override + ActionProviderWrapper createActionProviderWrapper(android.view.ActionProvider provider) { + return new ActionProviderWrapperJB(mContext, provider); + } + + class ActionProviderWrapperJB extends ActionProviderWrapper + implements android.view.ActionProvider.VisibilityListener { + ActionProvider.VisibilityListener mListener; + + public ActionProviderWrapperJB(Context context, android.view.ActionProvider inner) { + super(context, inner); + } + + @Override + public View onCreateActionView(MenuItem forItem) { + return mInner.onCreateActionView(forItem); + } + + @Override + public boolean overridesItemVisibility() { + return mInner.overridesItemVisibility(); + } + + @Override + public boolean isVisible() { + return mInner.isVisible(); + } + + @Override + public void refreshVisibility() { + mInner.refreshVisibility(); + } + + @Override + public void setVisibilityListener(ActionProvider.VisibilityListener listener) { + mListener = listener; + mInner.setVisibilityListener(listener != null ? this : null); + } + + @Override + public void onActionProviderVisibilityChanged(boolean isVisible) { + if (mListener != null) { + mListener.onActionProviderVisibilityChanged(isVisible); + } + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuPopupHelper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuPopupHelper.java new file mode 100644 index 0000000000..bfe02d6067 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuPopupHelper.java @@ -0,0 +1,404 @@ +/* + * 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Parcelable; +import android.support.v7.appcompat.R; +import android.support.v7.widget.ListPopupWindow; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MenuItem; +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 java.util.ArrayList; + +/** + * 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, + MenuPresenter { + + private static final String TAG = "MenuPopupHelper"; + + static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout; + + private final Context mContext; + private final LayoutInflater mInflater; + private final MenuBuilder mMenu; + private final MenuAdapter mAdapter; + private final boolean mOverflowOnly; + private final int mPopupMaxWidth; + private final int mPopupStyleAttr; + private final int mPopupStyleRes; + + private View mAnchorView; + private ListPopupWindow mPopup; + private ViewTreeObserver mTreeObserver; + private Callback mPresenterCallback; + + boolean mForceShowIcon; + + private ViewGroup mMeasureParent; + + /** Whether the cached content width value is valid. */ + private boolean mHasContentWidth; + + /** Cached content width from {@link #measureContentWidth}. */ + private int mContentWidth; + + private int mDropDownGravity = Gravity.NO_GRAVITY; + + public MenuPopupHelper(Context context, MenuBuilder menu) { + this(context, menu, null, false, R.attr.popupMenuStyle); + } + + public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) { + this(context, menu, anchorView, false, R.attr.popupMenuStyle); + } + + public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView, + boolean overflowOnly, int popupStyleAttr) { + this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0); + } + + public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView, + boolean overflowOnly, int popupStyleAttr, int popupStyleRes) { + mContext = context; + mInflater = LayoutInflater.from(context); + mMenu = menu; + mAdapter = new MenuAdapter(mMenu); + mOverflowOnly = overflowOnly; + mPopupStyleAttr = popupStyleAttr; + mPopupStyleRes = popupStyleRes; + + final Resources res = context.getResources(); + mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2, + res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth)); + + mAnchorView = anchorView; + + // Present the menu using our context, not the menu builder's context. + menu.addMenuPresenter(this, context); + } + + public void setAnchorView(View anchor) { + mAnchorView = anchor; + } + + public void setForceShowIcon(boolean forceShow) { + mForceShowIcon = forceShow; + } + + public void setGravity(int gravity) { + mDropDownGravity = gravity; + } + + public void show() { + if (!tryShow()) { + throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor"); + } + } + + public ListPopupWindow getPopup() { + return mPopup; + } + + public boolean tryShow() { + mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes); + mPopup.setOnDismissListener(this); + mPopup.setOnItemClickListener(this); + 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); + mPopup.setAnchorView(anchor); + mPopup.setDropDownGravity(mDropDownGravity); + } else { + return false; + } + + if (!mHasContentWidth) { + mContentWidth = measureContentWidth(); + mHasContentWidth = true; + } + + mPopup.setContentWidth(mContentWidth); + 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; + } + } + + 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() { + // Menus don't tend to be long, so this is more sane than it looks. + int maxWidth = 0; + View itemView = null; + int itemType = 0; + + final ListAdapter adapter = mAdapter; + 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); + + final int itemWidth = itemView.getMeasuredWidth(); + if (itemWidth >= mPopupMaxWidth) { + return mPopupMaxWidth; + } else if (itemWidth > maxWidth) { + maxWidth = itemWidth; + } + } + + return maxWidth; + } + + @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 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) { + mHasContentWidth = false; + + 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); + 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; + 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; + } + + @Override + public void notifyDataSetChanged() { + findExpandedIndex(); + super.notifyDataSetChanged(); + } + } +} + diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuPresenter.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuPresenter.java new file mode 100644 index 0000000000..2496ed5df7 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuPresenter.java @@ -0,0 +1,153 @@ +/* + * 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 android.support.v7.internal.view.renamemenu; + +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. + * + * @hide + */ +public interface MenuPresenter { + + /** + * Called by menu implementation to notify another component of open/close events. + * + * @hide + */ + 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, MenuBuilder)}. + * + * @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/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuView.java new file mode 100644 index 0000000000..8a4052b03e --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/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 android.support.v7.internal.view.renamemenu; + +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/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuWrapperFactory.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuWrapperFactory.java new file mode 100644 index 0000000000..ae681eb108 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuWrapperFactory.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.os.Build; +import android.support.v4.internal.view.SupportMenu; +import android.support.v4.internal.view.SupportMenuItem; +import android.support.v4.internal.view.SupportSubMenu; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; + +/** + * @hide + */ +public final class MenuWrapperFactory { + private MenuWrapperFactory() { + } + + public static Menu wrapSupportMenu(Context context, SupportMenu supportMenu) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return new MenuWrapperICS(context, supportMenu); + } + throw new UnsupportedOperationException(); + } + + public static MenuItem wrapSupportMenuItem(Context context, SupportMenuItem supportMenuItem) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return new MenuItemWrapperJB(context, supportMenuItem); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return new MenuItemWrapperICS(context, supportMenuItem); + } + throw new UnsupportedOperationException(); + } + + public static SubMenu wrapSupportSubMenu(Context context, SupportSubMenu supportSubMenu) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return new SubMenuWrapperICS(context, supportSubMenu); + } + throw new UnsupportedOperationException(); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuWrapperICS.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuWrapperICS.java new file mode 100644 index 0000000000..863be3e545 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/MenuWrapperICS.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.view.renamemenu; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.support.v4.internal.view.SupportMenu; +import android.support.v4.internal.view.SupportMenuItem; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; + +/** + * Wraps a support {@link SupportMenu} as a framework {@link android.view.Menu} + * @hide + */ +class MenuWrapperICS extends BaseMenuWrapper implements Menu { + + MenuWrapperICS(Context context, SupportMenu object) { + super(context, object); + } + + @Override + public MenuItem add(CharSequence title) { + return getMenuItemWrapper(mWrappedObject.add(title)); + } + + @Override + public MenuItem add(int titleRes) { + return getMenuItemWrapper(mWrappedObject.add(titleRes)); + } + + @Override + public MenuItem add(int groupId, int itemId, int order, CharSequence title) { + return getMenuItemWrapper(mWrappedObject.add(groupId, itemId, order, title)); + } + + @Override + public MenuItem add(int groupId, int itemId, int order, int titleRes) { + return getMenuItemWrapper(mWrappedObject.add(groupId, itemId, order, titleRes)); + } + + @Override + public SubMenu addSubMenu(CharSequence title) { + return getSubMenuWrapper(mWrappedObject.addSubMenu(title)); + } + + @Override + public SubMenu addSubMenu(int titleRes) { + return getSubMenuWrapper(mWrappedObject.addSubMenu(titleRes)); + } + + @Override + public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) { + return getSubMenuWrapper(mWrappedObject.addSubMenu(groupId, itemId, order, title)); + } + + @Override + public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) { + return getSubMenuWrapper( + mWrappedObject.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[] items = null; + if (outSpecificItems != null) { + items = new android.view.MenuItem[outSpecificItems.length]; + } + + int result = mWrappedObject + .addIntentOptions(groupId, itemId, order, caller, specifics, intent, flags, items); + + if (items != null) { + for (int i = 0, z = items.length; i < z; i++) { + outSpecificItems[i] = getMenuItemWrapper(items[i]); + } + } + + return result; + } + + @Override + public void removeItem(int id) { + internalRemoveItem(id); + mWrappedObject.removeItem(id); + } + + @Override + public void removeGroup(int groupId) { + internalRemoveGroup(groupId); + mWrappedObject.removeGroup(groupId); + } + + @Override + public void clear() { + internalClear(); + mWrappedObject.clear(); + } + + @Override + public void setGroupCheckable(int group, boolean checkable, boolean exclusive) { + mWrappedObject.setGroupCheckable(group, checkable, exclusive); + } + + @Override + public void setGroupVisible(int group, boolean visible) { + mWrappedObject.setGroupVisible(group, visible); + } + + @Override + public void setGroupEnabled(int group, boolean enabled) { + mWrappedObject.setGroupEnabled(group, enabled); + } + + @Override + public boolean hasVisibleItems() { + return mWrappedObject.hasVisibleItems(); + } + + @Override + public MenuItem findItem(int id) { + return getMenuItemWrapper(mWrappedObject.findItem(id)); + } + + @Override + public int size() { + return mWrappedObject.size(); + } + + @Override + public MenuItem getItem(int index) { + return getMenuItemWrapper(mWrappedObject.getItem(index)); + } + + @Override + public void close() { + mWrappedObject.close(); + } + + @Override + public boolean performShortcut(int keyCode, KeyEvent event, int flags) { + return mWrappedObject.performShortcut(keyCode, event, flags); + } + + @Override + public boolean isShortcutKey(int keyCode, KeyEvent event) { + return mWrappedObject.isShortcutKey(keyCode, event); + } + + @Override + public boolean performIdentifierAction(int id, int flags) { + return mWrappedObject.performIdentifierAction(id, flags); + } + + @Override + public void setQwertyMode(boolean isQwerty) { + mWrappedObject.setQwertyMode(isQwerty); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/SubMenuBuilder.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/SubMenuBuilder.java new file mode 100644 index 0000000000..cae726330a --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/SubMenuBuilder.java @@ -0,0 +1,141 @@ +/* + * 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; + +/** + * The model for a sub menu, which is an extension of the menu. Most methods are proxied to the + * parent menu. + * + * @hide + */ +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) { + super.setHeaderIconInt(icon); + return this; + } + + public SubMenu setHeaderIcon(int iconRes) { + super.setHeaderIconInt(ContextCompat.getDrawable(getContext(), iconRes)); + return this; + } + + public SubMenu setHeaderTitle(CharSequence title) { + super.setHeaderTitleInt(title); + return this; + } + + public SubMenu setHeaderTitle(int titleRes) { + super.setHeaderTitleInt(getContext().getResources().getString(titleRes)); + return this; + } + + public SubMenu setHeaderView(View view) { + super.setHeaderViewInt(view); + return this; + } + + @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/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/SubMenuWrapperICS.java b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/SubMenuWrapperICS.java new file mode 100644 index 0000000000..cdf3451984 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/view/renamemenu/SubMenuWrapperICS.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.view.renamemenu; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v4.internal.view.SupportSubMenu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; + +/** + * Wraps a support {@link SupportSubMenu} as a framework {@link android.view.SubMenu} + * @hide + */ +class SubMenuWrapperICS extends MenuWrapperICS implements SubMenu { + + SubMenuWrapperICS(Context context, SupportSubMenu subMenu) { + super(context, subMenu); + } + + @Override + public SupportSubMenu getWrappedObject() { + return (SupportSubMenu) mWrappedObject; + } + + @Override + public SubMenu setHeaderTitle(int titleRes) { + getWrappedObject().setHeaderTitle(titleRes); + return this; + } + + @Override + public SubMenu setHeaderTitle(CharSequence title) { + getWrappedObject().setHeaderTitle(title); + return this; + } + + @Override + public SubMenu setHeaderIcon(int iconRes) { + getWrappedObject().setHeaderIcon(iconRes); + return this; + } + + @Override + public SubMenu setHeaderIcon(Drawable icon) { + getWrappedObject().setHeaderIcon(icon); + return this; + } + + @Override + public SubMenu setHeaderView(View view) { + getWrappedObject().setHeaderView(view); + return this; + } + + @Override + public void clearHeader() { + getWrappedObject().clearHeader(); + } + + @Override + public SubMenu setIcon(int iconRes) { + getWrappedObject().setIcon(iconRes); + return this; + } + + @Override + public SubMenu setIcon(Drawable icon) { + getWrappedObject().setIcon(icon); + return this; + } + + @Override + public MenuItem getItem() { + return getMenuItemWrapper(getWrappedObject().getItem()); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AbsActionBarView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AbsActionBarView.java new file mode 100644 index 0000000000..0d06065ec7 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AbsActionBarView.java @@ -0,0 +1,288 @@ +/* + * 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.os.Build; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.support.v4.view.ViewPropertyAnimatorListener; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.ViewPropertyAnimatorCompatSet; +import android.support.v7.widget.ActionMenuPresenter; +import android.support.v7.widget.ActionMenuView; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +abstract class AbsActionBarView extends ViewGroup { + private static final Interpolator sAlphaInterpolator = new DecelerateInterpolator(); + + private static final int FADE_DURATION = 200; + + protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); + + /** Context against which to inflate popup menus. */ + protected final Context mPopupContext; + + protected ActionMenuView mMenuView; + protected ActionMenuPresenter mActionMenuPresenter; + protected ViewGroup mSplitView; + protected boolean mSplitActionBar; + protected boolean mSplitWhenNarrow; + protected int mContentHeight; + + protected ViewPropertyAnimatorCompat mVisibilityAnim; + + AbsActionBarView(Context context) { + this(context, null); + } + + AbsActionBarView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + AbsActionBarView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final TypedValue tv = new TypedValue(); + if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true) + && tv.resourceId != 0) { + mPopupContext = new ContextThemeWrapper(context, tv.resourceId); + } else { + mPopupContext = context; + } + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + if (Build.VERSION.SDK_INT >= 8) { + 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.ActionBar, + R.attr.actionBarStyle, 0); + setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0)); + a.recycle(); + + 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 setSplitToolbar(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(ViewGroup 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) { + ViewCompat.setAlpha(this, 0f); + if (mSplitView != null && mMenuView != null) { + ViewCompat.setAlpha(mMenuView, 0f); + } + } + ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(1f); + anim.setDuration(FADE_DURATION); + anim.setInterpolator(sAlphaInterpolator); + if (mSplitView != null && mMenuView != null) { + ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet(); + ViewPropertyAnimatorCompat splitAnim = ViewCompat.animate(mMenuView).alpha(1f); + splitAnim.setDuration(FADE_DURATION); + set.setListener(mVisAnimListener.withFinalVisibility(anim, visibility)); + set.play(anim).play(splitAnim); + set.start(); + } else { + anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility)); + anim.start(); + } + } else { + ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(0f); + anim.setDuration(FADE_DURATION); + anim.setInterpolator(sAlphaInterpolator); + if (mSplitView != null && mMenuView != null) { + ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet(); + ViewPropertyAnimatorCompat splitAnim = ViewCompat.animate(mMenuView).alpha(0f); + splitAnim.setDuration(FADE_DURATION); + set.setListener(mVisAnimListener.withFinalVisibility(anim, visibility)); + set.play(anim).play(splitAnim); + set.start(); + } else { + anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility)); + anim.start(); + } + } + } + + 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 isOverflowMenuShowPending() { + if (mActionMenuPresenter != null) { + return mActionMenuPresenter.isOverflowMenuShowPending(); + } + return false; + } + + public boolean isOverflowReserved() { + return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved(); + } + + public boolean canShowOverflowMenu() { + return isOverflowReserved() && getVisibility() == VISIBLE; + } + + 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); + } + + static protected int next(int x, int val, boolean isRtl) { + return isRtl ? x - val : x + val; + } + + protected int positionChild(View child, int x, int y, int contentHeight, boolean reverse) { + int childWidth = child.getMeasuredWidth(); + int childHeight = child.getMeasuredHeight(); + int childTop = y + (contentHeight - childHeight) / 2; + + if (reverse) { + child.layout(x - childWidth, childTop, x, childTop + childHeight); + } else { + child.layout(x, childTop, x + childWidth, childTop + childHeight); + } + + return (reverse ? -childWidth : childWidth); + } + + protected class VisibilityAnimListener implements ViewPropertyAnimatorListener { + private boolean mCanceled = false; + int mFinalVisibility; + + public VisibilityAnimListener withFinalVisibility(ViewPropertyAnimatorCompat animation, + int visibility) { + mVisibilityAnim = animation; + mFinalVisibility = visibility; + return this; + } + + @Override + public void onAnimationStart(View view) { + setVisibility(VISIBLE); + mCanceled = false; + } + + @Override + public void onAnimationEnd(View view) { + if (mCanceled) return; + + mVisibilityAnim = null; + setVisibility(mFinalVisibility); + if (mSplitView != null && mMenuView != null) { + mMenuView.setVisibility(mFinalVisibility); + } + } + + @Override + public void onAnimationCancel(View view) { + mCanceled = true; + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AbsSpinnerCompat.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AbsSpinnerCompat.java new file mode 100644 index 0000000000..0ec8e7a7b8 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AbsSpinnerCompat.java @@ -0,0 +1,451 @@ +/* + * 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.view.ViewCompat; +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. + */ +abstract class AbsSpinnerCompat extends AdapterViewCompat { + SpinnerAdapter mAdapter; + + int mHeightMeasureSpec; + int mWidthMeasureSpec; + + 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; + + AbsSpinnerCompat(Context context) { + super(context); + initAbsSpinner(); + } + + AbsSpinnerCompat(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + AbsSpinnerCompat(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initAbsSpinner(); + } + + /** + * 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 paddingLeft = getPaddingLeft(); + final int paddingTop = getPaddingTop(); + final int paddingRight = getPaddingRight(); + final int paddingBottom = getPaddingBottom(); + + mSpinnerPadding.left = paddingLeft > mSelectionLeftPadding ? paddingLeft + : mSelectionLeftPadding; + mSpinnerPadding.top = paddingTop > mSelectionTopPadding ? paddingTop + : mSelectionTopPadding; + mSpinnerPadding.right = paddingRight > mSelectionRightPadding ? paddingRight + : mSelectionRightPadding; + mSpinnerPadding.bottom = paddingBottom > mSelectionBottomPadding ? paddingBottom + : 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.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()); + + heightSize = ViewCompat.resolveSizeAndState(preferredHeight, heightMeasureSpec, 0); + widthSize = ViewCompat.resolveSizeAndState(preferredWidth, widthMeasureSpec, 0); + + 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 AbsSpinnerCompat.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 AbsSpinnerCompat#onSaveInstanceState()} + */ + SavedState(Parcelable superState) { + super(superState); + } + + /** + * Constructor called from {@link #CREATOR} + */ + 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(); + } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawable.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawable.java new file mode 100644 index 0000000000..6248f798be --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawable.java @@ -0,0 +1,44 @@ +package android.support.v7.internal.widget; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.drawable.Drawable; + +class ActionBarBackgroundDrawable extends Drawable { + + final ActionBarContainer mContainer; + + public ActionBarBackgroundDrawable(ActionBarContainer container) { + mContainer = container; + } + + @Override + public void draw(Canvas canvas) { + if (mContainer.mIsSplit) { + if (mContainer.mSplitBackground != null) { + mContainer.mSplitBackground.draw(canvas); + } + } else { + if (mContainer.mBackground != null) { + mContainer.mBackground.draw(canvas); + } + if (mContainer.mStackedBackground != null && mContainer.mIsStacked) { + mContainer.mStackedBackground.draw(canvas); + } + } + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getOpacity() { + return 0; + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawableV21.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawableV21.java new file mode 100644 index 0000000000..19cd5a1998 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawableV21.java @@ -0,0 +1,26 @@ +package android.support.v7.internal.widget; + +import android.graphics.Outline; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; + +class ActionBarBackgroundDrawableV21 extends ActionBarBackgroundDrawable { + + public ActionBarBackgroundDrawableV21(ActionBarContainer container) { + super(container); + } + + @Override + public void getOutline(@NonNull Outline outline) { + if (mContainer.mIsSplit) { + if (mContainer.mSplitBackground != null) { + mContainer.mSplitBackground.getOutline(outline); + } + } else { + // ignore the stacked background for shadow casting + if (mContainer.mBackground != null) { + mContainer.mBackground.getOutline(outline); + } + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarContainer.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarContainer.java new file mode 100644 index 0000000000..239fe4ce93 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarContainer.java @@ -0,0 +1,326 @@ +/* + * 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v7.appcompat.R; +import android.support.v7.internal.VersionUtils; +import android.support.v7.view.ActionMode; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +/** + * 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 FrameLayout { + private boolean mIsTransitioning; + private View mTabContainer; + private View mActionBarView; + private View mContextView; + + Drawable mBackground; + Drawable mStackedBackground; + Drawable mSplitBackground; + boolean mIsSplit; + boolean mIsStacked; + private int mHeight; + + public ActionBarContainer(Context context) { + this(context, null); + } + + public ActionBarContainer(Context context, AttributeSet attrs) { + super(context, attrs); + + // Set a transparent background so that we project appropriately. + final Drawable bg = VersionUtils.isAtLeastL() + ? new ActionBarBackgroundDrawableV21(this) + : new ActionBarBackgroundDrawable(this); + setBackgroundDrawable(bg); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.ActionBar); + mBackground = a.getDrawable(R.styleable.ActionBar_background); + mStackedBackground = a.getDrawable( + R.styleable.ActionBar_backgroundStacked); + mHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, -1); + + if (getId() == R.id.split_action_bar) { + mIsSplit = true; + mSplitBackground = a.getDrawable(R.styleable.ActionBar_backgroundSplit); + } + a.recycle(); + + setWillNotDraw(mIsSplit ? mSplitBackground == null : + mBackground == null && mStackedBackground == null); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + mActionBarView = findViewById(R.id.action_bar); + mContextView = findViewById(R.id.action_context_bar); + } + + public void setPrimaryBackground(Drawable bg) { + if (mBackground != null) { + mBackground.setCallback(null); + unscheduleDrawable(mBackground); + } + mBackground = bg; + if (bg != null) { + bg.setCallback(this); + if (mActionBarView != null) { + mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), + mActionBarView.getRight(), mActionBarView.getBottom()); + } + } + setWillNotDraw(mIsSplit ? mSplitBackground == null : + mBackground == null && mStackedBackground == null); + invalidate(); + } + + public void setStackedBackground(Drawable bg) { + if (mStackedBackground != null) { + mStackedBackground.setCallback(null); + unscheduleDrawable(mStackedBackground); + } + mStackedBackground = bg; + if (bg != null) { + bg.setCallback(this); + if ((mIsStacked && mStackedBackground != null)) { + mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(), + mTabContainer.getRight(), mTabContainer.getBottom()); + } + } + setWillNotDraw(mIsSplit ? mSplitBackground == null : + mBackground == null && mStackedBackground == null); + invalidate(); + } + + public void setSplitBackground(Drawable bg) { + if (mSplitBackground != null) { + mSplitBackground.setCallback(null); + unscheduleDrawable(mSplitBackground); + } + mSplitBackground = bg; + if (bg != null) { + bg.setCallback(this); + if (mIsSplit && mSplitBackground != null) { + mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); + } + } + setWillNotDraw(mIsSplit ? mSplitBackground == null : + mBackground == null && mStackedBackground == null); + invalidate(); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + final boolean isVisible = visibility == VISIBLE; + if (mBackground != null) mBackground.setVisible(isVisible, false); + if (mStackedBackground != null) mStackedBackground.setVisible(isVisible, false); + if (mSplitBackground != null) mSplitBackground.setVisible(isVisible, false); + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return (who == mBackground && !mIsSplit) || (who == mStackedBackground && mIsStacked) || + (who == mSplitBackground && mIsSplit) || super.verifyDrawable(who); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + if (mBackground != null && mBackground.isStateful()) { + mBackground.setState(getDrawableState()); + } + if (mStackedBackground != null && mStackedBackground.isStateful()) { + mStackedBackground.setState(getDrawableState()); + } + if (mSplitBackground != null && mSplitBackground.isStateful()) { + mSplitBackground.setState(getDrawableState()); + } + } + + public void jumpDrawablesToCurrentState() { + if (Build.VERSION.SDK_INT >= 11) { + super.jumpDrawablesToCurrentState(); + if (mBackground != null) { + mBackground.jumpToCurrentState(); + } + if (mStackedBackground != null) { + mStackedBackground.jumpToCurrentState(); + } + if (mSplitBackground != null) { + mSplitBackground.jumpToCurrentState(); + } + } + } + + /** + * 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; + } + + 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 ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { + // No starting an action mode for an action bar child! (Where would it go?) + return null; + } + + @Override + public android.view.ActionMode startActionModeForChild(View originalView, + android.view.ActionMode.Callback callback) { + return null; + } + + private boolean isCollapsed(View view) { + return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0; + } + + private int getMeasuredHeightWithMargins(View view) { + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mActionBarView == null && + MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) { + heightMeasureSpec = MeasureSpec.makeMeasureSpec( + Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + if (mActionBarView == null) return; + + final int mode = MeasureSpec.getMode(heightMeasureSpec); + if (mTabContainer != null && mTabContainer.getVisibility() != GONE + && mode != MeasureSpec.EXACTLY) { + final int topMarginForTabs; + if (!isCollapsed(mActionBarView)) { + topMarginForTabs = getMeasuredHeightWithMargins(mActionBarView); + } else if (!isCollapsed(mContextView)) { + topMarginForTabs = getMeasuredHeightWithMargins(mContextView); + } else { + topMarginForTabs = 0; + } + final int maxHeight = mode == MeasureSpec.AT_MOST ? + MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE; + setMeasuredDimension(getMeasuredWidth(), + Math.min(topMarginForTabs + getMeasuredHeightWithMargins(mTabContainer), + maxHeight)); + } + } + + @Override + public void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + final View tabContainer = mTabContainer; + final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE; + + if (tabContainer != null && tabContainer.getVisibility() != GONE) { + final int containerHeight = getMeasuredHeight(); + final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams(); + final int tabHeight = tabContainer.getMeasuredHeight(); + tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r, + containerHeight - lp.bottomMargin); + } + + boolean needsInvalidate = false; + if (mIsSplit) { + if (mSplitBackground != null) { + mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); + needsInvalidate = true; + } + } else { + if (mBackground != null) { + if (mActionBarView.getVisibility() == View.VISIBLE) { + mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), + mActionBarView.getRight(), mActionBarView.getBottom()); + } else if (mContextView != null && + mContextView.getVisibility() == View.VISIBLE) { + mBackground.setBounds(mContextView.getLeft(), mContextView.getTop(), + mContextView.getRight(), mContextView.getBottom()); + } else { + mBackground.setBounds(0, 0, 0, 0); + } + needsInvalidate = true; + } + mIsStacked = hasTabs; + if (hasTabs && mStackedBackground != null) { + mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(), + tabContainer.getRight(), tabContainer.getBottom()); + needsInvalidate = true; + } + } + + if (needsInvalidate) { + invalidate(); + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarContextView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarContextView.java new file mode 100644 index 0000000000..75a623d4a9 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarContextView.java @@ -0,0 +1,546 @@ +/* + * 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.support.v4.view.ViewPropertyAnimatorListener; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.ViewPropertyAnimatorCompatSet; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.ActionMenuPresenter; +import android.support.v7.widget.ActionMenuView; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +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; + +/** + * @hide + */ +public class ActionBarContextView extends AbsActionBarView implements ViewPropertyAnimatorListener { + private static final String TAG = "ActionBarContextView"; + + private CharSequence mTitle; + private CharSequence mSubtitle; + + private View mClose; + private View mCustomView; + private LinearLayout mTitleLayout; + private TextView mTitleView; + private TextView mSubtitleView; + private int mTitleStyleRes; + private int mSubtitleStyleRes; + private Drawable mSplitBackground; + private boolean mTitleOptional; + private int mCloseItemLayout; + + private ViewPropertyAnimatorCompatSet 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); + + final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, + R.styleable.ActionMode, defStyle, 0); + setBackgroundDrawable(a.getDrawable( + R.styleable.ActionMode_background)); + mTitleStyleRes = a.getResourceId( + R.styleable.ActionMode_titleTextStyle, 0); + mSubtitleStyleRes = a.getResourceId( + R.styleable.ActionMode_subtitleTextStyle, 0); + + mContentHeight = a.getLayoutDimension( + R.styleable.ActionMode_height, 0); + + mSplitBackground = a.getDrawable( + R.styleable.ActionMode_backgroundSplit); + + mCloseItemLayout = a.getResourceId( + R.styleable.ActionMode_closeItemLayout, + R.layout.abc_action_mode_close_item_material); + + a.recycle(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mActionMenuPresenter != null) { + mActionMenuPresenter.hideOverflowMenu(); + mActionMenuPresenter.hideSubMenus(); + } + } + + @Override + public void setSplitToolbar(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.setSplitToolbar(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.abc_action_bar_title_item, this); + mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1); + mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title); + mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle); + if (mTitleStyleRes != 0) { + mTitleView.setTextAppearance(getContext(), mTitleStyleRes); + } + if (mSubtitleStyleRes != 0) { + mSubtitleView.setTextAppearance(getContext(), 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(getContext()); + mClose = inflater.inflate(mCloseItemLayout, this, false); + addView(mClose); + } else if (mClose.getParent() == null) { + addView(mClose); + } + + View closeButton = mClose.findViewById(R.id.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(getContext()); + mActionMenuPresenter.setReserveOverflow(true); + + final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.MATCH_PARENT); + if (!mSplitActionBar) { + menu.addMenuPresenter(mActionMenuPresenter, mPopupContext); + 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, mPopupContext); + 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 ViewPropertyAnimatorCompatSet a = mCurrentAnimation; + if (a != null) { + mCurrentAnimation = null; + a.cancel(); + } + } + + 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) { + if (mTitleOptional) { + final int titleWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + mTitleLayout.measure(titleWidthSpec, childSpecHeight); + final int titleWidth = mTitleLayout.getMeasuredWidth(); + final boolean titleFits = titleWidth <= availableWidth; + if (titleFits) { + availableWidth -= titleWidth; + } + mTitleLayout.setVisibility(titleFits ? VISIBLE : GONE); + } else { + 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 ViewPropertyAnimatorCompatSet makeInAnimation() { + ViewCompat.setTranslationX(mClose, -mClose.getWidth() - + ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin); + ViewPropertyAnimatorCompat buttonAnimator = ViewCompat.animate(mClose).translationX(0); + buttonAnimator.setDuration(200); + buttonAnimator.setListener(this); + buttonAnimator.setInterpolator(new DecelerateInterpolator()); + + ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet(); + 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++) { + View child = mMenuView.getChildAt(i); + ViewCompat.setScaleY(child, 0); + ViewPropertyAnimatorCompat a = ViewCompat.animate(child).scaleY(1); + a.setDuration(300); + set.play(a); + } + } + } + + return set; + } + + private ViewPropertyAnimatorCompatSet makeOutAnimation() { + ViewPropertyAnimatorCompat buttonAnimator = ViewCompat.animate(mClose) + .translationX(-mClose.getWidth() - + ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin); + buttonAnimator.setDuration(200); + buttonAnimator.setListener(this); + buttonAnimator.setInterpolator(new DecelerateInterpolator()); + + ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet(); + set.play(buttonAnimator); + + if (mMenuView != null) { + final int count = mMenuView.getChildCount(); + if (count > 0) { + for (int i = 0; i < 0; i++) { + View child = mMenuView.getChildAt(i); + ViewCompat.setScaleY(child, 1); + ViewPropertyAnimatorCompat a = ViewCompat.animate(child).scaleY(0); + a.setDuration(300); + set.play(a); + } + } + } + + return set; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this); + int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft(); + final int y = getPaddingTop(); + final int contentHeight = b - t - getPaddingTop() - getPaddingBottom(); + + if (mClose != null && mClose.getVisibility() != GONE) { + MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams(); + final int startMargin = (isLayoutRtl ? lp.rightMargin : lp.leftMargin); + final int endMargin = (isLayoutRtl ? lp.leftMargin : lp.rightMargin); + x = next(x, startMargin, isLayoutRtl); + x += positionChild(mClose, x, y, contentHeight, isLayoutRtl); + x = next(x, endMargin, isLayoutRtl); + + if (mAnimateInOnLayout) { + mAnimationMode = ANIMATE_IN; + mCurrentAnimation = makeInAnimation(); + mCurrentAnimation.start(); + mAnimateInOnLayout = false; + } + } + + if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) { + x += positionChild(mTitleLayout, x, y, contentHeight, isLayoutRtl); + } + + if (mCustomView != null) { + x += positionChild(mCustomView, x, y, contentHeight, isLayoutRtl); + } + + x = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight(); + + if (mMenuView != null) { + x += positionChild(mMenuView, x, y, contentHeight, !isLayoutRtl); + } + } + + @Override + public void onAnimationStart(View view) { + } + + @Override + public void onAnimationEnd(View view) { + if (mAnimationMode == ANIMATE_OUT) { + killMode(); + } + mAnimationMode = ANIMATE_IDLE; + } + + @Override + public void onAnimationCancel(View view) { + } + + @Override + public boolean shouldDelayChildPressedState() { + return false; + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + if (Build.VERSION.SDK_INT >= 14) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + // Action mode started + event.setSource(this); + event.setClassName(getClass().getName()); + event.setPackageName(getContext().getPackageName()); + event.setContentDescription(mTitle); + } else { + super.onInitializeAccessibilityEvent(event); + } + } + } + + public void setTitleOptional(boolean titleOptional) { + if (titleOptional != mTitleOptional) { + requestLayout(); + } + mTitleOptional = titleOptional; + } + + public boolean isTitleOptional() { + return mTitleOptional; + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java new file mode 100644 index 0000000000..0a6b33fa7b --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java @@ -0,0 +1,822 @@ +/* + * Copyright (C) 2012 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcelable; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.support.v4.view.ViewPropertyAnimatorListener; +import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; +import android.support.v4.widget.ScrollerCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.VersionUtils; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.support.v7.widget.Toolbar; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; + +/** + * Special layout for the containing of an overlay action bar (and its content) to correctly handle + * fitting system windows when the content has request that its layout ignore them. + * + * @hide + */ +public class ActionBarOverlayLayout extends ViewGroup implements DecorContentParent { + private static final String TAG = "ActionBarOverlayLayout"; + + private int mActionBarHeight; + //private WindowDecorActionBar mActionBar; + private int mWindowVisibility = View.VISIBLE; + + // The main UI elements that we handle the layout of. + private ContentFrameLayout mContent; + private ActionBarContainer mActionBarBottom; + private ActionBarContainer mActionBarTop; + + // Some interior UI elements. + private DecorToolbar mDecorToolbar; + + // Content overlay drawable - generally the action bar's shadow + private Drawable mWindowContentOverlay; + private boolean mIgnoreWindowContentOverlay; + + private boolean mOverlayMode; + private boolean mHasNonEmbeddedTabs; + private boolean mHideOnContentScroll; + private boolean mAnimatingForFling; + private int mHideOnContentScrollReference; + private int mLastSystemUiVisibility; + private final Rect mBaseContentInsets = new Rect(); + private final Rect mLastBaseContentInsets = new Rect(); + private final Rect mContentInsets = new Rect(); + private final Rect mBaseInnerInsets = new Rect(); + private final Rect mInnerInsets = new Rect(); + private final Rect mLastInnerInsets = new Rect(); + + private ActionBarVisibilityCallback mActionBarVisibilityCallback; + + private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms + + private ScrollerCompat mFlingEstimator; + + private ViewPropertyAnimatorCompat mCurrentActionBarTopAnimator; + private ViewPropertyAnimatorCompat mCurrentActionBarBottomAnimator; + + private final ViewPropertyAnimatorListener mTopAnimatorListener + = new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) { + mCurrentActionBarTopAnimator = null; + mAnimatingForFling = false; + } + + @Override + public void onAnimationCancel(View view) { + mCurrentActionBarTopAnimator = null; + mAnimatingForFling = false; + } + }; + + private final ViewPropertyAnimatorListener mBottomAnimatorListener = + new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) { + mCurrentActionBarBottomAnimator = null; + mAnimatingForFling = false; + } + + @Override + public void onAnimationCancel(View view) { + mCurrentActionBarBottomAnimator = null; + mAnimatingForFling = false; + } + }; + + private final Runnable mRemoveActionBarHideOffset = new Runnable() { + public void run() { + haltActionBarHideOffsetAnimations(); + mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop).translationY(0) + .setListener(mTopAnimatorListener); + if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { + mCurrentActionBarBottomAnimator = ViewCompat.animate(mActionBarBottom).translationY(0) + .setListener(mBottomAnimatorListener); + } + } + }; + + private final Runnable mAddActionBarHideOffset = new Runnable() { + public void run() { + haltActionBarHideOffsetAnimations(); + mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop) + .translationY(-mActionBarTop.getHeight()) + .setListener(mTopAnimatorListener); + if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { + mCurrentActionBarBottomAnimator = ViewCompat.animate(mActionBarBottom) + .translationY(mActionBarBottom.getHeight()) + .setListener(mBottomAnimatorListener); + } + } + }; + +// public static final Property ACTION_BAR_HIDE_OFFSET = +// new IntProperty("actionBarHideOffset") { +// +// @Override +// public void setValue(ActionBarOverlayLayout object, int value) { +// object.setActionBarHideOffset(value); +// } +// +// @Override +// public Integer get(ActionBarOverlayLayout object) { +// return object.getActionBarHideOffset(); +// } +// }; + + static final int[] ATTRS = new int [] { + R.attr.actionBarSize, + android.R.attr.windowContentOverlay + }; + + public ActionBarOverlayLayout(Context context) { + super(context); + init(context); + } + + public ActionBarOverlayLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + private void init(Context context) { + TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS); + mActionBarHeight = ta.getDimensionPixelSize(0, 0); + mWindowContentOverlay = ta.getDrawable(1); + setWillNotDraw(mWindowContentOverlay == null); + ta.recycle(); + + mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion < + Build.VERSION_CODES.KITKAT; + + mFlingEstimator = ScrollerCompat.create(context); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + haltActionBarHideOffsetAnimations(); + } + + public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) { + mActionBarVisibilityCallback = cb; + if (getWindowToken() != null) { + // This is being initialized after being added to a window; + // make sure to update all state now. + mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility); + if (mLastSystemUiVisibility != 0) { + int newVis = mLastSystemUiVisibility; + onWindowSystemUiVisibilityChanged(newVis); + ViewCompat.requestApplyInsets(this); + } + } + } + + public void setOverlayMode(boolean overlayMode) { + mOverlayMode = overlayMode; + + /* + * Drawing the window content overlay was broken before K so starting to draw it + * again unexpectedly will cause artifacts in some apps. They should fix it. + */ + mIgnoreWindowContentOverlay = overlayMode && + getContext().getApplicationInfo().targetSdkVersion < + Build.VERSION_CODES.KITKAT; + } + + public boolean isInOverlayMode() { + return mOverlayMode; + } + + public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) { + mHasNonEmbeddedTabs = hasNonEmbeddedTabs; + } + + public void setShowingForActionMode(boolean showing) { + // TODO: Add workaround for this +// if (showing) { +// // Here's a fun hack: if the status bar is currently being hidden, +// // and the application has asked for stable content insets, then +// // we will end up with the action mode action bar being shown +// // without the status bar, but moved below where the status bar +// // would be. Not nice. Trying to have this be positioned +// // correctly is not easy (basically we need yet *another* content +// // inset from the window manager to know where to put it), so +// // instead we will just temporarily force the status bar to be shown. +// if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN +// | SYSTEM_UI_FLAG_LAYOUT_STABLE)) +// == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) { +// setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN); +// } +// } else { +// setDisabledSystemUiVisibility(0); +// } + } + + protected void onConfigurationChanged(Configuration newConfig) { + if (Build.VERSION.SDK_INT >= 8) { + super.onConfigurationChanged(newConfig); + } + init(getContext()); + ViewCompat.requestApplyInsets(this); + } + + public void onWindowSystemUiVisibilityChanged(int visible) { + if (Build.VERSION.SDK_INT >= 16) { + super.onWindowSystemUiVisibilityChanged(visible); + } + pullChildren(); + final int diff = mLastSystemUiVisibility ^ visible; + mLastSystemUiVisibility = visible; + final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0; + final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; + if (mActionBarVisibilityCallback != null) { + // We want the bar to be visible if it is not being hidden, + // or the app has not turned on a stable UI mode (meaning they + // are performing explicit layout around the action bar). + mActionBarVisibilityCallback.enableContentAnimations(!stable); + if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem(); + else mActionBarVisibilityCallback.hideForSystem(); + } + if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + if (mActionBarVisibilityCallback != null) { + ViewCompat.requestApplyInsets(this); + } + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mWindowVisibility = visibility; + if (mActionBarVisibilityCallback != null) { + mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility); + } + } + + private boolean applyInsets(View view, Rect insets, boolean left, boolean top, + boolean bottom, boolean right) { + boolean changed = false; + LayoutParams lp = (LayoutParams)view.getLayoutParams(); + if (left && lp.leftMargin != insets.left) { + changed = true; + lp.leftMargin = insets.left; + } + if (top && lp.topMargin != insets.top) { + changed = true; + lp.topMargin = insets.top; + } + if (right && lp.rightMargin != insets.right) { + changed = true; + lp.rightMargin = insets.right; + } + if (bottom && lp.bottomMargin != insets.bottom) { + changed = true; + lp.bottomMargin = insets.bottom; + } + return changed; + } + + @Override + protected boolean fitSystemWindows(Rect insets) { + pullChildren(); + + final int vis = ViewCompat.getWindowSystemUiVisibility(this); + final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; + final Rect systemInsets = insets; + + // Since we're not the top level view in the window decor, we do not need to + // inset the Action Bars + + boolean changed = false; + mBaseInnerInsets.set(systemInsets); + ViewUtils.computeFitSystemWindows(this, mBaseInnerInsets, mBaseContentInsets); + if (!mLastBaseContentInsets.equals(mBaseContentInsets)) { + changed = true; + mLastBaseContentInsets.set(mBaseContentInsets); + } + + if (changed) { + requestLayout(); + } + + // We don't do any more at this point. To correctly compute the content/inner + // insets in all cases, we need to know the measured size of the various action + // bar elements. fitSystemWindows() happens before the measure pass, so we can't + // do that here. Instead we will take this up in onMeasure(). + return true; + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + pullChildren(); + + int maxHeight = 0; + int maxWidth = 0; + int childState = 0; + + int topInset = 0; + int bottomInset = 0; + + measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0); + LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams(); + maxWidth = Math.max(maxWidth, + mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mActionBarTop)); + + // xlarge screen layout doesn't have bottom action bar. + if (mActionBarBottom != null) { + measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0); + lp = (LayoutParams) mActionBarBottom.getLayoutParams(); + maxWidth = Math.max(maxWidth, + mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mActionBarBottom)); + } + + final int vis = ViewCompat.getWindowSystemUiVisibility(this); + final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; + + if (stable) { + // This is the standard space needed for the action bar. For stable measurement, + // we can't depend on the size currently reported by it -- this must remain constant. + topInset = mActionBarHeight; + if (mHasNonEmbeddedTabs) { + final View tabs = mActionBarTop.getTabContainer(); + if (tabs != null) { + // If tabs are not embedded, increase space on top to account for them. + topInset += mActionBarHeight; + } + } + } else if (mActionBarTop.getVisibility() != GONE) { + // This is the space needed on top of the window for all of the action bar + // and tabs. + topInset = mActionBarTop.getMeasuredHeight(); + } + + if (mDecorToolbar.isSplit()) { + // If action bar is split, adjust bottom insets for it. + if (mActionBarBottom != null) { + if (stable) { + bottomInset = mActionBarHeight; + } else { + bottomInset = mActionBarBottom.getMeasuredHeight(); + } + } + } + + // If the window has not requested system UI layout flags, we need to + // make sure its content is not being covered by system UI... though it + // will still be covered by the action bar if they have requested it to + // overlay. + mContentInsets.set(mBaseContentInsets); + mInnerInsets.set(mBaseInnerInsets); + if (!mOverlayMode && !stable) { + mContentInsets.top += topInset; + mContentInsets.bottom += bottomInset; + } else { + mInnerInsets.top += topInset; + mInnerInsets.bottom += bottomInset; + } + applyInsets(mContent, mContentInsets, true, true, true, true); + + if (!mLastInnerInsets.equals(mInnerInsets)) { + // If the inner insets have changed, we need to dispatch this down to + // the app's fitSystemWindows(). We do this before measuring the content + // view to keep the same semantics as the normal fitSystemWindows() call. + mLastInnerInsets.set(mInnerInsets); + + mContent.dispatchFitSystemWindows(mInnerInsets); + } + + measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0); + lp = (LayoutParams) mContent.getLayoutParams(); + maxWidth = Math.max(maxWidth, + mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mContent)); + + // Account for padding too + maxWidth += getPaddingLeft() + getPaddingRight(); + maxHeight += getPaddingTop() + getPaddingBottom(); + + // Check against our minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension( + ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec, + childState << MEASURED_HEIGHT_STATE_SHIFT)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int count = getChildCount(); + + final int parentLeft = getPaddingLeft(); + final int parentRight = right - left - getPaddingRight(); + + final int parentTop = getPaddingTop(); + final int parentBottom = bottom - top - getPaddingBottom(); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + final int width = child.getMeasuredWidth(); + final int height = child.getMeasuredHeight(); + + int childLeft = parentLeft + lp.leftMargin; + int childTop; + if (child == mActionBarBottom) { + childTop = parentBottom - height - lp.bottomMargin; + } else { + childTop = parentTop + lp.topMargin; + } + + child.layout(childLeft, childTop, childLeft + width, childTop + height); + } + } + } + + @Override + public void draw(Canvas c) { + super.draw(c); + if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) { + final int top = mActionBarTop.getVisibility() == VISIBLE ? + (int) (mActionBarTop.getBottom() + ViewCompat.getTranslationY(mActionBarTop) + 0.5f) + : 0; + mWindowContentOverlay.setBounds(0, top, getWidth(), + top + mWindowContentOverlay.getIntrinsicHeight()); + mWindowContentOverlay.draw(c); + } + } + + @Override + public boolean shouldDelayChildPressedState() { + return false; + } + + @Override + public boolean onStartNestedScroll(View child, View target, int axes) { + if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) { + return false; + } + return mHideOnContentScroll; + } + + @Override + public void onNestedScrollAccepted(View child, View target, int axes) { + super.onNestedScrollAccepted(child, target, axes); + mHideOnContentScrollReference = getActionBarHideOffset(); + haltActionBarHideOffsetAnimations(); + if (mActionBarVisibilityCallback != null) { + mActionBarVisibilityCallback.onContentScrollStarted(); + } + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { + mHideOnContentScrollReference += dyConsumed; + setActionBarHideOffset(mHideOnContentScrollReference); + } + + @Override + public void onStopNestedScroll(View target) { + super.onStopNestedScroll(target); + if (mHideOnContentScroll && !mAnimatingForFling) { + if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) { + postRemoveActionBarHideOffset(); + } else { + postAddActionBarHideOffset(); + } + } + if (mActionBarVisibilityCallback != null) { + mActionBarVisibilityCallback.onContentScrollStopped(); + } + } + + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + if (!mHideOnContentScroll || !consumed) { + return false; + } + if (shouldHideActionBarOnFling(velocityX, velocityY)) { + addActionBarHideOffset(); + } else { + removeActionBarHideOffset(); + } + mAnimatingForFling = true; + return true; + } + + void pullChildren() { + if (mContent == null) { + mContent = (ContentFrameLayout) findViewById(R.id.action_bar_activity_content); + mActionBarTop = (ActionBarContainer) findViewById(R.id.action_bar_container); + mDecorToolbar = getDecorToolbar(findViewById(R.id.action_bar)); + mActionBarBottom = (ActionBarContainer) findViewById(R.id.split_action_bar); + } + } + + private DecorToolbar getDecorToolbar(View view) { + if (view instanceof DecorToolbar) { + return (DecorToolbar) view; + } else if (view instanceof Toolbar) { + return ((Toolbar) view).getWrapper(); + } else { + throw new IllegalStateException("Can't make a decor toolbar out of " + + view.getClass().getSimpleName()); + } + } + + public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { + if (hideOnContentScroll != mHideOnContentScroll) { + mHideOnContentScroll = hideOnContentScroll; + if (!hideOnContentScroll) { + if (VersionUtils.isAtLeastL()) { + stopNestedScroll(); + } + haltActionBarHideOffsetAnimations(); + setActionBarHideOffset(0); + } + } + } + + public boolean isHideOnContentScrollEnabled() { + return mHideOnContentScroll; + } + + public int getActionBarHideOffset() { + return mActionBarTop != null ? -((int) ViewCompat.getTranslationY(mActionBarTop)) : 0; + } + + public void setActionBarHideOffset(int offset) { + haltActionBarHideOffsetAnimations(); + final int topHeight = mActionBarTop.getHeight(); + offset = Math.max(0, Math.min(offset, topHeight)); + ViewCompat.setTranslationY(mActionBarTop, -offset); + if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { + // Match the hide offset proportionally for a split bar + final float fOffset = (float) offset / topHeight; + final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset); + ViewCompat.setTranslationY(mActionBarBottom, bOffset); + } + } + + private void haltActionBarHideOffsetAnimations() { + removeCallbacks(mRemoveActionBarHideOffset); + removeCallbacks(mAddActionBarHideOffset); + if (mCurrentActionBarTopAnimator != null) { + mCurrentActionBarTopAnimator.cancel(); + } + if (mCurrentActionBarBottomAnimator != null) { + mCurrentActionBarBottomAnimator.cancel(); + } + } + + private void postRemoveActionBarHideOffset() { + haltActionBarHideOffsetAnimations(); + postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY); + } + + private void postAddActionBarHideOffset() { + haltActionBarHideOffsetAnimations(); + postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY); + } + + private void removeActionBarHideOffset() { + haltActionBarHideOffsetAnimations(); + mRemoveActionBarHideOffset.run(); + } + + private void addActionBarHideOffset() { + haltActionBarHideOffsetAnimations(); + mAddActionBarHideOffset.run(); + } + + private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) { + mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE); + final int finalY = mFlingEstimator.getFinalY(); + return finalY > mActionBarTop.getHeight(); + } + + @Override + public void setWindowCallback(Window.Callback cb) { + pullChildren(); + mDecorToolbar.setWindowCallback(cb); + } + + @Override + public void setWindowTitle(CharSequence title) { + pullChildren(); + mDecorToolbar.setWindowTitle(title); + } + + @Override + public CharSequence getTitle() { + pullChildren(); + return mDecorToolbar.getTitle(); + } + + @Override + public void initFeature(int windowFeature) { + pullChildren(); + switch (windowFeature) { + case Window.FEATURE_PROGRESS: + mDecorToolbar.initProgress(); + break; + case Window.FEATURE_INDETERMINATE_PROGRESS: + mDecorToolbar.initIndeterminateProgress(); + break; + case Window.FEATURE_ACTION_BAR_OVERLAY: + setOverlayMode(true); + break; + } + } + + @Override + public void setUiOptions(int uiOptions) { + // Split Action Bar not included. + } + + @Override + public boolean hasIcon() { + pullChildren(); + return mDecorToolbar.hasIcon(); + } + + @Override + public boolean hasLogo() { + pullChildren(); + return mDecorToolbar.hasLogo(); + } + + @Override + public void setIcon(int resId) { + pullChildren(); + mDecorToolbar.setIcon(resId); + } + + @Override + public void setIcon(Drawable d) { + pullChildren(); + mDecorToolbar.setIcon(d); + } + + @Override + public void setLogo(int resId) { + pullChildren(); + mDecorToolbar.setLogo(resId); + } + + @Override + public boolean canShowOverflowMenu() { + pullChildren(); + return mDecorToolbar.canShowOverflowMenu(); + } + + @Override + public boolean isOverflowMenuShowing() { + pullChildren(); + return mDecorToolbar.isOverflowMenuShowing(); + } + + @Override + public boolean isOverflowMenuShowPending() { + pullChildren(); + return mDecorToolbar.isOverflowMenuShowPending(); + } + + @Override + public boolean showOverflowMenu() { + pullChildren(); + return mDecorToolbar.showOverflowMenu(); + } + + @Override + public boolean hideOverflowMenu() { + pullChildren(); + return mDecorToolbar.hideOverflowMenu(); + } + + @Override + public void setMenuPrepared() { + pullChildren(); + mDecorToolbar.setMenuPrepared(); + } + + @Override + public void setMenu(Menu menu, MenuPresenter.Callback cb) { + pullChildren(); + mDecorToolbar.setMenu(menu, cb); + } + + @Override + public void saveToolbarHierarchyState(SparseArray toolbarStates) { + pullChildren(); + mDecorToolbar.saveHierarchyState(toolbarStates); + } + + @Override + public void restoreToolbarHierarchyState(SparseArray toolbarStates) { + pullChildren(); + mDecorToolbar.restoreHierarchyState(toolbarStates); + } + + @Override + public void dismissPopups() { + pullChildren(); + mDecorToolbar.dismissPopupMenus(); + } + + public static class LayoutParams extends MarginLayoutParams { + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(int width, int height) { + super(width, height); + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + + public LayoutParams(ViewGroup.MarginLayoutParams source) { + super(source); + } + } + + public interface ActionBarVisibilityCallback { + void onWindowVisibilityChanged(int visibility); + void showForSystem(); + void hideForSystem(); + void enableContentAnimations(boolean enable); + void onContentScrollStarted(); + void onContentScrollStopped(); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActivityChooserModel.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActivityChooserModel.java new file mode 100644 index 0000000000..d97f8e7b9a --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActivityChooserModel.java @@ -0,0 +1,1100 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.widget; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.database.DataSetObservable; +import android.os.AsyncTask; +import android.os.Build; +import android.support.v4.os.AsyncTaskCompat; +import android.text.TextUtils; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * This class represents a data model for choosing a component for handing a + * given {@link Intent}. The model is responsible for querying the system for + * activities that can handle the given intent and order found activities + * based on historical data of previous choices. The historical data is stored + * in an application private file. If a client does not want to have persistent + * choice history the file can be omitted, thus the activities will be ordered + * based on historical usage for the current session. + *

+ *

+ * For each backing history file there is a singleton instance of this class. Thus, + * several clients that specify the same history file will share the same model. Note + * that if multiple clients are sharing the same model they should implement semantically + * equivalent functionality since setting the model intent will change the found + * activities and they may be inconsistent with the functionality of some of the clients. + * For example, choosing a share activity can be implemented by a single backing + * model and two different views for performing the selection. If however, one of the + * views is used for sharing but the other for importing, for example, then each + * view should be backed by a separate model. + *

+ *

+ * The way clients interact with this class is as follows: + *

+ *

+ *

+ * 
+ *  // Get a model and set it to a couple of clients with semantically similar function.
+ *  ActivityChooserModel dataModel =
+ *      ActivityChooserModel.get(context, "task_specific_history_file_name.xml");
+ *
+ *  ActivityChooserModelClient modelClient1 = getActivityChooserModelClient1();
+ *  modelClient1.setActivityChooserModel(dataModel);
+ *
+ *  ActivityChooserModelClient modelClient2 = getActivityChooserModelClient2();
+ *  modelClient2.setActivityChooserModel(dataModel);
+ *
+ *  // Set an intent to choose a an activity for.
+ *  dataModel.setIntent(intent);
+ * 
+ * 
+ * 

+ *

+ * Note: This class is thread safe. + *

+ * + * @hide + */ +public class ActivityChooserModel extends DataSetObservable { + + /** + * Client that utilizes an {@link ActivityChooserModel}. + */ + public interface ActivityChooserModelClient { + + /** + * Sets the {@link ActivityChooserModel}. + * + * @param dataModel The model. + */ + public void setActivityChooserModel(ActivityChooserModel dataModel); + } + + /** + * Defines a sorter that is responsible for sorting the activities + * based on the provided historical choices and an intent. + */ + public interface ActivitySorter { + + /** + * Sorts the activities in descending order of relevance + * based on previous history and an intent. + * + * @param intent The {@link Intent}. + * @param activities Activities to be sorted. + * @param historicalRecords Historical records. + */ + // This cannot be done by a simple comparator since an Activity weight + // is computed from history. Note that Activity implements Comparable. + public void sort(Intent intent, List activities, + List historicalRecords); + } + + /** + * Listener for choosing an activity. + */ + public interface OnChooseActivityListener { + + /** + * Called when an activity has been chosen. The client can decide whether + * an activity can be chosen and if so the caller of + * {@link ActivityChooserModel#chooseActivity(int)} will receive and {@link Intent} + * for launching it. + *

+ * Note: Modifying the intent is not permitted and + * any changes to the latter will be ignored. + *

+ * + * @param host The listener's host model. + * @param intent The intent for launching the chosen activity. + * @return Whether the intent is handled and should not be delivered to clients. + * + * @see ActivityChooserModel#chooseActivity(int) + */ + public boolean onChooseActivity(ActivityChooserModel host, Intent intent); + } + + /** + * Flag for selecting debug mode. + */ + private static final boolean DEBUG = false; + + /** + * Tag used for logging. + */ + private static final String LOG_TAG = ActivityChooserModel.class.getSimpleName(); + + /** + * The root tag in the history file. + */ + private static final String TAG_HISTORICAL_RECORDS = "historical-records"; + + /** + * The tag for a record in the history file. + */ + private static final String TAG_HISTORICAL_RECORD = "historical-record"; + + /** + * Attribute for the activity. + */ + private static final String ATTRIBUTE_ACTIVITY = "activity"; + + /** + * Attribute for the choice time. + */ + private static final String ATTRIBUTE_TIME = "time"; + + /** + * Attribute for the choice weight. + */ + private static final String ATTRIBUTE_WEIGHT = "weight"; + + /** + * The default name of the choice history file. + */ + public static final String DEFAULT_HISTORY_FILE_NAME = + "activity_choser_model_history.xml"; + + /** + * The default maximal length of the choice history. + */ + public static final int DEFAULT_HISTORY_MAX_LENGTH = 50; + + /** + * The amount with which to inflate a chosen activity when set as default. + */ + private static final int DEFAULT_ACTIVITY_INFLATION = 5; + + /** + * Default weight for a choice record. + */ + private static final float DEFAULT_HISTORICAL_RECORD_WEIGHT = 1.0f; + + /** + * The extension of the history file. + */ + private static final String HISTORY_FILE_EXTENSION = ".xml"; + + /** + * An invalid item index. + */ + private static final int INVALID_INDEX = -1; + + /** + * Lock to guard the model registry. + */ + private static final Object sRegistryLock = new Object(); + + /** + * This the registry for data models. + */ + private static final Map sDataModelRegistry = + new HashMap(); + + /** + * Lock for synchronizing on this instance. + */ + private final Object mInstanceLock = new Object(); + + /** + * List of activities that can handle the current intent. + */ + private final List mActivities = new ArrayList(); + + /** + * List with historical choice records. + */ + private final List mHistoricalRecords = new ArrayList(); + + /** + * Context for accessing resources. + */ + private final Context mContext; + + /** + * The name of the history file that backs this model. + */ + private final String mHistoryFileName; + + /** + * The intent for which a activity is being chosen. + */ + private Intent mIntent; + + /** + * The sorter for ordering activities based on intent and past choices. + */ + private ActivitySorter mActivitySorter = new DefaultSorter(); + + /** + * The maximal length of the choice history. + */ + private int mHistoryMaxSize = DEFAULT_HISTORY_MAX_LENGTH; + + /** + * Flag whether choice history can be read. In general many clients can + * share the same data model and {@link #readHistoricalDataIfNeeded()} may be called + * by arbitrary of them any number of times. Therefore, this class guarantees + * that the very first read succeeds and subsequent reads can be performed + * only after a call to {@link #persistHistoricalDataIfNeeded()} followed by change + * of the share records. + */ + private boolean mCanReadHistoricalData = true; + + /** + * Flag whether the choice history was read. This is used to enforce that + * before calling {@link #persistHistoricalDataIfNeeded()} a call to + * {@link #persistHistoricalDataIfNeeded()} has been made. This aims to avoid a + * scenario in which a choice history file exits, it is not read yet and + * it is overwritten. Note that always all historical records are read in + * full and the file is rewritten. This is necessary since we need to + * purge old records that are outside of the sliding window of past choices. + */ + private boolean mReadShareHistoryCalled = false; + + /** + * Flag whether the choice records have changed. In general many clients can + * share the same data model and {@link #persistHistoricalDataIfNeeded()} may be called + * by arbitrary of them any number of times. Therefore, this class guarantees + * that choice history will be persisted only if it has changed. + */ + private boolean mHistoricalRecordsChanged = true; + + /** + * Flag whether to reload the activities for the current intent. + */ + private boolean mReloadActivities = false; + + /** + * Policy for controlling how the model handles chosen activities. + */ + private OnChooseActivityListener mActivityChoserModelPolicy; + + /** + * Gets the data model backed by the contents of the provided file with historical data. + * Note that only one data model is backed by a given file, thus multiple calls with + * the same file name will return the same model instance. If no such instance is present + * it is created. + *

+ * Note: To use the default historical data file clients should explicitly + * pass as file name {@link #DEFAULT_HISTORY_FILE_NAME}. If no persistence of the choice + * history is desired clients should pass null for the file name. In such + * case a new model is returned for each invocation. + *

+ * + *

+ * Always use difference historical data files for semantically different actions. + * For example, sharing is different from importing. + *

+ * + * @param context Context for loading resources. + * @param historyFileName File name with choice history, null + * if the model should not be backed by a file. In this case the activities + * will be ordered only by data from the current session. + * + * @return The model. + */ + public static ActivityChooserModel get(Context context, String historyFileName) { + synchronized (sRegistryLock) { + ActivityChooserModel dataModel = sDataModelRegistry.get(historyFileName); + if (dataModel == null) { + dataModel = new ActivityChooserModel(context, historyFileName); + sDataModelRegistry.put(historyFileName, dataModel); + } + return dataModel; + } + } + + /** + * Creates a new instance. + * + * @param context Context for loading resources. + * @param historyFileName The history XML file. + */ + private ActivityChooserModel(Context context, String historyFileName) { + mContext = context.getApplicationContext(); + if (!TextUtils.isEmpty(historyFileName) + && !historyFileName.endsWith(HISTORY_FILE_EXTENSION)) { + mHistoryFileName = historyFileName + HISTORY_FILE_EXTENSION; + } else { + mHistoryFileName = historyFileName; + } + } + + /** + * Sets an intent for which to choose a activity. + *

+ * Note: Clients must set only semantically similar + * intents for each data model. + *

+ * + * @param intent The intent. + */ + public void setIntent(Intent intent) { + synchronized (mInstanceLock) { + if (mIntent == intent) { + return; + } + mIntent = intent; + mReloadActivities = true; + ensureConsistentState(); + } + } + + /** + * Gets the intent for which a activity is being chosen. + * + * @return The intent. + */ + public Intent getIntent() { + synchronized (mInstanceLock) { + return mIntent; + } + } + + /** + * Gets the number of activities that can handle the intent. + * + * @return The activity count. + * + * @see #setIntent(Intent) + */ + public int getActivityCount() { + synchronized (mInstanceLock) { + ensureConsistentState(); + return mActivities.size(); + } + } + + /** + * Gets an activity at a given index. + * + * @return The activity. + * + * @see ActivityResolveInfo + * @see #setIntent(Intent) + */ + public ResolveInfo getActivity(int index) { + synchronized (mInstanceLock) { + ensureConsistentState(); + return mActivities.get(index).resolveInfo; + } + } + + /** + * Gets the index of a the given activity. + * + * @param activity The activity index. + * + * @return The index if found, -1 otherwise. + */ + public int getActivityIndex(ResolveInfo activity) { + synchronized (mInstanceLock) { + ensureConsistentState(); + List activities = mActivities; + final int activityCount = activities.size(); + for (int i = 0; i < activityCount; i++) { + ActivityResolveInfo currentActivity = activities.get(i); + if (currentActivity.resolveInfo == activity) { + return i; + } + } + return INVALID_INDEX; + } + } + + /** + * Chooses a activity to handle the current intent. This will result in + * adding a historical record for that action and construct intent with + * its component name set such that it can be immediately started by the + * client. + *

+ * Note: By calling this method the client guarantees + * that the returned intent will be started. This intent is returned to + * the client solely to let additional customization before the start. + *

+ * + * @return An {@link Intent} for launching the activity or null if the + * policy has consumed the intent or there is not current intent + * set via {@link #setIntent(Intent)}. + * + * @see HistoricalRecord + * @see OnChooseActivityListener + */ + public Intent chooseActivity(int index) { + synchronized (mInstanceLock) { + if (mIntent == null) { + return null; + } + + ensureConsistentState(); + + ActivityResolveInfo chosenActivity = mActivities.get(index); + + ComponentName chosenName = new ComponentName( + chosenActivity.resolveInfo.activityInfo.packageName, + chosenActivity.resolveInfo.activityInfo.name); + + Intent choiceIntent = new Intent(mIntent); + choiceIntent.setComponent(chosenName); + + if (mActivityChoserModelPolicy != null) { + // Do not allow the policy to change the intent. + Intent choiceIntentCopy = new Intent(choiceIntent); + final boolean handled = mActivityChoserModelPolicy.onChooseActivity(this, + choiceIntentCopy); + if (handled) { + return null; + } + } + + HistoricalRecord historicalRecord = new HistoricalRecord(chosenName, + System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT); + addHisoricalRecord(historicalRecord); + + return choiceIntent; + } + } + + /** + * Sets the listener for choosing an activity. + * + * @param listener The listener. + */ + public void setOnChooseActivityListener(OnChooseActivityListener listener) { + synchronized (mInstanceLock) { + mActivityChoserModelPolicy = listener; + } + } + + /** + * Gets the default activity, The default activity is defined as the one + * with highest rank i.e. the first one in the list of activities that can + * handle the intent. + * + * @return The default activity, null id not activities. + * + * @see #getActivity(int) + */ + public ResolveInfo getDefaultActivity() { + synchronized (mInstanceLock) { + ensureConsistentState(); + if (!mActivities.isEmpty()) { + return mActivities.get(0).resolveInfo; + } + } + return null; + } + + /** + * Sets the default activity. The default activity is set by adding a + * historical record with weight high enough that this activity will + * become the highest ranked. Such a strategy guarantees that the default + * will eventually change if not used. Also the weight of the record for + * setting a default is inflated with a constant amount to guarantee that + * it will stay as default for awhile. + * + * @param index The index of the activity to set as default. + */ + public void setDefaultActivity(int index) { + synchronized (mInstanceLock) { + ensureConsistentState(); + + ActivityResolveInfo newDefaultActivity = mActivities.get(index); + ActivityResolveInfo oldDefaultActivity = mActivities.get(0); + + final float weight; + if (oldDefaultActivity != null) { + // Add a record with weight enough to boost the chosen at the top. + weight = oldDefaultActivity.weight - newDefaultActivity.weight + + DEFAULT_ACTIVITY_INFLATION; + } else { + weight = DEFAULT_HISTORICAL_RECORD_WEIGHT; + } + + ComponentName defaultName = new ComponentName( + newDefaultActivity.resolveInfo.activityInfo.packageName, + newDefaultActivity.resolveInfo.activityInfo.name); + HistoricalRecord historicalRecord = new HistoricalRecord(defaultName, + System.currentTimeMillis(), weight); + addHisoricalRecord(historicalRecord); + } + } + + /** + * Persists the history data to the backing file if the latter + * was provided. Calling this method before a call to {@link #readHistoricalDataIfNeeded()} + * throws an exception. Calling this method more than one without choosing an + * activity has not effect. + * + * @throws IllegalStateException If this method is called before a call to + * {@link #readHistoricalDataIfNeeded()}. + */ + private void persistHistoricalDataIfNeeded() { + if (!mReadShareHistoryCalled) { + throw new IllegalStateException("No preceding call to #readHistoricalData"); + } + if (!mHistoricalRecordsChanged) { + return; + } + mHistoricalRecordsChanged = false; + if (!TextUtils.isEmpty(mHistoryFileName)) { + AsyncTaskCompat.executeParallel(new PersistHistoryAsyncTask(), + mHistoricalRecords, mHistoryFileName); + } + } + + /** + * Sets the sorter for ordering activities based on historical data and an intent. + * + * @param activitySorter The sorter. + * + * @see ActivitySorter + */ + public void setActivitySorter(ActivitySorter activitySorter) { + synchronized (mInstanceLock) { + if (mActivitySorter == activitySorter) { + return; + } + mActivitySorter = activitySorter; + if (sortActivitiesIfNeeded()) { + notifyChanged(); + } + } + } + + /** + * Sets the maximal size of the historical data. Defaults to + * {@link #DEFAULT_HISTORY_MAX_LENGTH} + *

+ * Note: Setting this property will immediately + * enforce the specified max history size by dropping enough old + * historical records to enforce the desired size. Thus, any + * records that exceed the history size will be discarded and + * irreversibly lost. + *

+ * + * @param historyMaxSize The max history size. + */ + public void setHistoryMaxSize(int historyMaxSize) { + synchronized (mInstanceLock) { + if (mHistoryMaxSize == historyMaxSize) { + return; + } + mHistoryMaxSize = historyMaxSize; + pruneExcessiveHistoricalRecordsIfNeeded(); + if (sortActivitiesIfNeeded()) { + notifyChanged(); + } + } + } + + /** + * Gets the history max size. + * + * @return The history max size. + */ + public int getHistoryMaxSize() { + synchronized (mInstanceLock) { + return mHistoryMaxSize; + } + } + + /** + * Gets the history size. + * + * @return The history size. + */ + public int getHistorySize() { + synchronized (mInstanceLock) { + ensureConsistentState(); + return mHistoricalRecords.size(); + } + } + + /** + * Ensures the model is in a consistent state which is the + * activities for the current intent have been loaded, the + * most recent history has been read, and the activities + * are sorted. + */ + private void ensureConsistentState() { + boolean stateChanged = loadActivitiesIfNeeded(); + stateChanged |= readHistoricalDataIfNeeded(); + pruneExcessiveHistoricalRecordsIfNeeded(); + if (stateChanged) { + sortActivitiesIfNeeded(); + notifyChanged(); + } + } + + /** + * Sorts the activities if necessary which is if there is a + * sorter, there are some activities to sort, and there is some + * historical data. + * + * @return Whether sorting was performed. + */ + private boolean sortActivitiesIfNeeded() { + if (mActivitySorter != null && mIntent != null + && !mActivities.isEmpty() && !mHistoricalRecords.isEmpty()) { + mActivitySorter.sort(mIntent, mActivities, + Collections.unmodifiableList(mHistoricalRecords)); + return true; + } + return false; + } + + /** + * Loads the activities for the current intent if needed which is + * if they are not already loaded for the current intent. + * + * @return Whether loading was performed. + */ + private boolean loadActivitiesIfNeeded() { + if (mReloadActivities && mIntent != null) { + mReloadActivities = false; + mActivities.clear(); + List resolveInfos = mContext.getPackageManager() + .queryIntentActivities(mIntent, 0); + final int resolveInfoCount = resolveInfos.size(); + for (int i = 0; i < resolveInfoCount; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + mActivities.add(new ActivityResolveInfo(resolveInfo)); + } + return true; + } + return false; + } + + /** + * Reads the historical data if necessary which is it has + * changed, there is a history file, and there is not persist + * in progress. + * + * @return Whether reading was performed. + */ + private boolean readHistoricalDataIfNeeded() { + if (mCanReadHistoricalData && mHistoricalRecordsChanged && + !TextUtils.isEmpty(mHistoryFileName)) { + mCanReadHistoricalData = false; + mReadShareHistoryCalled = true; + readHistoricalDataImpl(); + return true; + } + return false; + } + + /** + * Adds a historical record. + * + * @param historicalRecord The record to add. + * @return True if the record was added. + */ + private boolean addHisoricalRecord(HistoricalRecord historicalRecord) { + final boolean added = mHistoricalRecords.add(historicalRecord); + if (added) { + mHistoricalRecordsChanged = true; + pruneExcessiveHistoricalRecordsIfNeeded(); + persistHistoricalDataIfNeeded(); + sortActivitiesIfNeeded(); + notifyChanged(); + } + return added; + } + + /** + * Prunes older excessive records to guarantee maxHistorySize. + */ + private void pruneExcessiveHistoricalRecordsIfNeeded() { + final int pruneCount = mHistoricalRecords.size() - mHistoryMaxSize; + if (pruneCount <= 0) { + return; + } + mHistoricalRecordsChanged = true; + for (int i = 0; i < pruneCount; i++) { + HistoricalRecord prunedRecord = mHistoricalRecords.remove(0); + if (DEBUG) { + Log.i(LOG_TAG, "Pruned: " + prunedRecord); + } + } + } + + /** + * Represents a record in the history. + */ + public final static class HistoricalRecord { + + /** + * The activity name. + */ + public final ComponentName activity; + + /** + * The choice time. + */ + public final long time; + + /** + * The record weight. + */ + public final float weight; + + /** + * Creates a new instance. + * + * @param activityName The activity component name flattened to string. + * @param time The time the activity was chosen. + * @param weight The weight of the record. + */ + public HistoricalRecord(String activityName, long time, float weight) { + this(ComponentName.unflattenFromString(activityName), time, weight); + } + + /** + * Creates a new instance. + * + * @param activityName The activity name. + * @param time The time the activity was chosen. + * @param weight The weight of the record. + */ + public HistoricalRecord(ComponentName activityName, long time, float weight) { + this.activity = activityName; + this.time = time; + this.weight = weight; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((activity == null) ? 0 : activity.hashCode()); + result = prime * result + (int) (time ^ (time >>> 32)); + result = prime * result + Float.floatToIntBits(weight); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + HistoricalRecord other = (HistoricalRecord) obj; + if (activity == null) { + if (other.activity != null) { + return false; + } + } else if (!activity.equals(other.activity)) { + return false; + } + if (time != other.time) { + return false; + } + if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append("; activity:").append(activity); + builder.append("; time:").append(time); + builder.append("; weight:").append(new BigDecimal(weight)); + builder.append("]"); + return builder.toString(); + } + } + + /** + * Represents an activity. + */ + public final class ActivityResolveInfo implements Comparable { + + /** + * The {@link ResolveInfo} of the activity. + */ + public final ResolveInfo resolveInfo; + + /** + * Weight of the activity. Useful for sorting. + */ + public float weight; + + /** + * Creates a new instance. + * + * @param resolveInfo activity {@link ResolveInfo}. + */ + public ActivityResolveInfo(ResolveInfo resolveInfo) { + this.resolveInfo = resolveInfo; + } + + @Override + public int hashCode() { + return 31 + Float.floatToIntBits(weight); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ActivityResolveInfo other = (ActivityResolveInfo) obj; + if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) { + return false; + } + return true; + } + + public int compareTo(ActivityResolveInfo another) { + return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append("resolveInfo:").append(resolveInfo.toString()); + builder.append("; weight:").append(new BigDecimal(weight)); + builder.append("]"); + return builder.toString(); + } + } + + /** + * Default activity sorter implementation. + */ + private final class DefaultSorter implements ActivitySorter { + private static final float WEIGHT_DECAY_COEFFICIENT = 0.95f; + + private final Map mPackageNameToActivityMap = + new HashMap(); + + public void sort(Intent intent, List activities, + List historicalRecords) { + Map packageNameToActivityMap = + mPackageNameToActivityMap; + packageNameToActivityMap.clear(); + + final int activityCount = activities.size(); + for (int i = 0; i < activityCount; i++) { + ActivityResolveInfo activity = activities.get(i); + activity.weight = 0.0f; + String packageName = activity.resolveInfo.activityInfo.packageName; + packageNameToActivityMap.put(packageName, activity); + } + + final int lastShareIndex = historicalRecords.size() - 1; + float nextRecordWeight = 1; + for (int i = lastShareIndex; i >= 0; i--) { + HistoricalRecord historicalRecord = historicalRecords.get(i); + String packageName = historicalRecord.activity.getPackageName(); + ActivityResolveInfo activity = packageNameToActivityMap.get(packageName); + if (activity != null) { + activity.weight += historicalRecord.weight * nextRecordWeight; + nextRecordWeight = nextRecordWeight * WEIGHT_DECAY_COEFFICIENT; + } + } + + Collections.sort(activities); + + if (DEBUG) { + for (int i = 0; i < activityCount; i++) { + Log.i(LOG_TAG, "Sorted: " + activities.get(i)); + } + } + } + } + + /** + * Command for reading the historical records from a file off the UI thread. + */ + private void readHistoricalDataImpl() { + FileInputStream fis = null; + try { + fis = mContext.openFileInput(mHistoryFileName); + } catch (FileNotFoundException fnfe) { + if (DEBUG) { + Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName); + } + return; + } + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + + int type = XmlPullParser.START_DOCUMENT; + while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { + type = parser.next(); + } + + if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) { + throw new XmlPullParserException("Share records file does not start with " + + TAG_HISTORICAL_RECORDS + " tag."); + } + + List historicalRecords = mHistoricalRecords; + historicalRecords.clear(); + + while (true) { + type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT) { + break; + } + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + String nodeName = parser.getName(); + if (!TAG_HISTORICAL_RECORD.equals(nodeName)) { + throw new XmlPullParserException("Share records file not well-formed."); + } + + String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY); + final long time = + Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME)); + final float weight = + Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT)); + HistoricalRecord readRecord = new HistoricalRecord(activity, time, weight); + historicalRecords.add(readRecord); + + if (DEBUG) { + Log.i(LOG_TAG, "Read " + readRecord.toString()); + } + } + + if (DEBUG) { + Log.i(LOG_TAG, "Read " + historicalRecords.size() + " historical records."); + } + } catch (XmlPullParserException xppe) { + Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, ioe); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException ioe) { + /* ignore */ + } + } + } + } + + /** + * Command for persisting the historical records to a file off the UI thread. + */ + private final class PersistHistoryAsyncTask extends AsyncTask { + + @Override + @SuppressWarnings("unchecked") + public Void doInBackground(Object... args) { + List historicalRecords = (List) args[0]; + String hostoryFileName = (String) args[1]; + + FileOutputStream fos = null; + + try { + fos = mContext.openFileOutput(hostoryFileName, Context.MODE_PRIVATE); + } catch (FileNotFoundException fnfe) { + Log.e(LOG_TAG, "Error writing historical recrod file: " + hostoryFileName, fnfe); + return null; + } + + XmlSerializer serializer = Xml.newSerializer(); + + try { + serializer.setOutput(fos, null); + serializer.startDocument("UTF-8", true); + serializer.startTag(null, TAG_HISTORICAL_RECORDS); + + final int recordCount = historicalRecords.size(); + for (int i = 0; i < recordCount; i++) { + HistoricalRecord record = historicalRecords.remove(0); + serializer.startTag(null, TAG_HISTORICAL_RECORD); + serializer.attribute(null, ATTRIBUTE_ACTIVITY, + record.activity.flattenToString()); + serializer.attribute(null, ATTRIBUTE_TIME, String.valueOf(record.time)); + serializer.attribute(null, ATTRIBUTE_WEIGHT, String.valueOf(record.weight)); + serializer.endTag(null, TAG_HISTORICAL_RECORD); + if (DEBUG) { + Log.i(LOG_TAG, "Wrote " + record.toString()); + } + } + + serializer.endTag(null, TAG_HISTORICAL_RECORDS); + serializer.endDocument(); + + if (DEBUG) { + Log.i(LOG_TAG, "Wrote " + recordCount + " historical records."); + } + } catch (IllegalArgumentException iae) { + Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, iae); + } catch (IllegalStateException ise) { + Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ise); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ioe); + } finally { + mCanReadHistoricalData = true; + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + /* ignore */ + } + } + } + return null; + } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java new file mode 100644 index 0000000000..9931afb758 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java @@ -0,0 +1,843 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.support.v4.view.ActionProvider; +import android.support.v4.view.ViewCompat; +import android.support.v7.appcompat.R; +import android.support.v7.widget.LinearLayoutCompat; +import android.support.v7.widget.ListPopupWindow; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.PopupWindow; +import android.widget.TextView; + +/** + * This class is a view for choosing an activity for handling a given {@link Intent}. + *

+ * The view is composed of two adjacent buttons: + *

    + *
  • + * The left button is an immediate action and allows one click activity choosing. + * Tapping this button immediately executes the intent without requiring any further + * user input. Long press on this button shows a popup for changing the default + * activity. + *
  • + *
  • + * The right button is an overflow action and provides an optimized menu + * of additional activities. Tapping this button shows a popup anchored to this + * view, listing the most frequently used activities. This list is initially + * limited to a small number of items in frequency used order. The last item, + * "Show all..." serves as an affordance to display all available activities. + *
  • + *
+ *

+ * + * @hide + */ +public class ActivityChooserView extends ViewGroup implements + ActivityChooserModel.ActivityChooserModelClient { + + private static final String LOG_TAG = "ActivityChooserView"; + + /** + * An adapter for displaying the activities in an {@link android.widget.AdapterView}. + */ + private final ActivityChooserViewAdapter mAdapter; + + /** + * Implementation of various interfaces to avoid publishing them in the APIs. + */ + private final Callbacks mCallbacks; + + /** + * The content of this view. + */ + private final LinearLayoutCompat mActivityChooserContent; + + /** + * Stores the background drawable to allow hiding and latter showing. + */ + private final Drawable mActivityChooserContentBackground; + + /** + * The expand activities action button; + */ + private final FrameLayout mExpandActivityOverflowButton; + + /** + * The image for the expand activities action button; + */ + private final ImageView mExpandActivityOverflowButtonImage; + + /** + * The default activities action button; + */ + private final FrameLayout mDefaultActivityButton; + + /** + * The image for the default activities action button; + */ + private final ImageView mDefaultActivityButtonImage; + + /** + * The maximal width of the list popup. + */ + private final int mListPopupMaxWidth; + + /** + * The ActionProvider hosting this view, if applicable. + */ + ActionProvider mProvider; + + /** + * Observer for the model data. + */ + private final DataSetObserver mModelDataSetOberver = new DataSetObserver() { + + @Override + public void onChanged() { + super.onChanged(); + mAdapter.notifyDataSetChanged(); + } + @Override + public void onInvalidated() { + super.onInvalidated(); + mAdapter.notifyDataSetInvalidated(); + } + }; + + private final OnGlobalLayoutListener mOnGlobalLayoutListener = new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (isShowingPopup()) { + if (!isShown()) { + getListPopupWindow().dismiss(); + } else { + getListPopupWindow().show(); + if (mProvider != null) { + mProvider.subUiVisibilityChanged(true); + } + } + } + } + }; + + /** + * Popup window for showing the activity overflow list. + */ + private ListPopupWindow mListPopupWindow; + + /** + * Listener for the dismissal of the popup/alert. + */ + private PopupWindow.OnDismissListener mOnDismissListener; + + /** + * Flag whether a default activity currently being selected. + */ + private boolean mIsSelectingDefaultActivity; + + /** + * The count of activities in the popup. + */ + private int mInitialActivityCount = ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT; + + /** + * Flag whether this view is attached to a window. + */ + private boolean mIsAttachedToWindow; + + /** + * String resource for formatting content description of the default target. + */ + private int mDefaultActionButtonContentDescription; + + /** + * Create a new instance. + * + * @param context The application environment. + */ + public ActivityChooserView(Context context) { + this(context, null); + } + + /** + * Create a new instance. + * + * @param context The application environment. + * @param attrs A collection of attributes. + */ + public ActivityChooserView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Create a new instance. + * + * @param context The application environment. + * @param attrs A collection of attributes. + * @param defStyle The default style to apply to this view. + */ + public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray attributesArray = context.obtainStyledAttributes(attrs, + R.styleable.ActivityChooserView, defStyle, 0); + + mInitialActivityCount = attributesArray.getInt( + R.styleable.ActivityChooserView_initialActivityCount, + ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT); + + Drawable expandActivityOverflowButtonDrawable = attributesArray.getDrawable( + R.styleable.ActivityChooserView_expandActivityOverflowButtonDrawable); + + attributesArray.recycle(); + + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.abc_activity_chooser_view, this, true); + + mCallbacks = new Callbacks(); + + mActivityChooserContent = (LinearLayoutCompat) findViewById(R.id.activity_chooser_view_content); + mActivityChooserContentBackground = mActivityChooserContent.getBackground(); + + mDefaultActivityButton = (FrameLayout) findViewById(R.id.default_activity_button); + mDefaultActivityButton.setOnClickListener(mCallbacks); + mDefaultActivityButton.setOnLongClickListener(mCallbacks); + mDefaultActivityButtonImage = (ImageView) mDefaultActivityButton.findViewById(R.id.image); + + final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button); + expandButton.setOnClickListener(mCallbacks); + expandButton.setOnTouchListener(new ListPopupWindow.ForwardingListener(expandButton) { + @Override + public ListPopupWindow getPopup() { + return getListPopupWindow(); + } + + @Override + protected boolean onForwardingStarted() { + showPopup(); + return true; + } + + @Override + protected boolean onForwardingStopped() { + dismissPopup(); + return true; + } + }); + mExpandActivityOverflowButton = expandButton; + mExpandActivityOverflowButtonImage = + (ImageView) expandButton.findViewById(R.id.image); + mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable); + + mAdapter = new ActivityChooserViewAdapter(); + mAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + super.onChanged(); + updateAppearance(); + } + }); + + Resources resources = context.getResources(); + mListPopupMaxWidth = Math.max(resources.getDisplayMetrics().widthPixels / 2, + resources.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth)); + } + + /** + * {@inheritDoc} + */ + public void setActivityChooserModel(ActivityChooserModel dataModel) { + mAdapter.setDataModel(dataModel); + if (isShowingPopup()) { + dismissPopup(); + showPopup(); + } + } + + /** + * Sets the background for the button that expands the activity + * overflow list. + * + * Note: Clients would like to set this drawable + * as a clue about the action the chosen activity will perform. For + * example, if a share activity is to be chosen the drawable should + * give a clue that sharing is to be performed. + * + * @param drawable The drawable. + */ + public void setExpandActivityOverflowButtonDrawable(Drawable drawable) { + mExpandActivityOverflowButtonImage.setImageDrawable(drawable); + } + + /** + * Sets the content description for the button that expands the activity + * overflow list. + * + * description as a clue about the action performed by the button. + * For example, if a share activity is to be chosen the content + * description should be something like "Share with". + * + * @param resourceId The content description resource id. + */ + public void setExpandActivityOverflowButtonContentDescription(int resourceId) { + CharSequence contentDescription = getContext().getString(resourceId); + mExpandActivityOverflowButtonImage.setContentDescription(contentDescription); + } + + /** + * Set the provider hosting this view, if applicable. + * @hide Internal use only + */ + public void setProvider(ActionProvider provider) { + mProvider = provider; + } + + /** + * Shows the popup window with activities. + * + * @return True if the popup was shown, false if already showing. + */ + public boolean showPopup() { + if (isShowingPopup() || !mIsAttachedToWindow) { + return false; + } + mIsSelectingDefaultActivity = false; + showPopupUnchecked(mInitialActivityCount); + return true; + } + + /** + * Shows the popup no matter if it was already showing. + * + * @param maxActivityCount The max number of activities to display. + */ + private void showPopupUnchecked(int maxActivityCount) { + if (mAdapter.getDataModel() == null) { + throw new IllegalStateException("No data model. Did you call #setDataModel?"); + } + + getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener); + + final boolean defaultActivityButtonShown = + mDefaultActivityButton.getVisibility() == VISIBLE; + + final int activityCount = mAdapter.getActivityCount(); + final int maxActivityCountOffset = defaultActivityButtonShown ? 1 : 0; + if (maxActivityCount != ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED + && activityCount > maxActivityCount + maxActivityCountOffset) { + mAdapter.setShowFooterView(true); + mAdapter.setMaxActivityCount(maxActivityCount - 1); + } else { + mAdapter.setShowFooterView(false); + mAdapter.setMaxActivityCount(maxActivityCount); + } + + ListPopupWindow popupWindow = getListPopupWindow(); + if (!popupWindow.isShowing()) { + if (mIsSelectingDefaultActivity || !defaultActivityButtonShown) { + mAdapter.setShowDefaultActivity(true, defaultActivityButtonShown); + } else { + mAdapter.setShowDefaultActivity(false, false); + } + final int contentWidth = Math.min(mAdapter.measureContentWidth(), mListPopupMaxWidth); + popupWindow.setContentWidth(contentWidth); + popupWindow.show(); + if (mProvider != null) { + mProvider.subUiVisibilityChanged(true); + } + popupWindow.getListView().setContentDescription(getContext().getString( + R.string.abc_activitychooserview_choose_application)); + } + } + + /** + * Dismisses the popup window with activities. + * + * @return True if dismissed, false if already dismissed. + */ + public boolean dismissPopup() { + if (isShowingPopup()) { + getListPopupWindow().dismiss(); + ViewTreeObserver viewTreeObserver = getViewTreeObserver(); + if (viewTreeObserver.isAlive()) { + viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener); + } + } + return true; + } + + /** + * Gets whether the popup window with activities is shown. + * + * @return True if the popup is shown. + */ + public boolean isShowingPopup() { + return getListPopupWindow().isShowing(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + ActivityChooserModel dataModel = mAdapter.getDataModel(); + if (dataModel != null) { + dataModel.registerObserver(mModelDataSetOberver); + } + mIsAttachedToWindow = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + ActivityChooserModel dataModel = mAdapter.getDataModel(); + if (dataModel != null) { + dataModel.unregisterObserver(mModelDataSetOberver); + } + ViewTreeObserver viewTreeObserver = getViewTreeObserver(); + if (viewTreeObserver.isAlive()) { + viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener); + } + if (isShowingPopup()) { + dismissPopup(); + } + mIsAttachedToWindow = false; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + View child = mActivityChooserContent; + // If the default action is not visible we want to be as tall as the + // ActionBar so if this widget is used in the latter it will look as + // a normal action button. + if (mDefaultActivityButton.getVisibility() != VISIBLE) { + heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), + MeasureSpec.EXACTLY); + } + measureChild(child, widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight()); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mActivityChooserContent.layout(0, 0, right - left, bottom - top); + if (!isShowingPopup()) { + dismissPopup(); + } + } + + public ActivityChooserModel getDataModel() { + return mAdapter.getDataModel(); + } + + /** + * Sets a listener to receive a callback when the popup is dismissed. + * + * @param listener The listener to be notified. + */ + public void setOnDismissListener(PopupWindow.OnDismissListener listener) { + mOnDismissListener = listener; + } + + /** + * Sets the initial count of items shown in the activities popup + * i.e. the items before the popup is expanded. This is an upper + * bound since it is not guaranteed that such number of intent + * handlers exist. + * + * @param itemCount The initial popup item count. + */ + public void setInitialActivityCount(int itemCount) { + mInitialActivityCount = itemCount; + } + + /** + * Sets a content description of the default action button. This + * resource should be a string taking one formatting argument and + * will be used for formatting the content description of the button + * dynamically as the default target changes. For example, a resource + * pointing to the string "share with %1$s" will result in a content + * description "share with Bluetooth" for the Bluetooth activity. + * + * @param resourceId The resource id. + */ + public void setDefaultActionButtonContentDescription(int resourceId) { + mDefaultActionButtonContentDescription = resourceId; + } + + /** + * Gets the list popup window which is lazily initialized. + * + * @return The popup. + */ + private ListPopupWindow getListPopupWindow() { + if (mListPopupWindow == null) { + mListPopupWindow = new ListPopupWindow(getContext()); + mListPopupWindow.setAdapter(mAdapter); + mListPopupWindow.setAnchorView(ActivityChooserView.this); + mListPopupWindow.setModal(true); + mListPopupWindow.setOnItemClickListener(mCallbacks); + mListPopupWindow.setOnDismissListener(mCallbacks); + } + return mListPopupWindow; + } + + /** + * Updates the buttons state. + */ + private void updateAppearance() { + // Expand overflow button. + if (mAdapter.getCount() > 0) { + mExpandActivityOverflowButton.setEnabled(true); + } else { + mExpandActivityOverflowButton.setEnabled(false); + } + // Default activity button. + final int activityCount = mAdapter.getActivityCount(); + final int historySize = mAdapter.getHistorySize(); + if (activityCount==1 || activityCount > 1 && historySize > 0) { + mDefaultActivityButton.setVisibility(VISIBLE); + ResolveInfo activity = mAdapter.getDefaultActivity(); + PackageManager packageManager = getContext().getPackageManager(); + mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager)); + if (mDefaultActionButtonContentDescription != 0) { + CharSequence label = activity.loadLabel(packageManager); + String contentDescription = getContext().getString( + mDefaultActionButtonContentDescription, label); + mDefaultActivityButton.setContentDescription(contentDescription); + } + } else { + mDefaultActivityButton.setVisibility(View.GONE); + } + // Activity chooser content. + if (mDefaultActivityButton.getVisibility() == VISIBLE) { + mActivityChooserContent.setBackgroundDrawable(mActivityChooserContentBackground); + } else { + mActivityChooserContent.setBackgroundDrawable(null); + } + } + + /** + * Interface implementation to avoid publishing them in the APIs. + */ + private class Callbacks implements AdapterView.OnItemClickListener, + View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener { + + // AdapterView#OnItemClickListener + public void onItemClick(AdapterView parent, View view, int position, long id) { + ActivityChooserViewAdapter adapter = (ActivityChooserViewAdapter) parent.getAdapter(); + final int itemViewType = adapter.getItemViewType(position); + switch (itemViewType) { + case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_FOOTER: { + showPopupUnchecked(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED); + } break; + case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_ACTIVITY: { + dismissPopup(); + if (mIsSelectingDefaultActivity) { + // The item at position zero is the default already. + if (position > 0) { + mAdapter.getDataModel().setDefaultActivity(position); + } + } else { + // If the default target is not shown in the list, the first + // item in the model is default action => adjust index + position = mAdapter.getShowDefaultActivity() ? position : position + 1; + Intent launchIntent = mAdapter.getDataModel().chooseActivity(position); + if (launchIntent != null) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + getContext().startActivity(launchIntent); + } + } + } break; + default: + throw new IllegalArgumentException(); + } + } + + // View.OnClickListener + public void onClick(View view) { + if (view == mDefaultActivityButton) { + dismissPopup(); + ResolveInfo defaultActivity = mAdapter.getDefaultActivity(); + final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity); + Intent launchIntent = mAdapter.getDataModel().chooseActivity(index); + if (launchIntent != null) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + getContext().startActivity(launchIntent); + } + } else if (view == mExpandActivityOverflowButton) { + mIsSelectingDefaultActivity = false; + showPopupUnchecked(mInitialActivityCount); + } else { + throw new IllegalArgumentException(); + } + } + + // OnLongClickListener#onLongClick + @Override + public boolean onLongClick(View view) { + if (view == mDefaultActivityButton) { + if (mAdapter.getCount() > 0) { + mIsSelectingDefaultActivity = true; + showPopupUnchecked(mInitialActivityCount); + } + } else { + throw new IllegalArgumentException(); + } + return true; + } + + // PopUpWindow.OnDismissListener#onDismiss + public void onDismiss() { + notifyOnDismissListener(); + if (mProvider != null) { + mProvider.subUiVisibilityChanged(false); + } + } + + private void notifyOnDismissListener() { + if (mOnDismissListener != null) { + mOnDismissListener.onDismiss(); + } + } + } + + /** + * Adapter for backing the list of activities shown in the popup. + */ + private class ActivityChooserViewAdapter extends BaseAdapter { + + public static final int MAX_ACTIVITY_COUNT_UNLIMITED = Integer.MAX_VALUE; + + public static final int MAX_ACTIVITY_COUNT_DEFAULT = 4; + + private static final int ITEM_VIEW_TYPE_ACTIVITY = 0; + + private static final int ITEM_VIEW_TYPE_FOOTER = 1; + + private static final int ITEM_VIEW_TYPE_COUNT = 3; + + private ActivityChooserModel mDataModel; + + private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT; + + private boolean mShowDefaultActivity; + + private boolean mHighlightDefaultActivity; + + private boolean mShowFooterView; + + public void setDataModel(ActivityChooserModel dataModel) { + ActivityChooserModel oldDataModel = mAdapter.getDataModel(); + if (oldDataModel != null && isShown()) { + oldDataModel.unregisterObserver(mModelDataSetOberver); + } + mDataModel = dataModel; + if (dataModel != null && isShown()) { + dataModel.registerObserver(mModelDataSetOberver); + } + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + if (mShowFooterView && position == getCount() - 1) { + return ITEM_VIEW_TYPE_FOOTER; + } else { + return ITEM_VIEW_TYPE_ACTIVITY; + } + } + + @Override + public int getViewTypeCount() { + return ITEM_VIEW_TYPE_COUNT; + } + + public int getCount() { + int count = 0; + int activityCount = mDataModel.getActivityCount(); + if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) { + activityCount--; + } + count = Math.min(activityCount, mMaxActivityCount); + if (mShowFooterView) { + count++; + } + return count; + } + + public Object getItem(int position) { + final int itemViewType = getItemViewType(position); + switch (itemViewType) { + case ITEM_VIEW_TYPE_FOOTER: + return null; + case ITEM_VIEW_TYPE_ACTIVITY: + if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) { + position++; + } + return mDataModel.getActivity(position); + default: + throw new IllegalArgumentException(); + } + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + final int itemViewType = getItemViewType(position); + switch (itemViewType) { + case ITEM_VIEW_TYPE_FOOTER: + if (convertView == null || convertView.getId() != ITEM_VIEW_TYPE_FOOTER) { + convertView = LayoutInflater.from(getContext()).inflate( + R.layout.abc_activity_chooser_view_list_item, parent, false); + convertView.setId(ITEM_VIEW_TYPE_FOOTER); + TextView titleView = (TextView) convertView.findViewById(R.id.title); + titleView.setText(getContext().getString( + R.string.abc_activity_chooser_view_see_all)); + } + return convertView; + case ITEM_VIEW_TYPE_ACTIVITY: + if (convertView == null || convertView.getId() != R.id.list_item) { + convertView = LayoutInflater.from(getContext()).inflate( + R.layout.abc_activity_chooser_view_list_item, parent, false); + } + PackageManager packageManager = getContext().getPackageManager(); + // Set the icon + ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); + ResolveInfo activity = (ResolveInfo) getItem(position); + iconView.setImageDrawable(activity.loadIcon(packageManager)); + // Set the title. + TextView titleView = (TextView) convertView.findViewById(R.id.title); + titleView.setText(activity.loadLabel(packageManager)); + // Highlight the default. + if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) { + ViewCompat.setActivated(convertView, true); + } else { + ViewCompat.setActivated(convertView, false); + } + return convertView; + default: + throw new IllegalArgumentException(); + } + } + + public int measureContentWidth() { + // The user may have specified some of the target not to be shown but we + // want to measure all of them since after expansion they should fit. + final int oldMaxActivityCount = mMaxActivityCount; + mMaxActivityCount = MAX_ACTIVITY_COUNT_UNLIMITED; + + int contentWidth = 0; + View itemView = null; + + final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int count = getCount(); + + for (int i = 0; i < count; i++) { + itemView = getView(i, itemView, null); + itemView.measure(widthMeasureSpec, heightMeasureSpec); + contentWidth = Math.max(contentWidth, itemView.getMeasuredWidth()); + } + + mMaxActivityCount = oldMaxActivityCount; + + return contentWidth; + } + + public void setMaxActivityCount(int maxActivityCount) { + if (mMaxActivityCount != maxActivityCount) { + mMaxActivityCount = maxActivityCount; + notifyDataSetChanged(); + } + } + + public ResolveInfo getDefaultActivity() { + return mDataModel.getDefaultActivity(); + } + + public void setShowFooterView(boolean showFooterView) { + if (mShowFooterView != showFooterView) { + mShowFooterView = showFooterView; + notifyDataSetChanged(); + } + } + + public int getActivityCount() { + return mDataModel.getActivityCount(); + } + + public int getHistorySize() { + return mDataModel.getHistorySize(); + } + + public ActivityChooserModel getDataModel() { + return mDataModel; + } + + public void setShowDefaultActivity(boolean showDefaultActivity, + boolean highlightDefaultActivity) { + if (mShowDefaultActivity != showDefaultActivity + || mHighlightDefaultActivity != highlightDefaultActivity) { + mShowDefaultActivity = showDefaultActivity; + mHighlightDefaultActivity = highlightDefaultActivity; + notifyDataSetChanged(); + } + } + + public boolean getShowDefaultActivity() { + return mShowDefaultActivity; + } + } + + /** + * Allows us to set the background using TintTypedArray + * @hide + */ + public static class InnerLayout extends LinearLayoutCompat { + + private static final int[] TINT_ATTRS = { + android.R.attr.background + }; + + public InnerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS); + setBackgroundDrawable(a.getDrawable(0)); + a.recycle(); + } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AdapterViewCompat.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AdapterViewCompat.java new file mode 100644 index 0000000000..bdce3c86a4 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AdapterViewCompat.java @@ -0,0 +1,1150 @@ +package android.support.v7.internal.widget; + +/* + * 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. + */ + +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.ContextMenu.ContextMenuInfo; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.widget.Adapter; +import android.widget.AdapterView; +import android.widget.ListView; + + +/** + * An AdapterView is a view whose children are determined by an {@link android.widget.Adapter}. + * + *

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

+ *

Developer Guides

+ *

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

+ * + * @hide + */ +public abstract class AdapterViewCompat extends ViewGroup { + + /** + * The item view type returned by {@link Adapter#getItemViewType(int)} when + * the adapter does not want the item's view recycled. + */ + 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. + */ + 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; + + AdapterViewCompat(Context context) { + super(context); + } + + AdapterViewCompat(Context context, AttributeSet attrs) { + super(context, attrs); + } + + AdapterViewCompat(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Interface definition for a callback to be invoked when an item in this + * AdapterView has been clicked. + */ + public interface OnItemClickListener { + + /** + * Callback method to be invoked when an item in this AdapterView has + * been clicked. + *

+ * Implementers can call getItemAtPosition(position) if they need + * to access the data associated with the selected item. + * + * @param parent The AdapterView where the click happened. + * @param view The view within the AdapterView that was clicked (this + * will be a view provided by the adapter) + * @param position The position of the view in the adapter. + * @param id The row id of the item that was clicked. + */ + void onItemClick(AdapterViewCompat parent, View view, int position, long id); + } + + class OnItemClickListenerWrapper implements AdapterView.OnItemClickListener { + + private final OnItemClickListener mWrappedListener; + + public OnItemClickListenerWrapper(OnItemClickListener listener) { + mWrappedListener = listener; + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + mWrappedListener.onItemClick(AdapterViewCompat.this, view, position, id); + } + } + + /** + * 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, 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(AdapterViewCompat 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(AdapterViewCompat 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(AdapterViewCompat 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 (AdapterViewCompat.this.getAdapter().hasStableIds() && mInstanceState != null + && mOldItemCount == 0 && mItemCount > 0) { + AdapterViewCompat.this.onRestoreInstanceState(mInstanceState); + mInstanceState = null; + } else { + rememberSyncState(); + } + checkFocus(); + requestLayout(); + } + + @Override + public void onInvalidated() { + mDataChanged = true; + + if (AdapterViewCompat.this.getAdapter().hasStableIds()) { + // Remember the current state for the case where our hosting activity is being + // stopped and later restarted + mInstanceState = AdapterViewCompat.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 + 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; + } + } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AppCompatPopupWindow.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AppCompatPopupWindow.java new file mode 100644 index 0000000000..511a332639 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/AppCompatPopupWindow.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.v7.appcompat.R; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewTreeObserver.OnScrollChangedListener; +import android.widget.PopupWindow; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; + +/** + * @hide + */ +public class AppCompatPopupWindow extends PopupWindow { + + private static final String TAG = "AppCompatPopupWindow"; + + private final boolean mOverlapAnchor; + + public AppCompatPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, + R.styleable.PopupWindow, defStyleAttr, 0); + mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); + // We re-set this for tinting purposes + setBackgroundDrawable(a.getDrawable(R.styleable.PopupWindow_android_popupBackground)); + a.recycle(); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + // For devices pre-ICS, we need to wrap the internal OnScrollChangedListener + // due to NPEs. + wrapOnScrollChangedListener(this); + } + } + + @Override + public void showAsDropDown(View anchor, int xoff, int yoff) { + if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) { + // If we're pre-L, emulate overlapAnchor by modifying the yOff + yoff -= anchor.getHeight(); + } + super.showAsDropDown(anchor, xoff, yoff); + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + @Override + public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { + if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) { + // If we're pre-L, emulate overlapAnchor by modifying the yOff + yoff -= anchor.getHeight(); + } + super.showAsDropDown(anchor, xoff, yoff, gravity); + } + + @Override + public void update(View anchor, int xoff, int yoff, int width, int height) { + if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) { + // If we're pre-L, emulate overlapAnchor by modifying the yOff + yoff -= anchor.getHeight(); + } + super.update(anchor, xoff, yoff, width, height); + } + + private static void wrapOnScrollChangedListener(final PopupWindow popup) { + try { + final Field fieldAnchor = PopupWindow.class.getDeclaredField("mAnchor"); + fieldAnchor.setAccessible(true); + + final Field fieldListener = PopupWindow.class + .getDeclaredField("mOnScrollChangedListener"); + fieldListener.setAccessible(true); + + final OnScrollChangedListener originalListener = + (OnScrollChangedListener) fieldListener.get(popup); + + // Now set a new listener, wrapping the original and only proxying the call when + // we have an anchor view. + fieldListener.set(popup, new OnScrollChangedListener() { + @Override + public void onScrollChanged() { + try { + WeakReference mAnchor = (WeakReference) fieldAnchor.get(popup); + if (mAnchor == null || mAnchor.get() == null) { + return; + } else { + originalListener.onScrollChanged(); + } + } catch (IllegalAccessException e) { + // Oh well... + } + } + }); + } catch (Exception e) { + Log.d(TAG, "Exception while installing workaround OnScrollChangedListener", e); + } + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/CompatTextView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/CompatTextView.java new file mode 100644 index 0000000000..f90ab75338 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/CompatTextView.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.support.v7.appcompat.R; +import android.support.v7.internal.text.AllCapsTransformationMethod; +import android.text.method.TransformationMethod; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import java.util.Locale; + +/** + * @hide + */ +public class CompatTextView extends TextView { + + public CompatTextView(Context context) { + this(context, null); + } + + public CompatTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CompatTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // First read the TextAppearance style id + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView, + defStyle, 0); + final int ap = a.getResourceId(R.styleable.CompatTextView_android_textAppearance, -1); + a.recycle(); + + // Now check TextAppearance's textAllCaps value + if (ap != -1) { + TypedArray appearance = context.obtainStyledAttributes(ap, R.styleable.TextAppearance); + if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) { + setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false)); + } + appearance.recycle(); + } + + // Now read the style's value + a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView, defStyle, 0); + if (a.hasValue(R.styleable.CompatTextView_textAllCaps)) { + setAllCaps(a.getBoolean(R.styleable.CompatTextView_textAllCaps, false)); + } + a.recycle(); + } + + public void setAllCaps(boolean allCaps) { + setTransformationMethod(allCaps ? new AllCapsTransformationMethod(getContext()) : null); + } + + @Override + public void setTextAppearance(Context context, int resid) { + super.setTextAppearance(context, resid); + + TypedArray appearance = context.obtainStyledAttributes(resid, R.styleable.TextAppearance); + if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) { + setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false)); + } + appearance.recycle(); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ContentFrameLayout.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ContentFrameLayout.java new file mode 100644 index 0000000000..1f140d164b --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ContentFrameLayout.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * @hide + */ +public class ContentFrameLayout extends FrameLayout { + + public ContentFrameLayout(Context context) { + this(context, null); + } + + public ContentFrameLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ContentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + /** + * @hide + */ + public void dispatchFitSystemWindows(Rect insets) { + fitSystemWindows(insets); + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java new file mode 100644 index 0000000000..780aeebbe1 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.util.SparseArray; +import android.view.Menu; +import android.view.Window; + +/** + * Implemented by the top-level decor layout for a window. DecorContentParent offers + * entry points for a number of title/window decor features. + * + * @hide + */ +public interface DecorContentParent { + void setWindowCallback(Window.Callback cb); + void setWindowTitle(CharSequence title); + CharSequence getTitle(); + void initFeature(int windowFeature); + void setUiOptions(int uiOptions); + boolean hasIcon(); + boolean hasLogo(); + void setIcon(int resId); + void setIcon(Drawable d); + void setLogo(int resId); + boolean canShowOverflowMenu(); + boolean isOverflowMenuShowing(); + boolean isOverflowMenuShowPending(); + boolean showOverflowMenu(); + boolean hideOverflowMenu(); + void setMenuPrepared(); + void setMenu(Menu menu, MenuPresenter.Callback cb); + void saveToolbarHierarchyState(SparseArray toolbarStates); + void restoreToolbarHierarchyState(SparseArray toolbarStates); + void dismissPopups(); + +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java new file mode 100644 index 0000000000..f824dd7c11 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.util.SparseArray; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.SpinnerAdapter; + +/** + * Common interface for a toolbar that sits as part of the window decor. + * Layouts that control window decor use this as a point of interaction with different + * bar implementations. + * + * @hide + */ +public interface DecorToolbar { + ViewGroup getViewGroup(); + Context getContext(); + boolean isSplit(); + boolean hasExpandedActionView(); + void collapseActionView(); + void setWindowCallback(Window.Callback cb); + void setWindowTitle(CharSequence title); + CharSequence getTitle(); + void setTitle(CharSequence title); + CharSequence getSubtitle(); + void setSubtitle(CharSequence subtitle); + void initProgress(); + void initIndeterminateProgress(); + boolean canSplit(); + void setSplitView(ViewGroup splitView); + void setSplitToolbar(boolean split); + void setSplitWhenNarrow(boolean splitWhenNarrow); + boolean hasIcon(); + boolean hasLogo(); + void setIcon(int resId); + void setIcon(Drawable d); + void setLogo(int resId); + void setLogo(Drawable d); + boolean canShowOverflowMenu(); + boolean isOverflowMenuShowing(); + boolean isOverflowMenuShowPending(); + boolean showOverflowMenu(); + boolean hideOverflowMenu(); + void setMenuPrepared(); + void setMenu(Menu menu, MenuPresenter.Callback cb); + void dismissPopupMenus(); + + int getDisplayOptions(); + void setDisplayOptions(int opts); + void setEmbeddedTabView(ScrollingTabContainerView tabView); + boolean hasEmbeddedTabs(); + boolean isTitleTruncated(); + void setCollapsible(boolean collapsible); + void setHomeButtonEnabled(boolean enable); + int getNavigationMode(); + void setNavigationMode(int mode); + void setDropdownParams(SpinnerAdapter adapter, AdapterViewCompat.OnItemSelectedListener listener); + void setDropdownSelectedPosition(int position); + int getDropdownSelectedPosition(); + int getDropdownItemCount(); + void setCustomView(View view); + View getCustomView(); + void animateToVisibility(int visibility); + void setNavigationIcon(Drawable icon); + void setNavigationIcon(int resId); + void setNavigationContentDescription(CharSequence description); + void setNavigationContentDescription(int resId); + void setDefaultNavigationContentDescription(int defaultNavigationContentDescription); + void setDefaultNavigationIcon(Drawable icon); + void saveHierarchyState(SparseArray toolbarStates); + void restoreHierarchyState(SparseArray toolbarStates); + void setBackgroundDrawable(Drawable d); + int getHeight(); + void setVisibility(int visible); + int getVisibility(); + void setMenuCallbacks(MenuPresenter.Callback presenterCallback, + MenuBuilder.Callback menuBuilderCallback); + Menu getMenu(); + int getPopupTheme(); +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DrawableUtils.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DrawableUtils.java new file mode 100644 index 0000000000..1ff5e4ddf6 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/DrawableUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.util.Log; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * @hide + */ +public class DrawableUtils { + + private static final String TAG = "DrawableUtils"; + + public static final Rect INSETS_NONE = new Rect(); + + private static Class sInsetsClazz; + + static { + if (Build.VERSION.SDK_INT >= 18) { + try { + sInsetsClazz = Class.forName("android.graphics.Insets"); + } catch (ClassNotFoundException e) { + // Oh well... + } + } + } + + private DrawableUtils() {} + + /** + * Allows us to get the optical insets for a {@link Drawable}. Since this is hidden we need to + * use reflection. Since the {@code Insets} class is hidden also, we return a Rect instead. + */ + public static Rect getOpticalBounds(Drawable drawable) { + if (sInsetsClazz != null) { + try { + // If the Drawable is wrapped, we need to manually unwrap it and process + // the wrapped drawable. + drawable = DrawableCompat.unwrap(drawable); + + final Method getOpticalInsetsMethod = drawable.getClass() + .getMethod("getOpticalInsets"); + final Object insets = getOpticalInsetsMethod.invoke(drawable); + + if (insets != null) { + // If the drawable has some optical insets, let's copy them into a Rect + final Rect result = new Rect(); + + for (Field field : sInsetsClazz.getFields()) { + switch (field.getName()) { + case "left": + result.left = field.getInt(insets); + break; + case "top": + result.top = field.getInt(insets); + break; + case "right": + result.right = field.getInt(insets); + break; + case "bottom": + result.bottom = field.getInt(insets); + break; + } + } + return result; + } + } catch (Exception e) { + // Eugh, we hit some kind of reflection issue... + Log.e(TAG, "Couldn't obtain the optical insets. Ignoring."); + } + } + + // If we reach here, either we're running on a device pre-v18, the Drawable didn't have + // any optical insets, or a reflection issue, so we'll just return an empty rect + return INSETS_NONE; + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsFrameLayout.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsFrameLayout.java new file mode 100644 index 0000000000..49f95fa3b3 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsFrameLayout.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * @hide + */ +public class FitWindowsFrameLayout extends FrameLayout implements FitWindowsViewGroup { + + private OnFitSystemWindowsListener mListener; + + public FitWindowsFrameLayout(Context context) { + super(context); + } + + public FitWindowsFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener) { + mListener = listener; + } + + @Override + protected boolean fitSystemWindows(Rect insets) { + if (mListener != null) { + mListener.onFitSystemWindows(insets); + } + return super.fitSystemWindows(insets); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsLinearLayout.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsLinearLayout.java new file mode 100644 index 0000000000..6adf74dae2 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsLinearLayout.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +/** + * @hide + */ +public class FitWindowsLinearLayout extends LinearLayout implements FitWindowsViewGroup { + + private OnFitSystemWindowsListener mListener; + + public FitWindowsLinearLayout(Context context) { + super(context); + } + + public FitWindowsLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener) { + mListener = listener; + } + + @Override + protected boolean fitSystemWindows(Rect insets) { + if (mListener != null) { + mListener.onFitSystemWindows(insets); + } + return super.fitSystemWindows(insets); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsViewGroup.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsViewGroup.java new file mode 100644 index 0000000000..c84fda0e4a --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/FitWindowsViewGroup.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.graphics.Rect; + +/** + * @hide + */ +public interface FitWindowsViewGroup { + + public interface OnFitSystemWindowsListener { + void onFitSystemWindows(Rect insets); + } + + public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener); + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java new file mode 100644 index 0000000000..e2e6c4c569 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.graphics.drawable.DrawableWrapper; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.ListAdapter; +import android.widget.ListView; + +import java.lang.reflect.Field; + +/** + * This class contains a number of useful things for ListView. Mainly used by + * {@link android.support.v7.widget.ListPopupWindow}. + * + * @hide + */ +public class ListViewCompat extends ListView { + + public static final int INVALID_POSITION = -1; + public static final int NO_POSITION = -1; + + private static final int[] STATE_SET_NOTHING = new int[] { 0 }; + + final Rect mSelectorRect = new Rect(); + int mSelectionLeftPadding = 0; + int mSelectionTopPadding = 0; + int mSelectionRightPadding = 0; + int mSelectionBottomPadding = 0; + + private Field mIsChildViewEnabled; + + private GateKeeperDrawable mSelector; + + public ListViewCompat(Context context) { + this(context, null); + } + + public ListViewCompat(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ListViewCompat(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + try { + mIsChildViewEnabled = AbsListView.class.getDeclaredField("mIsChildViewEnabled"); + mIsChildViewEnabled.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + + @Override + public void setSelector(Drawable sel) { + mSelector = sel != null ? new GateKeeperDrawable(sel) : null; + super.setSelector(mSelector); + + final Rect padding = new Rect(); + if (sel != null) { + sel.getPadding(padding); + } + + mSelectionLeftPadding = padding.left; + mSelectionTopPadding = padding.top; + mSelectionRightPadding = padding.right; + mSelectionBottomPadding = padding.bottom; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + setSelectorEnabled(true); + updateSelectorStateCompat(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + final boolean drawSelectorOnTop = false; + if (!drawSelectorOnTop) { + drawSelectorCompat(canvas); + } + + super.dispatchDraw(canvas); + } + + protected void updateSelectorStateCompat() { + Drawable selector = getSelector(); + if (selector != null && shouldShowSelectorCompat()) { + selector.setState(getDrawableState()); + } + } + + protected boolean shouldShowSelectorCompat() { + return touchModeDrawsInPressedStateCompat() && isPressed(); + } + + protected boolean touchModeDrawsInPressedStateCompat() { + return false; + } + + protected void drawSelectorCompat(Canvas canvas) { + if (!mSelectorRect.isEmpty()) { + final Drawable selector = getSelector(); + if (selector != null) { + selector.setBounds(mSelectorRect); + selector.draw(canvas); + } + } + } + + /** + * 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. + */ + public int lookForSelectablePosition(int position, boolean lookDown) { + final ListAdapter adapter = getAdapter(); + if (adapter == null || isInTouchMode()) { + return INVALID_POSITION; + } + + final int count = adapter.getCount(); + if (!getAdapter().areAllItemsEnabled()) { + if (lookDown) { + position = Math.max(0, position); + while (position < count && !adapter.isEnabled(position)) { + position++; + } + } else { + position = Math.min(position, count - 1); + while (position >= 0 && !adapter.isEnabled(position)) { + position--; + } + } + + if (position < 0 || position >= count) { + return INVALID_POSITION; + } + return position; + } else { + if (position < 0 || position >= count) { + return INVALID_POSITION; + } + return position; + } + } + + protected void positionSelectorLikeTouchCompat(int position, View sel, float x, float y) { + positionSelectorLikeFocusCompat(position, sel); + + Drawable selector = getSelector(); + if (selector != null && position != INVALID_POSITION) { + DrawableCompat.setHotspot(selector, x, y); + } + } + + protected void positionSelectorLikeFocusCompat(int position, View sel) { + // If we're changing position, update the visibility since the selector + // is technically being detached from the previous selection. + final Drawable selector = getSelector(); + final boolean manageState = selector != null && position != INVALID_POSITION; + if (manageState) { + selector.setVisible(false, false); + } + + positionSelectorCompat(position, sel); + + if (manageState) { + final Rect bounds = mSelectorRect; + final float x = bounds.exactCenterX(); + final float y = bounds.exactCenterY(); + selector.setVisible(getVisibility() == VISIBLE, false); + DrawableCompat.setHotspot(selector, x, y); + } + } + + protected void positionSelectorCompat(int position, View sel) { + final Rect selectorRect = mSelectorRect; + selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); + + // Adjust for selection padding. + selectorRect.left -= mSelectionLeftPadding; + selectorRect.top -= mSelectionTopPadding; + selectorRect.right += mSelectionRightPadding; + selectorRect.bottom += mSelectionBottomPadding; + + try { + // AbsListView.mIsChildViewEnabled controls the selector's state so we need to + // modify it's value + final boolean isChildViewEnabled = mIsChildViewEnabled.getBoolean(this); + if (sel.isEnabled() != isChildViewEnabled) { + mIsChildViewEnabled.set(this, !isChildViewEnabled); + if (position != INVALID_POSITION) { + refreshDrawableState(); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * Measures the height of the given range of children (inclusive) and returns the height + * with this ListView's padding and divider heights included. If maxHeight is provided, the + * measuring will stop when the current height reaches maxHeight. + * + * @param widthMeasureSpec The width measure spec to be given to a child's + * {@link View#measure(int, int)}. + * @param startPosition The position of the first child to be shown. + * @param endPosition The (inclusive) position of the last child to be + * shown. Specify {@link #NO_POSITION} if the last child + * should be the last available child from the adapter. + * @param maxHeight The maximum height that will be returned (if all the + * children don't fit in this value, this value will be + * returned). + * @param disallowPartialChildPosition In general, whether the returned height should only + * contain entire children. This is more powerful--it is + * the first inclusive position at which partial + * children will not be allowed. Example: it looks nice + * to have at least 3 completely visible children, and + * in portrait this will most likely fit; but in + * landscape there could be times when even 2 children + * can not be completely shown, so a value of 2 + * (remember, inclusive) would be good (assuming + * startPosition is 0). + * @return The height of this ListView with the given children. + */ + public int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition, + int endPosition, final int maxHeight, + int disallowPartialChildPosition) { + + final int paddingTop = getListPaddingTop(); + final int paddingBottom = getListPaddingBottom(); + final int paddingLeft = getListPaddingLeft(); + final int paddingRight = getListPaddingRight(); + final int reportedDividerHeight = getDividerHeight(); + final Drawable divider = getDivider(); + + final ListAdapter adapter = getAdapter(); + + if (adapter == null) { + return paddingTop + paddingBottom; + } + + // Include the padding of the list + int returnedHeight = paddingTop + paddingBottom; + final int dividerHeight = ((reportedDividerHeight > 0) && divider != null) + ? reportedDividerHeight : 0; + + // The previous height value that was less than maxHeight and contained + // no partial children + int prevHeightWithoutPartialChild = 0; + + View child = null; + int viewType = 0; + int count = adapter.getCount(); + for (int i = 0; i < count; i++) { + int newType = adapter.getItemViewType(i); + if (newType != viewType) { + child = null; + viewType = newType; + } + child = adapter.getView(i, child, this); + + // Compute child height spec + int heightMeasureSpec; + final ViewGroup.LayoutParams childLp = child.getLayoutParams(); + if (childLp != null && childLp.height > 0) { + heightMeasureSpec = MeasureSpec.makeMeasureSpec(childLp.height, + MeasureSpec.EXACTLY); + } else { + heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + child.measure(widthMeasureSpec, heightMeasureSpec); + + 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; + } + + protected void setSelectorEnabled(boolean enabled) { + if (mSelector != null) { + mSelector.setEnabled(enabled); + } + } + + private static class GateKeeperDrawable extends DrawableWrapper { + private boolean mEnabled; + + public GateKeeperDrawable(Drawable drawable) { + super(drawable); + mEnabled = true; + } + + void setEnabled(boolean enabled) { + mEnabled = enabled; + } + + @Override + public boolean setState(int[] stateSet) { + if (mEnabled) { + return super.setState(stateSet); + } + return false; + } + + @Override + public void draw(Canvas canvas) { + if (mEnabled) { + super.draw(canvas); + } + } + + @Override + public void setHotspot(float x, float y) { + if (mEnabled) { + super.setHotspot(x, y); + } + } + + @Override + public void setHotspotBounds(int left, int top, int right, int bottom) { + if (mEnabled) { + super.setHotspotBounds(left, top, right, bottom); + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + if (mEnabled) { + return super.setVisible(visible, restart); + } + return false; + } + } + + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/NativeActionModeAwareLayout.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/NativeActionModeAwareLayout.java new file mode 100644 index 0000000000..d19fff9263 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/NativeActionModeAwareLayout.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 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 android.support.v7.internal.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.ActionMode; +import android.view.View; + +/** + * @hide + */ +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +public class NativeActionModeAwareLayout extends ContentFrameLayout { + + private OnActionModeForChildListener mActionModeForChildListener; + + public NativeActionModeAwareLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setActionModeForChildListener(OnActionModeForChildListener listener) { + mActionModeForChildListener = listener; + } + + public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) { + if (mActionModeForChildListener != null) { + return mActionModeForChildListener.startActionModeForChild(originalView, callback); + } + return super.startActionModeForChild(originalView, callback); + } + + /** + * @hide + */ + public interface OnActionModeForChildListener { + ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java new file mode 100644 index 0000000000..48ebab82c9 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2015 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 android.support.v7.internal.widget; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.Movie; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Drawable.ConstantState; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.LongSparseArray; +import android.util.TypedValue; + +import java.io.IOException; +import java.io.InputStream; + +/** + * This extends Resources but delegates the calls to another Resources object. This enables + * any customization done by some subclass of Resources to be also picked up. + */ +class ResourcesWrapper extends Resources { + + private final Resources mResources; + + public ResourcesWrapper(Resources resources) { + super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration()); + mResources = resources; + } + + @Override + public CharSequence getText(int id) throws NotFoundException { + return mResources.getText(id); + } + + @Override + public CharSequence getQuantityText(int id, int quantity) throws NotFoundException { + return mResources.getQuantityText(id, quantity); + } + + @Override + public String getString(int id) throws NotFoundException { + return mResources.getString(id); + } + + @Override + public String getString(int id, Object... formatArgs) throws NotFoundException { + return mResources.getString(id, formatArgs); + } + + @Override + public String getQuantityString(int id, int quantity, Object... formatArgs) + throws NotFoundException { + return mResources.getQuantityString(id, quantity, formatArgs); + } + + @Override + public String getQuantityString(int id, int quantity) throws NotFoundException { + return mResources.getQuantityString(id, quantity); + } + + @Override + public CharSequence getText(int id, CharSequence def) { + return mResources.getText(id, def); + } + + @Override + public CharSequence[] getTextArray(int id) throws NotFoundException { + return mResources.getTextArray(id); + } + + @Override + public String[] getStringArray(int id) throws NotFoundException { + return mResources.getStringArray(id); + } + + @Override + public int[] getIntArray(int id) throws NotFoundException { + return mResources.getIntArray(id); + } + + @Override + public TypedArray obtainTypedArray(int id) throws NotFoundException { + return mResources.obtainTypedArray(id); + } + + @Override + public float getDimension(int id) throws NotFoundException { + return mResources.getDimension(id); + } + + @Override + public int getDimensionPixelOffset(int id) throws NotFoundException { + return mResources.getDimensionPixelOffset(id); + } + + @Override + public int getDimensionPixelSize(int id) throws NotFoundException { + return mResources.getDimensionPixelSize(id); + } + + @Override + public float getFraction(int id, int base, int pbase) { + return mResources.getFraction(id, base, pbase); + } + + @Override + public Drawable getDrawable(int id) throws NotFoundException { + return mResources.getDrawable(id); + } + + @Override + public Drawable getDrawable(int id, Theme theme) throws NotFoundException { + return mResources.getDrawable(id, theme); + } + + @Override + public Drawable getDrawableForDensity(int id, int density) throws NotFoundException { + return mResources.getDrawableForDensity(id, density); + } + + @Override + public Drawable getDrawableForDensity(int id, int density, Theme theme) { + return mResources.getDrawableForDensity(id, density, theme); + } + + @Override + public Movie getMovie(int id) throws NotFoundException { + return mResources.getMovie(id); + } + + @Override + public int getColor(int id) throws NotFoundException { + return mResources.getColor(id); + } + + @Override + public ColorStateList getColorStateList(int id) throws NotFoundException { + return mResources.getColorStateList(id); + } + + @Override + public boolean getBoolean(int id) throws NotFoundException { + return mResources.getBoolean(id); + } + + @Override + public int getInteger(int id) throws NotFoundException { + return mResources.getInteger(id); + } + + @Override + public XmlResourceParser getLayout(int id) throws NotFoundException { + return mResources.getLayout(id); + } + + @Override + public XmlResourceParser getAnimation(int id) throws NotFoundException { + return mResources.getAnimation(id); + } + + @Override + public XmlResourceParser getXml(int id) throws NotFoundException { + return mResources.getXml(id); + } + + @Override + public InputStream openRawResource(int id) throws NotFoundException { + return mResources.openRawResource(id); + } + + @Override + public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { + return mResources.openRawResource(id, value); + } + + @Override + public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { + return mResources.openRawResourceFd(id); + } + + @Override + public void getValue(int id, TypedValue outValue, boolean resolveRefs) + throws NotFoundException { + mResources.getValue(id, outValue, resolveRefs); + } + + @Override + public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs) + throws NotFoundException { + mResources.getValueForDensity(id, density, outValue, resolveRefs); + } + + @Override + public void getValue(String name, TypedValue outValue, boolean resolveRefs) + throws NotFoundException { + mResources.getValue(name, outValue, resolveRefs); + } + + @Override + public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { + return mResources.obtainAttributes(set, attrs); + } + + @Override + public void updateConfiguration(Configuration config, DisplayMetrics metrics) { + super.updateConfiguration(config, metrics); + if (mResources != null) { // called from super's constructor. So, need to check. + mResources.updateConfiguration(config, metrics); + } + } + + @Override + public DisplayMetrics getDisplayMetrics() { + return mResources.getDisplayMetrics(); + } + + @Override + public Configuration getConfiguration() { + return mResources.getConfiguration(); + } + + @Override + public int getIdentifier(String name, String defType, String defPackage) { + return mResources.getIdentifier(name, defType, defPackage); + } + + @Override + public String getResourceName(int resid) throws NotFoundException { + return mResources.getResourceName(resid); + } + + @Override + public String getResourcePackageName(int resid) throws NotFoundException { + return mResources.getResourcePackageName(resid); + } + + @Override + public String getResourceTypeName(int resid) throws NotFoundException { + return mResources.getResourceTypeName(resid); + } + + @Override + public String getResourceEntryName(int resid) throws NotFoundException { + return mResources.getResourceEntryName(resid); + } + + @Override + public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle) + throws XmlPullParserException, IOException { + mResources.parseBundleExtras(parser, outBundle); + } + + @Override + public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle) + throws XmlPullParserException { + mResources.parseBundleExtra(tagName, attrs, outBundle); + } +} + diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/RtlSpacingHelper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/RtlSpacingHelper.java new file mode 100644 index 0000000000..e6c80d1572 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/RtlSpacingHelper.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +/** + * RtlSpacingHelper manages the relationship between left/right and start/end for views + * that need to maintain both absolute and relative settings for a form of spacing similar + * to view padding. + * + * @hide + */ +public class RtlSpacingHelper { + public static final int UNDEFINED = Integer.MIN_VALUE; + + private int mLeft = 0; + private int mRight = 0; + private int mStart = UNDEFINED; + private int mEnd = UNDEFINED; + private int mExplicitLeft = 0; + private int mExplicitRight = 0; + + private boolean mIsRtl = false; + private boolean mIsRelative = false; + + public int getLeft() { + return mLeft; + } + + public int getRight() { + return mRight; + } + + public int getStart() { + return mIsRtl ? mRight : mLeft; + } + + public int getEnd() { + return mIsRtl ? mLeft : mRight; + } + + public void setRelative(int start, int end) { + mStart = start; + mEnd = end; + mIsRelative = true; + if (mIsRtl) { + if (end != UNDEFINED) mLeft = end; + if (start != UNDEFINED) mRight = start; + } else { + if (start != UNDEFINED) mLeft = start; + if (end != UNDEFINED) mRight = end; + } + } + + public void setAbsolute(int left, int right) { + mIsRelative = false; + if (left != UNDEFINED) mLeft = mExplicitLeft = left; + if (right != UNDEFINED) mRight = mExplicitRight = right; + } + + public void setDirection(boolean isRtl) { + if (isRtl == mIsRtl) { + return; + } + mIsRtl = isRtl; + if (mIsRelative) { + if (isRtl) { + mLeft = mEnd != UNDEFINED ? mEnd : mExplicitLeft; + mRight = mStart != UNDEFINED ? mStart : mExplicitRight; + } else { + mLeft = mStart != UNDEFINED ? mStart : mExplicitLeft; + mRight = mEnd != UNDEFINED ? mEnd : mExplicitRight; + } + } else { + mLeft = mExplicitLeft; + mRight = mExplicitRight; + } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ScrollingTabContainerView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ScrollingTabContainerView.java new file mode 100644 index 0000000000..1426208572 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ScrollingTabContainerView.java @@ -0,0 +1,609 @@ +/* + * 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.support.v4.view.ViewPropertyAnimatorListener; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.ActionBarPolicy; +import android.support.v7.widget.LinearLayoutCompat; +import android.text.TextUtils; +import android.text.TextUtils.TruncateAt; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +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 android.widget.TextView; +import android.widget.Toast; + +/** + * This widget implements the dynamic action bar tab behavior that can change across different + * configurations or circumstances. + * + * @hide + */ +public class ScrollingTabContainerView extends HorizontalScrollView + implements AdapterViewCompat.OnItemClickListener { + + private static final String TAG = "ScrollingTabContainerView"; + Runnable mTabSelector; + private TabClickListener mTabClickListener; + + private LinearLayoutCompat mTabLayout; + private SpinnerCompat mTabSpinner; + private boolean mAllowCollapse; + + int mMaxTabWidth; + int mStackedTabMaxWidth; + private int mContentHeight; + private int mSelectedTabIndex; + + protected ViewPropertyAnimatorCompat mVisibilityAnim; + protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); + + private static final Interpolator sAlphaInterpolator = new DecelerateInterpolator(); + + private static final int FADE_DURATION = 200; + + public ScrollingTabContainerView(Context context) { + super(context); + + setHorizontalScrollBarEnabled(false); + + ActionBarPolicy abp = ActionBarPolicy.get(context); + setContentHeight(abp.getTabContainerHeight()); + mStackedTabMaxWidth = abp.getStackedTabMaxWidth(); + + 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; + } + mMaxTabWidth = Math.min(mMaxTabWidth, mStackedTabMaxWidth); + } 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); + } + } + if (mTabSpinner != null && position >= 0) { + mTabSpinner.setSelection(position); + } + } + + public void setContentHeight(int contentHeight) { + mContentHeight = contentHeight; + requestLayout(); + } + + private LinearLayoutCompat createTabLayout() { + final LinearLayoutCompat tabLayout = new LinearLayoutCompat(getContext(), null, + R.attr.actionBarTabBarStyle); + tabLayout.setMeasureWithLargestChildEnabled(true); + tabLayout.setGravity(Gravity.CENTER); + tabLayout.setLayoutParams(new LinearLayoutCompat.LayoutParams( + LinearLayoutCompat.LayoutParams.WRAP_CONTENT, LinearLayoutCompat.LayoutParams.MATCH_PARENT)); + return tabLayout; + } + + private SpinnerCompat createSpinner() { + final SpinnerCompat spinner = new SpinnerCompat(getContext(), null, + R.attr.actionDropDownStyle); + spinner.setLayoutParams(new LinearLayoutCompat.LayoutParams( + LinearLayoutCompat.LayoutParams.WRAP_CONTENT, LinearLayoutCompat.LayoutParams.MATCH_PARENT)); + spinner.setOnItemClickListenerInt(this); + return spinner; + } + + protected void onConfigurationChanged(Configuration newConfig) { + if (Build.VERSION.SDK_INT >= 8) { + super.onConfigurationChanged(newConfig); + } + + ActionBarPolicy abp = ActionBarPolicy.get(getContext()); + // Action bar can change size on configuration changes. + // Reread the desired height from the theme-specified style. + setContentHeight(abp.getTabContainerHeight()); + mStackedTabMaxWidth = abp.getStackedTabMaxWidth(); + } + + public void animateToVisibility(int visibility) { + if (mVisibilityAnim != null) { + mVisibilityAnim.cancel(); + } + if (visibility == VISIBLE) { + if (getVisibility() != VISIBLE) { + ViewCompat.setAlpha(this, 0f); + } + + ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(1f); + anim.setDuration(FADE_DURATION); + + anim.setInterpolator(sAlphaInterpolator); + anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility)); + anim.start(); + } else { + ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(0f); + anim.setDuration(FADE_DURATION); + + anim.setInterpolator(sAlphaInterpolator); + anim.setListener(mVisAnimListener.withFinalVisibility(anim, 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) { + final TabView tabView = new TabView(getContext(), 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 LinearLayoutCompat.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 LinearLayoutCompat.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 onItemClick(AdapterViewCompat parent, View view, int position, long id) { + TabView tabView = (TabView) view; + tabView.getTab().select(); + } + + private class TabView extends LinearLayoutCompat implements OnLongClickListener { + private final int[] BG_ATTRS = { + android.R.attr.background + }; + + private ActionBar.Tab mTab; + private TextView mTextView; + private ImageView mIconView; + private View mCustomView; + + public TabView(Context context, ActionBar.Tab tab, boolean forList) { + super(context, null, R.attr.actionBarTabStyle); + mTab = tab; + + TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, BG_ATTRS, + R.attr.actionBarTabStyle, 0); + if (a.hasValue(0)) { + setBackgroundDrawable(a.getDrawable(0)); + } + a.recycle(); + + if (forList) { + setGravity(GravityCompat.START | Gravity.CENTER_VERTICAL); + } + + update(); + } + + public void bindTab(ActionBar.Tab tab) { + mTab = tab; + update(); + } + + @Override + public void setSelected(boolean selected) { + final boolean changed = (isSelected() != selected); + super.setSelected(selected); + if (changed && selected) { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + } + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + // This view masquerades as an action bar tab. + event.setClassName(ActionBar.Tab.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + + if (Build.VERSION.SDK_INT >= 14) { + // This view masquerades as an action bar tab. + info.setClassName(ActionBar.Tab.class.getName()); + } + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // Re-measure if we went beyond our maximum size. + if (mMaxTabWidth > 0 && getMeasuredWidth() > mMaxTabWidth) { + super.onMeasure(MeasureSpec.makeMeasureSpec(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); + } + + final boolean hasText = !TextUtils.isEmpty(text); + if (hasText) { + if (mTextView == null) { + TextView textView = new CompatTextView(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.setText(text); + mTextView.setVisibility(VISIBLE); + } else if (mTextView != null) { + mTextView.setVisibility(GONE); + mTextView.setText(null); + } + + if (mIconView != null) { + mIconView.setContentDescription(tab.getContentDescription()); + } + + if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) { + setOnLongClickListener(this); + } else { + setOnLongClickListener(null); + setLongClickable(false); + } + } + } + + public boolean onLongClick(View v) { + final int[] screenPos = new int[2]; + getLocationOnScreen(screenPos); + + final Context context = getContext(); + final int width = getWidth(); + final int height = getHeight(); + final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + + Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(), + Toast.LENGTH_SHORT); + // Show under the tab + cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, + (screenPos[0] + width / 2) - screenWidth / 2, height); + + cheatSheet.show(); + return true; + } + + 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 ViewPropertyAnimatorListener { + private boolean mCanceled = false; + private int mFinalVisibility; + + public VisibilityAnimListener withFinalVisibility(ViewPropertyAnimatorCompat animation, + int visibility) { + mFinalVisibility = visibility; + mVisibilityAnim = animation; + return this; + } + + @Override + public void onAnimationStart(View view) { + setVisibility(VISIBLE); + mCanceled = false; + } + + @Override + public void onAnimationEnd(View view) { + if (mCanceled) return; + + mVisibilityAnim = null; + setVisibility(mFinalVisibility); + } + + @Override + public void onAnimationCancel(View view) { + mCanceled = true; + } + } +} + diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java new file mode 100644 index 0000000000..b2b8af24ad --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java @@ -0,0 +1,1102 @@ +/* + * 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 android.support.v7.internal.widget; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.ViewCompat; +import android.support.v7.appcompat.R; +import android.support.v7.widget.ListPopupWindow; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.AdapterView; +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 android.widget.Adapter} associated with this view. + * + *

See the Spinner + * tutorial.

+ */ +class SpinnerCompat extends AbsSpinnerCompat implements DialogInterface.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; + + /** + * Forwarding listener used to implement drag-to-open. + */ + private ListPopupWindow.ForwardingListener mForwardingListener; + + private SpinnerPopup mPopup; + + private DropDownAdapter mTempAdapter; + + int mDropDownWidth; + + private int mGravity; + + private boolean mDisableChildrenWhenDisabled; + + private Rect mTempRect = new Rect(); + + private final TintManager mTintManager; + + /** + * Construct a new spinner with the given context's theme. + * + * @param context The Context the view is running in, through which it can access the current + * theme, resources, etc. + */ + SpinnerCompat(Context context) { + this(context, null); + } + + /** + * Construct a new spinner with the given context's theme and the supplied mode of displaying + * choices. mode may be one of {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}. + * + * @param context The Context the view is running in, through which it can access the current + * theme, resources, etc. + * @param mode Constant describing how the user will select choices from the spinner. + * @see #MODE_DIALOG + * @see #MODE_DROPDOWN + */ + SpinnerCompat(Context context, int mode) { + this(context, null, R.attr.spinnerStyle, mode); + } + + /** + * Construct a new spinner with the given context's theme and the supplied attribute set. + * + * @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. + */ + SpinnerCompat(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.spinnerStyle); + } + + /** + * 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. + */ + SpinnerCompat(Context context, AttributeSet attrs, int defStyle) { + this(context, attrs, defStyle, MODE_THEME); + } + + /** + * Construct a new spinner with the given context's theme, the supplied attribute set, and + * default style. mode may be one of {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN} + * and determines how the user will select choices from the spinner. + * + * @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. + * @param mode Constant describing how the user will select choices from the spinner. + * @see #MODE_DIALOG + * @see #MODE_DROPDOWN + */ + SpinnerCompat(Context context, AttributeSet attrs, int defStyle, int mode) { + super(context, attrs, defStyle); + + TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, + R.styleable.Spinner, defStyle, 0); + + // Need to reset this for tinting purposes + if (a.hasValue(R.styleable.Spinner_android_background)) { + setBackgroundDrawable(a.getDrawable(R.styleable.Spinner_android_background)); + } + + if (mode == MODE_THEME) { + mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG); + } + + switch (mode) { + case MODE_DIALOG: { + mPopup = new DialogPopup(); + break; + } + + case MODE_DROPDOWN: { + final DropdownPopup popup = new DropdownPopup(context, attrs, defStyle); + + mDropDownWidth = a.getLayoutDimension(R.styleable.Spinner_android_dropDownWidth, + ViewGroup.LayoutParams.WRAP_CONTENT); + + popup.setBackgroundDrawable( + a.getDrawable(R.styleable.Spinner_android_popupBackground)); + + mPopup = popup; + mForwardingListener = new ListPopupWindow.ForwardingListener(this) { + @Override + public ListPopupWindow getPopup() { + return popup; + } + + @Override + public boolean onForwardingStarted() { + if (!mPopup.isShowing()) { + mPopup.show(); + } + return true; + } + }; + break; + } + } + + mGravity = a.getInt(R.styleable.Spinner_android_gravity, Gravity.CENTER); + + mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt)); + + mDisableChildrenWhenDisabled = a.getBoolean( + R.styleable.Spinner_disableChildrenWhenDisabled, false); + + 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; + } + + // Keep the TintManager in case we need it later + mTintManager = a.getTintManager(); + } + + /** + * Set the background drawable for the spinner's popup window of choices. Only valid in {@link + * #MODE_DROPDOWN}; this method is a no-op in other modes. + * + * @param background Background drawable + */ + public void setPopupBackgroundDrawable(Drawable background) { + if (!(mPopup instanceof DropdownPopup)) { + Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring..."); + return; + } + ((DropdownPopup) mPopup).setBackgroundDrawable(background); + } + + /** + * Set the background drawable for the spinner's popup window of choices. Only valid in {@link + * #MODE_DROPDOWN}; this method is a no-op in other modes. + * + * @param resId Resource ID of a background drawable + */ + public void setPopupBackgroundResource(int resId) { + setPopupBackgroundDrawable(mTintManager.getDrawable(resId)); + } + + /** + * Get the background drawable for the spinner's popup window of choices. Only valid in {@link + * #MODE_DROPDOWN}; other modes will return null. + * + * @return background Background drawable + */ + public Drawable getPopupBackground() { + return mPopup.getBackground(); + } + + /** + * Set a vertical offset in pixels for the spinner's popup window of choices. Only valid in + * {@link #MODE_DROPDOWN}; this method is a no-op in other modes. + * + * @param pixels Vertical offset in pixels + */ + public void setDropDownVerticalOffset(int pixels) { + mPopup.setVerticalOffset(pixels); + } + + /** + * Get the configured vertical offset in pixels for the spinner's popup window of choices. Only + * valid in {@link #MODE_DROPDOWN}; other modes will return 0. + * + * @return Vertical offset in pixels + */ + public int getDropDownVerticalOffset() { + return mPopup.getVerticalOffset(); + } + + /** + * Set a horizontal offset in pixels for the spinner's popup window of choices. Only valid in + * {@link #MODE_DROPDOWN}; this method is a no-op in other modes. + * + * @param pixels Horizontal offset in pixels + */ + public void setDropDownHorizontalOffset(int pixels) { + mPopup.setHorizontalOffset(pixels); + } + + /** + * Get the configured horizontal offset in pixels for the spinner's popup window of choices. + * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0. + * + * @return Horizontal offset in pixels + */ + public int getDropDownHorizontalOffset() { + return mPopup.getHorizontalOffset(); + } + + /** + * Set the width of the spinner's popup window of choices in pixels. This value may also be set + * to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} to match the width of the Spinner + * itself, or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured + * size of contained dropdown list items. + * + *

Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.

+ * + * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT + */ + public void setDropDownWidth(int pixels) { + if (!(mPopup instanceof DropdownPopup)) { + Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring"); + return; + } + mDropDownWidth = pixels; + } + + /** + * Get the configured width of the spinner's popup window of choices in pixels. The returned + * value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} meaning the popup + * window will match the width of the Spinner itself, or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} + * to wrap to the measured size of contained dropdown list items. + * + * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT + */ + public int getDropDownWidth() { + return mDropDownWidth; + } + + @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} + */ + public void setGravity(int gravity) { + if (mGravity != gravity) { + if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) { + gravity |= GravityCompat.START; + } + mGravity = gravity; + requestLayout(); + } + } + + @Override + public void setAdapter(SpinnerAdapter adapter) { + super.setAdapter(adapter); + + mRecycler.clear(); + + final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; + if (targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP + && adapter != null && adapter.getViewTypeCount() != 1) { + throw new IllegalArgumentException("Spinner adapter view type count must be 1"); + } + 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 = makeView(0, false); + mRecycler.put(0, child); + } + + 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."); + } + + void setOnItemClickListenerInt(OnItemClickListener l) { + super.setOnItemClickListener(l); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) { + return true; + } + + return super.onTouchEvent(event); + } + + @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 means 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; + if (mAdapter != null) { + View sel = makeView(mSelectedPosition, true); + int width = sel.getMeasuredWidth(); + int selectedOffset = childrenLeft; + final int layoutDirection = ViewCompat.getLayoutDirection(this); + final int absoluteGravity = GravityCompat.getAbsoluteGravity(mGravity, layoutDirection); + switch (absoluteGravity & 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 + * @param addChild true to add the child to the spinner, false to obtain and configure only. + * @return A view for the given position + */ + private View makeView(int position, boolean addChild) { + + View child; + + if (!mDataChanged) { + child = mRecycler.get(position); + if (child != null) { + // Position the view + setUpChild(child, addChild); + + return child; + } + } + + // Nothing found in the recycler -- ask the adapter for a view + child = mAdapter.getView(position, null, this); + + // Position the view + setUpChild(child, addChild); + + return child; + } + + /** + * Helper for makeAndAddView to set the position of a view and fill out its layout paramters. + * + * @param child The view to position + * @param addChild true if the child should be added to the Spinner during setup + */ + private void setUpChild(View child, boolean addChild) { + + // Respect layout params that are already in the view. Otherwise + // make some up... + ViewGroup.LayoutParams lp = child.getLayoutParams(); + if (lp == null) { + lp = generateDefaultLayoutParams(); + } + + if (addChild) { + 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; + } + + @Override + public Parcelable onSaveInstanceState() { + final SavedState ss = new SavedState(super.onSaveInstanceState()); + ss.showDropdown = mPopup != null && mPopup.isShowing(); + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + + super.onRestoreInstanceState(ss.getSuperState()); + + if (ss.showDropdown) { + ViewTreeObserver vto = getViewTreeObserver(); + if (vto != null) { + final ViewTreeObserver.OnGlobalLayoutListener listener + = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (!mPopup.isShowing()) { + mPopup.show(); + } + final ViewTreeObserver vto = getViewTreeObserver(); + if (vto != null) { + vto.removeGlobalOnLayoutListener(this); + } + } + }; + vto.addOnGlobalLayoutListener(listener); + } + } + } + + static class SavedState extends AbsSpinnerCompat.SavedState { + + boolean showDropdown; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + showDropdown = in.readByte() != 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeByte((byte) (showDropdown ? 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]; + } + }; + } + + /** + *

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(); + + public void setBackgroundDrawable(Drawable bg); + + public void setVerticalOffset(int px); + + public void setHorizontalOffset(int px); + + public Drawable getBackground(); + + public int getVerticalOffset(); + + public int getHorizontalOffset(); + } + + private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener { + + private AlertDialog mPopup; + + private ListAdapter mListAdapter; + + private CharSequence mPrompt; + + public void dismiss() { + if (mPopup != null) { + 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() { + if (mListAdapter == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + if (mPrompt != null) { + builder.setTitle(mPrompt); + } + mPopup = builder.setSingleChoiceItems(mListAdapter, + getSelectedItemPosition(), this).create(); + mPopup.show(); + } + + public void onClick(DialogInterface dialog, int which) { + setSelection(which); + if (mOnItemClickListener != null) { + performItemClick(null, which, mListAdapter.getItemId(which)); + } + dismiss(); + } + + @Override + public void setBackgroundDrawable(Drawable bg) { + Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring"); + } + + @Override + public void setVerticalOffset(int px) { + Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring"); + } + + @Override + public void setHorizontalOffset(int px) { + Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring"); + } + + @Override + public Drawable getBackground() { + return null; + } + + @Override + public int getVerticalOffset() { + return 0; + } + + @Override + public int getHorizontalOffset() { + return 0; + } + } + + private class DropdownPopup extends ListPopupWindow implements SpinnerPopup { + + private CharSequence mHintText; + + private ListAdapter mAdapter; + + public DropdownPopup( + Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + setAnchorView(SpinnerCompat.this); + setModal(true); + setPromptPosition(POSITION_PROMPT_ABOVE); + + setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View v, int position, long id) { + SpinnerCompat.this.setSelection(position); + if (mOnItemClickListener != null) { + SpinnerCompat.this + .performItemClick(v, position, mAdapter.getItemId(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; + } + + void computeContentWidth() { + final Drawable background = getBackground(); + int hOffset = 0; + if (background != null) { + background.getPadding(mTempRect); + hOffset = ViewUtils.isLayoutRtl(SpinnerCompat.this) ? mTempRect.right + : -mTempRect.left; + } else { + mTempRect.left = mTempRect.right = 0; + } + + final int spinnerPaddingLeft = SpinnerCompat.this.getPaddingLeft(); + final int spinnerPaddingRight = SpinnerCompat.this.getPaddingRight(); + final int spinnerWidth = SpinnerCompat.this.getWidth(); + if (mDropDownWidth == WRAP_CONTENT) { + int contentWidth = measureContentWidth( + (SpinnerAdapter) mAdapter, getBackground()); + final int contentWidthLimit = getContext().getResources() + .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right; + if (contentWidth > contentWidthLimit) { + contentWidth = contentWidthLimit; + } + setContentWidth(Math.max( + contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight)); + } else if (mDropDownWidth == MATCH_PARENT) { + setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight); + } else { + setContentWidth(mDropDownWidth); + } + if (ViewUtils.isLayoutRtl(SpinnerCompat.this)) { + hOffset += spinnerWidth - spinnerPaddingRight - getWidth(); + } else { + hOffset += spinnerPaddingLeft; + } + setHorizontalOffset(hOffset); + } + + public void show(int textDirection, int textAlignment) { + final boolean wasShowing = isShowing(); + + computeContentWidth(); + setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); + super.show(); + final ListView listView = getListView(); + listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + //listView.setTextDirection(textDirection); + //listView.setTextAlignment(textAlignment); + setSelection(SpinnerCompat.this.getSelectedItemPosition()); + + if (wasShowing) { + // Skip setting up the layout/dismiss listener below. If we were previously + // showing it will still stick around. + return; + } + + // Make sure we hide if our anchor goes away. + // TODO: This might be appropriate to push all the way down to PopupWindow, + // but it may have other side effects to investigate first. (Text editing handles, etc.) + final ViewTreeObserver vto = getViewTreeObserver(); + if (vto != null) { + final ViewTreeObserver.OnGlobalLayoutListener layoutListener + = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + computeContentWidth(); + + // Use super.show here to update; we don't want to move the selected + // position or adjust other things that would be reset otherwise. + DropdownPopup.super.show(); + } + }; + vto.addOnGlobalLayoutListener(layoutListener); + setOnDismissListener(new PopupWindow.OnDismissListener() { + @Override + public void onDismiss() { + final ViewTreeObserver vto = getViewTreeObserver(); + if (vto != null) { + vto.removeGlobalOnLayoutListener(layoutListener); + } + } + }); + } + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ThemeUtils.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ThemeUtils.java new file mode 100644 index 0000000000..aacecfa36b --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ThemeUtils.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.support.v4.graphics.ColorUtils; +import android.util.TypedValue; + +class ThemeUtils { + + private static final ThreadLocal TL_TYPED_VALUE = new ThreadLocal<>(); + + private static final int[] DISABLED_STATE_SET = new int[]{-android.R.attr.state_enabled}; + private static final int[] EMPTY_STATE_SET = new int[0]; + + private static final int[] TEMP_ARRAY = new int[1]; + + static ColorStateList createDisabledStateList(int textColor, int disabledTextColor) { + // Now create a new ColorStateList with the default color, and the new disabled + // color + final int[][] states = new int[2][]; + final int[] colors = new int[2]; + int i = 0; + + // Disabled state + states[i] = DISABLED_STATE_SET; + colors[i] = disabledTextColor; + i++; + + // Default state + states[i] = EMPTY_STATE_SET; + colors[i] = textColor; + i++; + + return new ColorStateList(states, colors); + } + + static int getThemeAttrColor(Context context, int attr) { + TEMP_ARRAY[0] = attr; + TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY); + try { + return a.getColor(0, 0); + } finally { + a.recycle(); + } + } + + static ColorStateList getThemeAttrColorStateList(Context context, int attr) { + TEMP_ARRAY[0] = attr; + TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY); + try { + return a.getColorStateList(0); + } finally { + a.recycle(); + } + } + + static int getDisabledThemeAttrColor(Context context, int attr) { + final ColorStateList csl = getThemeAttrColorStateList(context, attr); + if (csl != null && csl.isStateful()) { + // If the CSL is stateful, we'll assume it has a disabled state and use it + return csl.getColorForState(DISABLED_STATE_SET, csl.getDefaultColor()); + } else { + // Else, we'll generate the color using disabledAlpha from the theme + + final TypedValue tv = getTypedValue(); + // Now retrieve the disabledAlpha value from the theme + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true); + final float disabledAlpha = tv.getFloat(); + + return getThemeAttrColor(context, attr, disabledAlpha); + } + } + + private static TypedValue getTypedValue() { + TypedValue typedValue = TL_TYPED_VALUE.get(); + if (typedValue == null) { + typedValue = new TypedValue(); + TL_TYPED_VALUE.set(typedValue); + } + return typedValue; + } + + static int getThemeAttrColor(Context context, int attr, float alpha) { + final int color = getThemeAttrColor(context, attr); + final int originalAlpha = Color.alpha(color); + return ColorUtils.setAlphaComponent(color, Math.round(originalAlpha * alpha)); + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java new file mode 100644 index 0000000000..29ec605f46 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.support.annotation.Nullable; +import android.support.v4.view.TintableBackgroundView; +import android.util.AttributeSet; +import android.widget.AutoCompleteTextView; + +/** + * An tint aware {@link android.widget.AutoCompleteTextView}. + *

+ * This will automatically be used when you use {@link AutoCompleteTextView} in your layouts. You + * should only need to manually use this class writing custom views. + */ +public class TintAutoCompleteTextView extends AutoCompleteTextView implements + TintableBackgroundView { + + private static final int[] TINT_ATTRS = { + android.R.attr.background, + android.R.attr.popupBackground + }; + + private TintManager mTintManager; + private TintInfo mBackgroundTint; + + public TintAutoCompleteTextView(Context context) { + this(context, null); + } + + public TintAutoCompleteTextView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.autoCompleteTextViewStyle); + } + + public TintAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(TintContextWrapper.wrap(context), attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + mTintManager = a.getTintManager(); + + if (a.hasValue(0)) { + setSupportBackgroundTintList( + mTintManager.getColorStateList(a.getResourceId(0, -1))); + } + if (a.hasValue(1)) { + setDropDownBackgroundDrawable(a.getDrawable(1)); + } + a.recycle(); + } + } + + @Override + public void setDropDownBackgroundResource(int id) { + setDropDownBackgroundDrawable(mTintManager.getDrawable(id)); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View, + * android.content.res.ColorStateList)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintList(@Nullable ColorStateList tint) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintList = tint; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public ColorStateList getSupportBackgroundTintList() { + return mBackgroundTint != null ? mBackgroundTint.mTintList : null; + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintMode = tintMode; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public PorterDuff.Mode getSupportBackgroundTintMode() { + return mBackgroundTint != null ? mBackgroundTint.mTintMode : null; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + applySupportBackgroundTint(); + } + + private void applySupportBackgroundTint() { + if (getBackground() != null && mBackgroundTint != null) { + TintManager.tintViewBackground(this, mBackgroundTint); + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintButton.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintButton.java new file mode 100644 index 0000000000..41b3c3b354 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintButton.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.v4.view.TintableBackgroundView; +import android.util.AttributeSet; +import android.widget.Button; + +/** + * An tint aware {@link android.widget.Button}. + *

+ * This will automatically be used when you use {@link android.widget.Button} in your layouts. You + * should only need to manually use this class when writing custom views. + */ +public class TintButton extends Button implements TintableBackgroundView { + + private static final int[] TINT_ATTRS = { + android.R.attr.background + }; + + private TintInfo mBackgroundTint; + + public TintButton(Context context) { + this(context, null); + } + + public TintButton(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.buttonStyle); + } + + public TintButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + if (a.hasValue(0)) { + setSupportBackgroundTintList( + a.getTintManager().getColorStateList(a.getResourceId(0, -1))); + } + a.recycle(); + } + + final ColorStateList textColors = getTextColors(); + if (textColors != null && !textColors.isStateful()) { + // If we have a ColorStateList which isn't stateful, create one which includes + // a disabled state + + final int disabledTextColor; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // Pre-Lollipop, we will use textColorSecondary with android:disabledAlpha + // applied + disabledTextColor = ThemeUtils.getDisabledThemeAttrColor(context, + android.R.attr.textColorSecondary); + } else { + // With certain styles on Lollipop, there is a StateListAnimator which sets + // an alpha on the whole view, so we don't need to apply disabledAlpha to + // textColorSecondary + disabledTextColor = ThemeUtils.getThemeAttrColor(context, + android.R.attr.textColorSecondary); + } + + setTextColor(ThemeUtils.createDisabledStateList( + textColors.getDefaultColor(), disabledTextColor)); + } + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View, + * android.content.res.ColorStateList)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintList(@Nullable ColorStateList tint) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintList = tint; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public ColorStateList getSupportBackgroundTintList() { + return mBackgroundTint != null ? mBackgroundTint.mTintList : null; + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintMode = tintMode; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public PorterDuff.Mode getSupportBackgroundTintMode() { + return mBackgroundTint != null ? mBackgroundTint.mTintMode : null; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + applySupportBackgroundTint(); + } + + private void applySupportBackgroundTint() { + if (getBackground() != null && mBackgroundTint != null) { + TintManager.tintViewBackground(this, mBackgroundTint); + } + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintCheckBox.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintCheckBox.java new file mode 100644 index 0000000000..ea1fa684f5 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintCheckBox.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.CheckBox; + +/** + * An tint aware {@link android.widget.CheckBox}. + *

+ * This will automatically be used when you use {@link android.widget.CheckBox} in your layouts. + * You should only need to manually use this class when writing custom views. + */ +public class TintCheckBox extends CheckBox { + + private static final int[] TINT_ATTRS = { + android.R.attr.button + }; + + private TintManager mTintManager; + + public TintCheckBox(Context context) { + this(context, null); + } + + public TintCheckBox(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.checkboxStyle); + } + + public TintCheckBox(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + setButtonDrawable(a.getDrawable(0)); + a.recycle(); + + mTintManager = a.getTintManager(); + } + } + + @Override + public void setButtonDrawable(int resid) { + if (mTintManager != null) { + setButtonDrawable(mTintManager.getDrawable(resid)); + } else { + super.setButtonDrawable(resid); + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintCheckedTextView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintCheckedTextView.java new file mode 100644 index 0000000000..c969ad21ad --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintCheckedTextView.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.CheckedTextView; + +/** + * An tint aware {@link android.widget.CheckedTextView}. + *

+ * This will automatically be used when you use {@link android.widget.CheckedTextView} in your + * layouts. You should only need to manually use this class when writing custom views. + */ +public class TintCheckedTextView extends CheckedTextView { + + private static final int[] TINT_ATTRS = { + android.R.attr.checkMark + }; + + private TintManager mTintManager; + + public TintCheckedTextView(Context context) { + this(context, null); + } + + public TintCheckedTextView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.checkedTextViewStyle); + } + + public TintCheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + setCheckMarkDrawable(a.getDrawable(0)); + a.recycle(); + + mTintManager = a.getTintManager(); + } + } + + @Override + public void setCheckMarkDrawable(int resid) { + if (mTintManager != null) { + setCheckMarkDrawable(mTintManager.getDrawable(resid)); + } else { + super.setCheckMarkDrawable(resid); + } + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintContextWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintContextWrapper.java new file mode 100644 index 0000000000..794728862a --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintContextWrapper.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Resources; + +/** + * A {@link android.content.ContextWrapper} which returns a tint-aware + * {@link android.content.res.Resources} instance from {@link #getResources()}. + * + * @hide + */ +class TintContextWrapper extends ContextWrapper { + + private final TintManager mTintManager; + + public static Context wrap(Context context) { + if (!(context instanceof TintContextWrapper)) { + context = new TintContextWrapper(context); + } + return context; + } + + TintContextWrapper(Context base) { + super(base); + mTintManager = new TintManager(base); + } + + @Override + public Resources getResources() { + return mTintManager.getResources(); + } + + final TintManager getTintManager() { + return mTintManager; + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintEditText.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintEditText.java new file mode 100644 index 0000000000..95ba859701 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintEditText.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.support.annotation.Nullable; +import android.support.v4.view.TintableBackgroundView; +import android.util.AttributeSet; +import android.widget.EditText; + +/** + * An tint aware {@link android.widget.EditText}. + *

+ * This will automatically be used when you use {@link android.widget.EditText} in your + * layouts. You should only need to manually use this class when writing custom views. + */ +public class TintEditText extends EditText implements TintableBackgroundView { + + private static final int[] TINT_ATTRS = { + android.R.attr.background + }; + + private TintInfo mBackgroundTint; + + public TintEditText(Context context) { + this(context, null); + } + + public TintEditText(Context context, AttributeSet attrs) { + this(TintContextWrapper.wrap(context), attrs, android.R.attr.editTextStyle); + } + + public TintEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + if (a.hasValue(0)) { + setSupportBackgroundTintList( + a.getTintManager().getColorStateList(a.getResourceId(0, -1))); + } + a.recycle(); + } + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View, + * android.content.res.ColorStateList)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintList(@Nullable ColorStateList tint) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintList = tint; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public ColorStateList getSupportBackgroundTintList() { + return mBackgroundTint != null ? mBackgroundTint.mTintList : null; + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintMode = tintMode; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public PorterDuff.Mode getSupportBackgroundTintMode() { + return mBackgroundTint != null ? mBackgroundTint.mTintMode : null; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + applySupportBackgroundTint(); + } + + private void applySupportBackgroundTint() { + if (getBackground() != null && mBackgroundTint != null) { + TintManager.tintViewBackground(this, mBackgroundTint); + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintImageView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintImageView.java new file mode 100644 index 0000000000..17dd557385 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintImageView.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.support.annotation.DrawableRes; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * An tint aware {@link android.widget.ImageView} + * + * @hide + */ +public class TintImageView extends ImageView { + + private static final int[] TINT_ATTRS = { + android.R.attr.background, + android.R.attr.src + }; + + private final TintManager mTintManager; + + public TintImageView(Context context) { + this(context, null); + } + + public TintImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TintImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, TINT_ATTRS, + defStyleAttr, 0); + if (a.length() > 0) { + if (a.hasValue(0)) { + setBackgroundDrawable(a.getDrawable(0)); + } + if (a.hasValue(1)) { + setImageDrawable(a.getDrawable(1)); + } + } + a.recycle(); + + // Keep the TintManager in case we need it later + mTintManager = a.getTintManager(); + } + + @Override + public void setImageResource(@DrawableRes int resId) { + // Intercept this call and instead retrieve the Drawable via the tint manager + setImageDrawable(mTintManager.getDrawable(resId)); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintInfo.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintInfo.java new file mode 100644 index 0000000000..ba2a56a385 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintInfo.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; + +class TintInfo { + ColorStateList mTintList; + PorterDuff.Mode mTintMode; +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintManager.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintManager.java new file mode 100644 index 0000000000..9fdfaa8339 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintManager.java @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v4.util.LruCache; +import android.support.v7.appcompat.R; +import android.util.Log; +import android.util.SparseArray; +import android.util.TypedValue; +import android.view.View; + +import static android.support.v7.internal.widget.ThemeUtils.getDisabledThemeAttrColor; +import static android.support.v7.internal.widget.ThemeUtils.getThemeAttrColor; +import static android.support.v7.internal.widget.ThemeUtils.getThemeAttrColorStateList; + +/** + * @hide + */ +public final class TintManager { + + static final boolean SHOULD_BE_USED = Build.VERSION.SDK_INT < 21; + + private static final String TAG = TintManager.class.getSimpleName(); + private static final boolean DEBUG = false; + + static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN; + + private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6); + + /** + * Drawables which should be tinted with the value of {@code R.attr.colorControlNormal}, + * using the default mode. + */ + private static final int[] TINT_COLOR_CONTROL_NORMAL = { + R.drawable.abc_ic_ab_back_mtrl_am_alpha, + R.drawable.abc_ic_go_search_api_mtrl_alpha, + R.drawable.abc_ic_search_api_mtrl_alpha, + R.drawable.abc_ic_commit_search_api_mtrl_alpha, + R.drawable.abc_ic_clear_mtrl_alpha, + R.drawable.abc_ic_menu_share_mtrl_alpha, + R.drawable.abc_ic_menu_copy_mtrl_am_alpha, + R.drawable.abc_ic_menu_cut_mtrl_alpha, + R.drawable.abc_ic_menu_selectall_mtrl_alpha, + R.drawable.abc_ic_menu_paste_mtrl_am_alpha, + R.drawable.abc_ic_menu_moreoverflow_mtrl_alpha, + R.drawable.abc_ic_voice_search_api_mtrl_alpha, + R.drawable.abc_textfield_search_default_mtrl_alpha, + R.drawable.abc_textfield_default_mtrl_alpha, + R.drawable.abc_ab_share_pack_mtrl_alpha + }; + + /** + * Drawables which should be tinted with the value of {@code R.attr.colorControlActivated}, + * using the default mode. + */ + private static final int[] TINT_COLOR_CONTROL_ACTIVATED = { + R.drawable.abc_textfield_activated_mtrl_alpha, + R.drawable.abc_textfield_search_activated_mtrl_alpha, + R.drawable.abc_cab_background_top_mtrl_alpha, + R.drawable.abc_text_cursor_mtrl_alpha + }; + + /** + * Drawables which should be tinted with the value of {@code android.R.attr.colorBackground}, + * using the {@link android.graphics.PorterDuff.Mode#MULTIPLY} mode. + */ + private static final int[] TINT_COLOR_BACKGROUND_MULTIPLY = { + R.drawable.abc_popup_background_mtrl_mult, + R.drawable.abc_cab_background_internal_bg, + R.drawable.abc_menu_hardkey_panel_mtrl_mult + }; + + /** + * Drawables which should be tinted using a state list containing values of + * {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated} + */ + private static final int[] TINT_COLOR_CONTROL_STATE_LIST = { + R.drawable.abc_edit_text_material, + R.drawable.abc_tab_indicator_material, + R.drawable.abc_textfield_search_material, + R.drawable.abc_spinner_mtrl_am_alpha, + R.drawable.abc_btn_check_material, + R.drawable.abc_btn_radio_material, + R.drawable.abc_spinner_textfield_background_material, + R.drawable.abc_ratingbar_full_material, + R.drawable.abc_switch_track_mtrl_alpha, + R.drawable.abc_switch_thumb_material, + R.drawable.abc_btn_default_mtrl_shape, + R.drawable.abc_btn_borderless_material + }; + + /** + * Drawables which contain other drawables which should be tinted. The child drawable IDs + * should be defined in one of the arrays above. + */ + private static final int[] CONTAINERS_WITH_TINT_CHILDREN = { + R.drawable.abc_cab_background_top_material + }; + + private final Context mContext; + private final Resources mResources; + private final TypedValue mTypedValue; + + private final SparseArray mColorStateLists; + private ColorStateList mDefaultColorStateList; + + /** + * A helper method to instantiate a {@link TintManager} and then call {@link #getDrawable(int)}. + * This method should not be used routinely. + */ + public static Drawable getDrawable(Context context, int resId) { + if (isInTintList(resId)) { + final TintManager tm = (context instanceof TintContextWrapper) + ? ((TintContextWrapper) context).getTintManager() + : new TintManager(context); + return tm.getDrawable(resId); + } else { + return ContextCompat.getDrawable(context, resId); + } + } + + public TintManager(Context context) { + mColorStateLists = new SparseArray<>(); + mContext = context; + mTypedValue = new TypedValue(); + mResources = new TintResources(context.getResources(), this); + } + + Resources getResources() { + return mResources; + } + + public Drawable getDrawable(int resId) { + Drawable drawable = ContextCompat.getDrawable(mContext, resId); + + if (drawable != null) { + drawable = drawable.mutate(); + + if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) { + ColorStateList colorStateList = getColorStateListForKnownDrawableId(resId); + PorterDuff.Mode tintMode = DEFAULT_MODE; + if (resId == R.drawable.abc_switch_thumb_material) { + tintMode = PorterDuff.Mode.MULTIPLY; + } + + if (colorStateList != null) { + drawable = DrawableCompat.wrap(drawable); + DrawableCompat.setTintList(drawable, colorStateList); + DrawableCompat.setTintMode(drawable, tintMode); + } + } else if (arrayContains(CONTAINERS_WITH_TINT_CHILDREN, resId)) { + drawable = mResources.getDrawable(resId); + } else { + tintDrawable(resId, drawable); + } + } + return drawable; + } + + void tintDrawable(final int resId, final Drawable drawable) { + PorterDuff.Mode tintMode = null; + boolean colorAttrSet = false; + int colorAttr = 0; + int alpha = -1; + + if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) { + colorAttr = R.attr.colorControlNormal; + colorAttrSet = true; + } else if (arrayContains(TINT_COLOR_CONTROL_ACTIVATED, resId)) { + colorAttr = R.attr.colorControlActivated; + colorAttrSet = true; + } else if (arrayContains(TINT_COLOR_BACKGROUND_MULTIPLY, resId)) { + colorAttr = android.R.attr.colorBackground; + colorAttrSet = true; + tintMode = PorterDuff.Mode.MULTIPLY; + } else if (resId == R.drawable.abc_list_divider_mtrl_alpha) { + colorAttr = android.R.attr.colorForeground; + colorAttrSet = true; + alpha = Math.round(0.16f * 255); + } + + if (colorAttrSet) { + if (tintMode == null) { + tintMode = DEFAULT_MODE; + } + final int color = getThemeAttrColor(mContext, colorAttr); + + tintDrawableUsingColorFilter(drawable, color, tintMode); + + if (alpha != -1) { + drawable.setAlpha(alpha); + } + + if (DEBUG) { + Log.d(TAG, "Tinted Drawable ID: " + mResources.getResourceName(resId) + + " with color: #" + Integer.toHexString(color)); + } + } + } + + private static boolean arrayContains(int[] array, int value) { + for (int id : array) { + if (id == value) { + return true; + } + } + return false; + } + + private static boolean isInTintList(int drawableId) { + return arrayContains(TINT_COLOR_BACKGROUND_MULTIPLY, drawableId) || + arrayContains(TINT_COLOR_CONTROL_NORMAL, drawableId) || + arrayContains(TINT_COLOR_CONTROL_ACTIVATED, drawableId) || + arrayContains(TINT_COLOR_CONTROL_STATE_LIST, drawableId) || + arrayContains(CONTAINERS_WITH_TINT_CHILDREN, drawableId); + } + + ColorStateList getColorStateList(int resId) { + return arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId) + ? getColorStateListForKnownDrawableId(resId) + : null; + } + + private ColorStateList getColorStateListForKnownDrawableId(int resId) { + // Try the cache first + ColorStateList colorStateList = mColorStateLists.get(resId); + + if (colorStateList == null) { + // ...if the cache did not contain a color state list, try and create + if (resId == R.drawable.abc_edit_text_material) { + colorStateList = createEditTextColorStateList(); + } else if (resId == R.drawable.abc_switch_track_mtrl_alpha) { + colorStateList = createSwitchTrackColorStateList(); + } else if (resId == R.drawable.abc_switch_thumb_material) { + colorStateList = createSwitchThumbColorStateList(); + } else if (resId == R.drawable.abc_btn_default_mtrl_shape + || resId == R.drawable.abc_btn_borderless_material) { + colorStateList = createButtonColorStateList(); + } else if (resId == R.drawable.abc_spinner_mtrl_am_alpha + || resId == R.drawable.abc_spinner_textfield_background_material) { + colorStateList = createSpinnerColorStateList(); + } else { + // If we don't have an explicit color state list for this Drawable, use the default + colorStateList = getDefaultColorStateList(); + } + + // ..and add it to the cache + mColorStateLists.append(resId, colorStateList); + } + return colorStateList; + } + + private ColorStateList getDefaultColorStateList() { + if (mDefaultColorStateList == null) { + /** + * Generate the default color state list which uses the colorControl attributes. + * Order is important here. The default enabled state needs to go at the bottom. + */ + + final int colorControlNormal = getThemeAttrColor(mContext, R.attr.colorControlNormal); + final int colorControlActivated = getThemeAttrColor(mContext, + R.attr.colorControlActivated); + + final int[][] states = new int[7][]; + final int[] colors = new int[7]; + int i = 0; + + // Disabled state + states[i] = new int[] { -android.R.attr.state_enabled }; + colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal); + i++; + + states[i] = new int[] { android.R.attr.state_focused }; + colors[i] = colorControlActivated; + i++; + + states[i] = new int[] { android.R.attr.state_activated }; + colors[i] = colorControlActivated; + i++; + + states[i] = new int[] { android.R.attr.state_pressed }; + colors[i] = colorControlActivated; + i++; + + states[i] = new int[] { android.R.attr.state_checked }; + colors[i] = colorControlActivated; + i++; + + states[i] = new int[] { android.R.attr.state_selected }; + colors[i] = colorControlActivated; + i++; + + // Default enabled state + states[i] = new int[0]; + colors[i] = colorControlNormal; + i++; + + mDefaultColorStateList = new ColorStateList(states, colors); + } + return mDefaultColorStateList; + } + + private ColorStateList createSwitchTrackColorStateList() { + final int[][] states = new int[3][]; + final int[] colors = new int[3]; + int i = 0; + + // Disabled state + states[i] = new int[]{-android.R.attr.state_enabled}; + colors[i] = getThemeAttrColor(mContext, android.R.attr.colorForeground, 0.1f); + i++; + + states[i] = new int[]{android.R.attr.state_checked}; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated, 0.3f); + i++; + + // Default enabled state + states[i] = new int[0]; + colors[i] = getThemeAttrColor(mContext, android.R.attr.colorForeground, 0.3f); + i++; + + return new ColorStateList(states, colors); + } + + private ColorStateList createSwitchThumbColorStateList() { + final int[][] states = new int[3][]; + final int[] colors = new int[3]; + int i = 0; + + final ColorStateList thumbColor = getThemeAttrColorStateList(mContext, + R.attr.colorSwitchThumbNormal); + + if (thumbColor != null && thumbColor.isStateful()) { + // If colorSwitchThumbNormal is a valid ColorStateList, extract the default and + // disabled colors from it + + // Disabled state + states[i] = new int[]{-android.R.attr.state_enabled}; + colors[i] = thumbColor.getColorForState(states[i], 0); + i++; + + states[i] = new int[]{android.R.attr.state_checked}; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated); + i++; + + // Default enabled state + states[i] = new int[0]; + colors[i] = thumbColor.getDefaultColor(); + i++; + } else { + // Else we'll use an approximation using the default disabled alpha + + // Disabled state + states[i] = new int[]{-android.R.attr.state_enabled}; + colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorSwitchThumbNormal); + i++; + + states[i] = new int[]{android.R.attr.state_checked}; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated); + i++; + + // Default enabled state + states[i] = new int[0]; + colors[i] = getThemeAttrColor(mContext, R.attr.colorSwitchThumbNormal); + i++; + } + + return new ColorStateList(states, colors); + } + + private ColorStateList createEditTextColorStateList() { + final int[][] states = new int[3][]; + final int[] colors = new int[3]; + int i = 0; + + // Disabled state + states[i] = new int[]{-android.R.attr.state_enabled}; + colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal); + i++; + + states[i] = new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused}; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlNormal); + i++; + + // Default enabled state + states[i] = new int[0]; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated); + i++; + + return new ColorStateList(states, colors); + } + + private ColorStateList createButtonColorStateList() { + final int[][] states = new int[4][]; + final int[] colors = new int[4]; + int i = 0; + + // Disabled state + states[i] = new int[]{-android.R.attr.state_enabled}; + colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorButtonNormal); + i++; + + states[i] = new int[]{android.R.attr.state_pressed}; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlHighlight); + i++; + + states[i] = new int[]{android.R.attr.state_focused}; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlHighlight); + i++; + + // Default enabled state + states[i] = new int[0]; + colors[i] = getThemeAttrColor(mContext, R.attr.colorButtonNormal); + i++; + + return new ColorStateList(states, colors); + } + + private ColorStateList createSpinnerColorStateList() { + final int[][] states = new int[3][]; + final int[] colors = new int[3]; + int i = 0; + + // Disabled state + states[i] = new int[]{-android.R.attr.state_enabled}; + colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal); + i++; + + states[i] = new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused}; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlNormal); + i++; + + states[i] = new int[0]; + colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated); + i++; + + return new ColorStateList(states, colors); + } + + private static class ColorFilterLruCache extends LruCache { + + public ColorFilterLruCache(int maxSize) { + super(maxSize); + } + + PorterDuffColorFilter get(int color, PorterDuff.Mode mode) { + return get(generateCacheKey(color, mode)); + } + + PorterDuffColorFilter put(int color, PorterDuff.Mode mode, PorterDuffColorFilter filter) { + return put(generateCacheKey(color, mode), filter); + } + + private static int generateCacheKey(int color, PorterDuff.Mode mode) { + int hashCode = 1; + hashCode = 31 * hashCode + color; + hashCode = 31 * hashCode + mode.hashCode(); + return hashCode; + } + } + + public static void tintViewBackground(View view, TintInfo tint) { + final Drawable background = view.getBackground(); + if (tint.mTintList != null) { + tintDrawableUsingColorFilter( + background, + tint.mTintList.getColorForState(view.getDrawableState(), + tint.mTintList.getDefaultColor()), + tint.mTintMode != null ? tint.mTintMode : DEFAULT_MODE); + } else { + background.clearColorFilter(); + } + } + + private static void tintDrawableUsingColorFilter(Drawable drawable, int color, + PorterDuff.Mode mode) { + // First, lets see if the cache already contains the color filter + PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode); + + if (filter == null) { + // Cache miss, so create a color filter and add it to the cache + filter = new PorterDuffColorFilter(color, mode); + COLOR_FILTER_CACHE.put(color, mode, filter); + } + + drawable.setColorFilter(filter); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java new file mode 100644 index 0000000000..336243c170 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.support.annotation.Nullable; +import android.support.v4.view.TintableBackgroundView; +import android.util.AttributeSet; +import android.widget.MultiAutoCompleteTextView; + +/** + * An tint aware {@link android.widget.MultiAutoCompleteTextView}. + *

+ * This will automatically be used when you use {@link android.widget.MultiAutoCompleteTextView} + * in your layouts. You should only need to manually use this class when writing custom views. + */ +public class TintMultiAutoCompleteTextView extends MultiAutoCompleteTextView + implements TintableBackgroundView { + + private static final int[] TINT_ATTRS = { + android.R.attr.background, + android.R.attr.popupBackground + }; + + private TintManager mTintManager; + private TintInfo mBackgroundTint; + + public TintMultiAutoCompleteTextView(Context context) { + this(context, null); + } + + public TintMultiAutoCompleteTextView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.autoCompleteTextViewStyle); + } + + public TintMultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(TintContextWrapper.wrap(context), attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + mTintManager = a.getTintManager(); + + if (a.hasValue(0)) { + setSupportBackgroundTintList( + mTintManager.getColorStateList(a.getResourceId(0, -1))); + } + if (a.hasValue(1)) { + setDropDownBackgroundDrawable(a.getDrawable(1)); + } + a.recycle(); + } + } + + @Override + public void setDropDownBackgroundResource(int id) { + if (mTintManager != null) { + setDropDownBackgroundDrawable(mTintManager.getDrawable(id)); + } else { + super.setDropDownBackgroundResource(id); + } + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View, + * android.content.res.ColorStateList)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintList(@Nullable ColorStateList tint) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintList = tint; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public ColorStateList getSupportBackgroundTintList() { + return mBackgroundTint != null ? mBackgroundTint.mTintList : null; + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintMode = tintMode; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public PorterDuff.Mode getSupportBackgroundTintMode() { + return mBackgroundTint != null ? mBackgroundTint.mTintMode : null; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + applySupportBackgroundTint(); + } + + private void applySupportBackgroundTint() { + if (getBackground() != null && mBackgroundTint != null) { + TintManager.tintViewBackground(this, mBackgroundTint); + } + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintRadioButton.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintRadioButton.java new file mode 100644 index 0000000000..f5e537cf99 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintRadioButton.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.RadioButton; + +/** + * An tint aware {@link android.widget.RadioButton}. + *

+ * This will automatically be used when you use {@link android.widget.RadioButton} in your + * layouts. You should only need to manually use this class when writing custom views. + */ +public class TintRadioButton extends RadioButton { + + private static final int[] TINT_ATTRS = { + android.R.attr.button + }; + + private TintManager mTintManager; + + public TintRadioButton(Context context) { + this(context, null); + } + + public TintRadioButton(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.radioButtonStyle); + } + + public TintRadioButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + setButtonDrawable(a.getDrawable(0)); + a.recycle(); + + mTintManager = a.getTintManager(); + } + } + + @Override + public void setButtonDrawable(int resid) { + if (mTintManager != null) { + setButtonDrawable(mTintManager.getDrawable(resid)); + } else { + super.setButtonDrawable(resid); + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java new file mode 100644 index 0000000000..da0b965943 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Shader; +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.support.v4.graphics.drawable.DrawableWrapper; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.Gravity; +import android.widget.RatingBar; + +/** + * An tint aware {@link android.widget.RatingBar}. + *

+ * This will automatically be used when you use {@link android.widget.RatingBar} in your + * layouts. You should only need to manually use this class when writing custom views. + */ +public class TintRatingBar extends RatingBar { + + private static final int[] TINT_ATTRS = { + android.R.attr.indeterminateDrawable, + android.R.attr.progressDrawable + }; + + private Bitmap mSampleTile; + + public TintRatingBar(Context context) { + this(context, null); + } + + public TintRatingBar(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.ratingBarStyle); + } + + public TintRatingBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + + Drawable drawable = a.getDrawable(0); + if (drawable != null) { + setIndeterminateDrawable(tileifyIndeterminate(drawable)); + } + + drawable = a.getDrawable(1); + if (drawable != null) { + setProgressDrawable(tileify(drawable, false)); + } + + a.recycle(); + } + } + + /** + * 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 DrawableWrapper) { + Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable(); + if (inner != null) { + inner = tileify(inner, clip); + ((DrawableWrapper) drawable).setWrappedDrawable(inner); + } + } else 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 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; + } + + /** + * 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; + } + + private Shape getDrawableShape() { + final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; + return new RoundRectShape(roundedCorners, null, null); + } + + @Override + protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + if (mSampleTile != null) { + final int width = mSampleTile.getWidth() * getNumStars(); + setMeasuredDimension(ViewCompat.resolveSizeAndState(width, widthMeasureSpec, 0), + getMeasuredHeight()); + } + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintResources.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintResources.java new file mode 100644 index 0000000000..3dfbbc1d5d --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintResources.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.v7.appcompat.R; + +/** + * This class allows us to intercept calls so that we can tint resources (if applicable). + * + * @hide + */ +class TintResources extends ResourcesWrapper { + + private final TintManager mTintManager; + + public TintResources(Resources resources, TintManager tintManager) { + super(resources); + mTintManager = tintManager; + } + + /** + * We intercept this call so that we tint the result (if applicable). This is needed for things + * like {@link DrawableContainer}s which retrieve their children via this method. + */ + @Override + public Drawable getDrawable(int id) throws NotFoundException { + Drawable d = super.getDrawable(id); + if (d != null) { + mTintManager.tintDrawable(id, d); + } + return d; + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintSpinner.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintSpinner.java new file mode 100644 index 0000000000..a1b99bc26d --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintSpinner.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.v4.view.TintableBackgroundView; +import android.util.AttributeSet; +import android.widget.ListPopupWindow; +import android.widget.Spinner; + +import java.lang.reflect.Field; + +/** + * An tint aware {@link android.widget.Spinner}. + *

+ * This will automatically be used when you use {@link android.widget.Spinner} in your + * layouts. You should only need to manually use this class when writing custom views. + */ +public class TintSpinner extends Spinner implements TintableBackgroundView { + + private static final int[] TINT_ATTRS = { + android.R.attr.background, + android.R.attr.popupBackground + }; + + private TintInfo mBackgroundTint; + + public TintSpinner(Context context) { + this(context, null); + } + + public TintSpinner(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.spinnerStyle); + } + + public TintSpinner(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + if (TintManager.SHOULD_BE_USED) { + TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + TINT_ATTRS, defStyleAttr, 0); + if (a.hasValue(0)) { + setSupportBackgroundTintList( + a.getTintManager().getColorStateList(a.getResourceId(0, -1))); + } + if (a.hasValue(1)) { + final Drawable popupBackground = a.getDrawable(1); + if (Build.VERSION.SDK_INT >= 16) { + setPopupBackgroundDrawable(popupBackground); + } else if (Build.VERSION.SDK_INT >= 11) { + setPopupBackgroundDrawableV11(this, popupBackground); + } + } + a.recycle(); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private static void setPopupBackgroundDrawableV11(Spinner view, Drawable background) { + try { + Field popupField = Spinner.class.getDeclaredField("mPopup"); + popupField.setAccessible(true); + + Object popup = popupField.get(view); + + if (popup instanceof ListPopupWindow) { + ((ListPopupWindow) popup).setBackgroundDrawable(background); + } + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View, + * android.content.res.ColorStateList)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintList(@Nullable ColorStateList tint) { + if (mBackgroundTint == null && tint != null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintList = tint; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public ColorStateList getSupportBackgroundTintList() { + return mBackgroundTint != null ? mBackgroundTint.mTintList : null; + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)} + * + * @hide + */ + @Override + public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintMode = tintMode; + applySupportBackgroundTint(); + } + + /** + * This should be accessed via + * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)} + * + * @hide + */ + @Override + @Nullable + public PorterDuff.Mode getSupportBackgroundTintMode() { + return mBackgroundTint != null ? mBackgroundTint.mTintMode : null; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + applySupportBackgroundTint(); + } + + private void applySupportBackgroundTint() { + if (getBackground() != null && mBackgroundTint != null) { + TintManager.tintViewBackground(this, mBackgroundTint); + } + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintTypedArray.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintTypedArray.java new file mode 100644 index 0000000000..221e2a12fa --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/TintTypedArray.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.TypedValue; + +/** + * A class that wraps a {@link android.content.res.TypedArray} and provides the same public API + * surface. The purpose of this class is so that we can intercept the {@link #getDrawable(int)} + * call and tint the result. + * + * @hide + */ +public class TintTypedArray { + + private final Context mContext; + private final TypedArray mWrapped; + + private TintManager mTintManager; + + public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set, + int[] attrs) { + TypedArray array = context.obtainStyledAttributes(set, attrs); + return new TintTypedArray(context, array); + } + + public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set, + int[] attrs, int defStyleAttr, int defStyleRes) { + TypedArray array = context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes); + return new TintTypedArray(context, array); + } + + private TintTypedArray(Context context, TypedArray array) { + mContext = context; + mWrapped = array; + } + + public Drawable getDrawable(int index) { + if (mWrapped.hasValue(index)) { + final int resourceId = mWrapped.getResourceId(index, 0); + if (resourceId != 0) { + return getTintManager().getDrawable(resourceId); + } + } + return mWrapped.getDrawable(index); + } + + public int length() { + return mWrapped.length(); + } + + public int getIndexCount() { + return mWrapped.getIndexCount(); + } + + public int getIndex(int at) { + return mWrapped.getIndex(at); + } + + public Resources getResources() { + return mWrapped.getResources(); + } + + public CharSequence getText(int index) { + return mWrapped.getText(index); + } + + public String getString(int index) { + return mWrapped.getString(index); + } + + public String getNonResourceString(int index) { + return mWrapped.getNonResourceString(index); + } + + public boolean getBoolean(int index, boolean defValue) { + return mWrapped.getBoolean(index, defValue); + } + + public int getInt(int index, int defValue) { + return mWrapped.getInt(index, defValue); + } + + public float getFloat(int index, float defValue) { + return mWrapped.getFloat(index, defValue); + } + + public int getColor(int index, int defValue) { + return mWrapped.getColor(index, defValue); + } + + public ColorStateList getColorStateList(int index) { + return mWrapped.getColorStateList(index); + } + + public int getInteger(int index, int defValue) { + return mWrapped.getInteger(index, defValue); + } + + public float getDimension(int index, float defValue) { + return mWrapped.getDimension(index, defValue); + } + + public int getDimensionPixelOffset(int index, int defValue) { + return mWrapped.getDimensionPixelOffset(index, defValue); + } + + public int getDimensionPixelSize(int index, int defValue) { + return mWrapped.getDimensionPixelSize(index, defValue); + } + + public int getLayoutDimension(int index, String name) { + return mWrapped.getLayoutDimension(index, name); + } + + public int getLayoutDimension(int index, int defValue) { + return mWrapped.getLayoutDimension(index, defValue); + } + + public float getFraction(int index, int base, int pbase, float defValue) { + return mWrapped.getFraction(index, base, pbase, defValue); + } + + public int getResourceId(int index, int defValue) { + return mWrapped.getResourceId(index, defValue); + } + + public CharSequence[] getTextArray(int index) { + return mWrapped.getTextArray(index); + } + + public boolean getValue(int index, TypedValue outValue) { + return mWrapped.getValue(index, outValue); + } + + public int getType(int index) { + return mWrapped.getType(index); + } + + public boolean hasValue(int index) { + return mWrapped.hasValue(index); + } + + public TypedValue peekValue(int index) { + return mWrapped.peekValue(index); + } + + public String getPositionDescription() { + return mWrapped.getPositionDescription(); + } + + public void recycle() { + mWrapped.recycle(); + } + + public int getChangingConfigurations() { + return mWrapped.getChangingConfigurations(); + } + + public TintManager getTintManager() { + if (mTintManager == null) { + mTintManager = (mContext instanceof TintContextWrapper) + ? ((TintContextWrapper) mContext).getTintManager() + : new TintManager(mContext); + } + return mTintManager; + } + +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java new file mode 100644 index 0000000000..148bb27c8e --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java @@ -0,0 +1,716 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.app.ActionBar; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.renamemenu.ActionMenuItem; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.support.v7.widget.ActionMenuPresenter; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.SpinnerAdapter; + +/** + * Internal class used to interact with the Toolbar widget without + * exposing interface methods to the public API. + * + *

ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView + * so that either variant acting as a + * {@link android.support.v7.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave + * in the same way.

+ * + * @hide + */ +public class ToolbarWidgetWrapper implements DecorToolbar { + private static final String TAG = "ToolbarWidgetWrapper"; + + private static final int AFFECTS_LOGO_MASK = + ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO; + + private Toolbar mToolbar; + + private int mDisplayOpts; + private View mTabView; + private SpinnerCompat mSpinner; + private View mCustomView; + + private Drawable mIcon; + private Drawable mLogo; + private Drawable mNavIcon; + + private boolean mTitleSet; + private CharSequence mTitle; + private CharSequence mSubtitle; + private CharSequence mHomeDescription; + + private Window.Callback mWindowCallback; + private boolean mMenuPrepared; + private ActionMenuPresenter mActionMenuPresenter; + + private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD; + + private final TintManager mTintManager; + private int mDefaultNavigationContentDescription = 0; + private Drawable mDefaultNavigationIcon; + + public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) { + this(toolbar, style, R.string.abc_action_bar_up_description, + R.drawable.abc_ic_ab_back_mtrl_am_alpha); + } + + public ToolbarWidgetWrapper(Toolbar toolbar, boolean style, + int defaultNavigationContentDescription, int defaultNavigationIcon) { + mToolbar = toolbar; + mTitle = toolbar.getTitle(); + mSubtitle = toolbar.getSubtitle(); + mTitleSet = mTitle != null; + mNavIcon = toolbar.getNavigationIcon(); + + if (style) { + final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(), + null, R.styleable.ActionBar, R.attr.actionBarStyle, 0); + + final CharSequence title = a.getText(R.styleable.ActionBar_title); + if (!TextUtils.isEmpty(title)) { + setTitle(title); + } + + final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle); + if (!TextUtils.isEmpty(subtitle)) { + setSubtitle(subtitle); + } + + final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo); + if (logo != null) { + setLogo(logo); + } + + final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon); + if (mNavIcon == null && icon != null) { + setIcon(icon); + } + + final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator); + if (navIcon != null) { + setNavigationIcon(navIcon); + } + + setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0)); + + final int customNavId = a.getResourceId( + R.styleable.ActionBar_customNavigationLayout, 0); + if (customNavId != 0) { + setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId, + mToolbar, false)); + setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM); + } + + final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0); + if (height > 0) { + final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams(); + lp.height = height; + mToolbar.setLayoutParams(lp); + } + + final int contentInsetStart = a.getDimensionPixelOffset( + R.styleable.ActionBar_contentInsetStart, -1); + final int contentInsetEnd = a.getDimensionPixelOffset( + R.styleable.ActionBar_contentInsetEnd, -1); + if (contentInsetStart >= 0 || contentInsetEnd >= 0) { + mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0), + Math.max(contentInsetEnd, 0)); + } + + final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0); + if (titleTextStyle != 0) { + mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle); + } + + final int subtitleTextStyle = a.getResourceId( + R.styleable.ActionBar_subtitleTextStyle, 0); + if (subtitleTextStyle != 0) { + mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle); + } + + final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0); + if (popupTheme != 0) { + mToolbar.setPopupTheme(popupTheme); + } + + a.recycle(); + // Keep the TintManager in case we need it later + mTintManager = a.getTintManager(); + } else { + mDisplayOpts = detectDisplayOptions(); + // Create a TintManager in case we need it later + mTintManager = new TintManager(toolbar.getContext()); + } + + setDefaultNavigationContentDescription(defaultNavigationContentDescription); + mHomeDescription = mToolbar.getNavigationContentDescription(); + + setDefaultNavigationIcon(mTintManager.getDrawable(defaultNavigationIcon)); + + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(), + 0, android.R.id.home, 0, 0, mTitle); + @Override + public void onClick(View v) { + if (mWindowCallback != null && mMenuPrepared) { + mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem); + } + } + }); + } + + /** + * Sets the default content description for the navigation button. + *

+ * It changes the current content description if and only if the provided resource id is + * different than the current default resource id and the current content description is empty. + * + * @param defaultNavigationContentDescription The resource id for the default content + * description + */ + @Override + public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) { + if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) { + return; + } + mDefaultNavigationContentDescription = defaultNavigationContentDescription; + if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) { + setNavigationContentDescription(mDefaultNavigationContentDescription); + } + } + + @Override + public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) { + if (mDefaultNavigationIcon != defaultNavigationIcon) { + mDefaultNavigationIcon = defaultNavigationIcon; + updateNavigationIcon(); + } + } + + private int detectDisplayOptions() { + int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME | + ActionBar.DISPLAY_USE_LOGO; + if (mToolbar.getNavigationIcon() != null) { + opts |= ActionBar.DISPLAY_HOME_AS_UP; + } + return opts; + } + + @Override + public ViewGroup getViewGroup() { + return mToolbar; + } + + @Override + public Context getContext() { + return mToolbar.getContext(); + } + + @Override + public boolean isSplit() { + return false; + } + + @Override + public boolean hasExpandedActionView() { + return mToolbar.hasExpandedActionView(); + } + + @Override + public void collapseActionView() { + mToolbar.collapseActionView(); + } + + @Override + public void setWindowCallback(Window.Callback cb) { + mWindowCallback = cb; + } + + @Override + public void setWindowTitle(CharSequence title) { + // "Real" title always trumps window title. + if (!mTitleSet) { + setTitleInt(title); + } + } + + @Override + public CharSequence getTitle() { + return mToolbar.getTitle(); + } + + @Override + public void setTitle(CharSequence title) { + mTitleSet = true; + setTitleInt(title); + } + + private void setTitleInt(CharSequence title) { + mTitle = title; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setTitle(title); + } + } + + @Override + public CharSequence getSubtitle() { + return mToolbar.getSubtitle(); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mSubtitle = subtitle; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setSubtitle(subtitle); + } + } + + @Override + public void initProgress() { + Log.i(TAG, "Progress display unsupported"); + } + + @Override + public void initIndeterminateProgress() { + Log.i(TAG, "Progress display unsupported"); + } + + @Override + public boolean canSplit() { + return false; + } + + @Override + public void setSplitView(ViewGroup splitView) { + } + + @Override + public void setSplitToolbar(boolean split) { + if (split) { + throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar"); + } + } + + @Override + public void setSplitWhenNarrow(boolean splitWhenNarrow) { + // Ignore. + } + + @Override + public boolean hasIcon() { + return mIcon != null; + } + + @Override + public boolean hasLogo() { + return mLogo != null; + } + + @Override + public void setIcon(int resId) { + setIcon(resId != 0 ? mTintManager.getDrawable(resId) : null); + } + + @Override + public void setIcon(Drawable d) { + mIcon = d; + updateToolbarLogo(); + } + + @Override + public void setLogo(int resId) { + setLogo(resId != 0 ? mTintManager.getDrawable(resId) : null); + } + + @Override + public void setLogo(Drawable d) { + mLogo = d; + updateToolbarLogo(); + } + + private void updateToolbarLogo() { + Drawable logo = null; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) { + if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) { + logo = mLogo != null ? mLogo : mIcon; + } else { + logo = mIcon; + } + } + mToolbar.setLogo(logo); + } + + @Override + public boolean canShowOverflowMenu() { + return mToolbar.canShowOverflowMenu(); + } + + @Override + public boolean isOverflowMenuShowing() { + return mToolbar.isOverflowMenuShowing(); + } + + @Override + public boolean isOverflowMenuShowPending() { + return mToolbar.isOverflowMenuShowPending(); + } + + @Override + public boolean showOverflowMenu() { + return mToolbar.showOverflowMenu(); + } + + @Override + public boolean hideOverflowMenu() { + return mToolbar.hideOverflowMenu(); + } + + @Override + public void setMenuPrepared() { + mMenuPrepared = true; + } + + @Override + public void setMenu(Menu menu, MenuPresenter.Callback cb) { + if (mActionMenuPresenter == null) { + mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext()); + mActionMenuPresenter.setId(R.id.action_menu_presenter); + } + mActionMenuPresenter.setCallback(cb); + mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter); + } + + @Override + public void dismissPopupMenus() { + mToolbar.dismissPopupMenus(); + } + + @Override + public int getDisplayOptions() { + return mDisplayOpts; + } + + @Override + public void setDisplayOptions(int newOpts) { + final int oldOpts = mDisplayOpts; + final int changed = oldOpts ^ newOpts; + mDisplayOpts = newOpts; + if (changed != 0) { + if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + updateNavigationIcon(); + updateHomeAccessibility(); + } else { + mToolbar.setNavigationIcon(null); + } + } + + if ((changed & AFFECTS_LOGO_MASK) != 0) { + updateToolbarLogo(); + } + + if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setTitle(mTitle); + mToolbar.setSubtitle(mSubtitle); + } else { + mToolbar.setTitle(null); + mToolbar.setSubtitle(null); + } + } + + if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) { + if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.addView(mCustomView); + } else { + mToolbar.removeView(mCustomView); + } + } + } + } + + @Override + public void setEmbeddedTabView(ScrollingTabContainerView tabView) { + if (mTabView != null && mTabView.getParent() == mToolbar) { + mToolbar.removeView(mTabView); + } + mTabView = tabView; + if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) { + mToolbar.addView(mTabView, 0); + Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams(); + lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; + lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + lp.gravity = Gravity.START | Gravity.BOTTOM; + tabView.setAllowCollapse(true); + } + } + + @Override + public boolean hasEmbeddedTabs() { + return mTabView != null; + } + + @Override + public boolean isTitleTruncated() { + return mToolbar.isTitleTruncated(); + } + + @Override + public void setCollapsible(boolean collapsible) { + mToolbar.setCollapsible(collapsible); + } + + @Override + public void setHomeButtonEnabled(boolean enable) { + // Ignore + } + + @Override + public int getNavigationMode() { + return mNavigationMode; + } + + @Override + public void setNavigationMode(int mode) { + final int oldMode = mNavigationMode; + if (mode != oldMode) { + switch (oldMode) { + case ActionBar.NAVIGATION_MODE_LIST: + if (mSpinner != null && mSpinner.getParent() == mToolbar) { + mToolbar.removeView(mSpinner); + } + break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabView != null && mTabView.getParent() == mToolbar) { + mToolbar.removeView(mTabView); + } + break; + } + + mNavigationMode = mode; + + switch (mode) { + case ActionBar.NAVIGATION_MODE_STANDARD: + break; + case ActionBar.NAVIGATION_MODE_LIST: + ensureSpinner(); + mToolbar.addView(mSpinner, 0); + break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabView != null) { + mToolbar.addView(mTabView, 0); + Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams(); + lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; + lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + lp.gravity = Gravity.START | Gravity.BOTTOM; + } + break; + default: + throw new IllegalArgumentException("Invalid navigation mode " + mode); + } + } + } + + private void ensureSpinner() { + if (mSpinner == null) { + mSpinner = new SpinnerCompat(getContext(), null, R.attr.actionDropDownStyle); + Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL); + mSpinner.setLayoutParams(lp); + } + } + + @Override + public void setDropdownParams(SpinnerAdapter adapter, + AdapterViewCompat.OnItemSelectedListener listener) { + ensureSpinner(); + mSpinner.setAdapter(adapter); + mSpinner.setOnItemSelectedListener(listener); + } + + @Override + public void setDropdownSelectedPosition(int position) { + if (mSpinner == null) { + throw new IllegalStateException( + "Can't set dropdown selected position without an adapter"); + } + mSpinner.setSelection(position); + } + + @Override + public int getDropdownSelectedPosition() { + return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0; + } + + @Override + public int getDropdownItemCount() { + return mSpinner != null ? mSpinner.getCount() : 0; + } + + @Override + public void setCustomView(View view) { + if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.removeView(mCustomView); + } + mCustomView = view; + if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.addView(mCustomView); + } + } + + @Override + public View getCustomView() { + return mCustomView; + } + + @Override + public void animateToVisibility(int visibility) { + if (visibility == View.GONE) { + ViewCompat.animate(mToolbar).alpha(0) + .setListener(new ViewPropertyAnimatorListenerAdapter() { + private boolean mCanceled = false; + @Override + public void onAnimationEnd(View view) { + if (!mCanceled) { + mToolbar.setVisibility(View.GONE); + } + } + + @Override + public void onAnimationCancel(View view) { + mCanceled = true; + } + }); + } else if (visibility == View.VISIBLE) { + ViewCompat.animate(mToolbar).alpha(1) + .setListener(new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationStart(View view) { + mToolbar.setVisibility(View.VISIBLE); + } + }); + } + } + + @Override + public void setNavigationIcon(Drawable icon) { + mNavIcon = icon; + updateNavigationIcon(); + } + + @Override + public void setNavigationIcon(int resId) { + setNavigationIcon(resId != 0 + ? mTintManager.getDrawable(resId) + : null); + } + + @Override + public void setNavigationContentDescription(CharSequence description) { + mHomeDescription = description; + updateHomeAccessibility(); + } + + @Override + public void setNavigationContentDescription(int resId) { + setNavigationContentDescription(resId == 0 ? null : getContext().getString(resId)); + } + + private void updateHomeAccessibility() { + if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + if (TextUtils.isEmpty(mHomeDescription)) { + mToolbar.setNavigationContentDescription(mDefaultNavigationContentDescription); + } else { + mToolbar.setNavigationContentDescription(mHomeDescription); + } + } + } + + private void updateNavigationIcon() { + if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon); + } + } + + @Override + public void saveHierarchyState(SparseArray toolbarStates) { + mToolbar.saveHierarchyState(toolbarStates); + } + + @Override + public void restoreHierarchyState(SparseArray toolbarStates) { + mToolbar.restoreHierarchyState(toolbarStates); + } + + @Override + public void setBackgroundDrawable(Drawable d) { + //noinspection deprecation + mToolbar.setBackgroundDrawable(d); + } + + @Override + public int getHeight() { + return mToolbar.getHeight(); + } + + @Override + public void setVisibility(int visible) { + mToolbar.setVisibility(visible); + } + + @Override + public int getVisibility() { + return mToolbar.getVisibility(); + } + + @Override + public void setMenuCallbacks(MenuPresenter.Callback actionMenuPresenterCallback, + MenuBuilder.Callback menuBuilderCallback) { + mToolbar.setMenuCallbacks(actionMenuPresenterCallback, menuBuilderCallback); + } + + @Override + public Menu getMenu() { + return mToolbar.getMenu(); + } + + @Override + public int getPopupTheme() { + return mToolbar.getPopupTheme(); + } + +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ViewStubCompat.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ViewStubCompat.java new file mode 100644 index 0000000000..10873a7294 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ViewStubCompat.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.support.v7.appcompat.R; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + + +import java.lang.ref.WeakReference; + +/** + * Backport of {@link android.view.ViewStub} so that we can set the + * {@link android.view.LayoutInflater} on devices before Jelly Bean. + * + * @hide + */ +public final class ViewStubCompat extends View { + private int mLayoutResource = 0; + private int mInflatedId; + + private WeakReference mInflatedViewRef; + + private LayoutInflater mInflater; + private OnInflateListener mInflateListener; + + public ViewStubCompat(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ViewStubCompat(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStubCompat, + defStyle, 0); + + mInflatedId = a.getResourceId(R.styleable.ViewStubCompat_android_inflatedId, NO_ID); + mLayoutResource = a.getResourceId(R.styleable.ViewStubCompat_android_layout, 0); + + setId(a.getResourceId(R.styleable.ViewStubCompat_android_id, NO_ID)); + a.recycle(); + + setVisibility(GONE); + setWillNotDraw(true); + } + + /** + * Returns the id taken by the inflated view. If the inflated id is + * {@link View#NO_ID}, the inflated view keeps its original id. + * + * @return A positive integer used to identify the inflated view or + * {@link #NO_ID} if the inflated view should keep its id. + * + * @see #setInflatedId(int) + * @attr ref android.R.styleable#ViewStub_inflatedId + */ + public int getInflatedId() { + return mInflatedId; + } + + /** + * Defines the id taken by the inflated view. If the inflated id is + * {@link View#NO_ID}, the inflated view keeps its original id. + * + * @param inflatedId A positive integer used to identify the inflated view or + * {@link #NO_ID} if the inflated view should keep its id. + * + * @see #getInflatedId() + * @attr ref android.R.styleable#ViewStub_inflatedId + */ + public void setInflatedId(int inflatedId) { + mInflatedId = inflatedId; + } + + /** + * Returns the layout resource that will be used by {@link #setVisibility(int)} or + * {@link #inflate()} to replace this StubbedView + * in its parent by another view. + * + * @return The layout resource identifier used to inflate the new View. + * + * @see #setLayoutResource(int) + * @see #setVisibility(int) + * @see #inflate() + * @attr ref android.R.styleable#ViewStub_layout + */ + public int getLayoutResource() { + return mLayoutResource; + } + + /** + * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible + * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is + * used to replace this StubbedView in its parent. + * + * @param layoutResource A valid layout resource identifier (different from 0.) + * + * @see #getLayoutResource() + * @see #setVisibility(int) + * @see #inflate() + * @attr ref android.R.styleable#ViewStub_layout + */ + public void setLayoutResource(int layoutResource) { + mLayoutResource = layoutResource; + } + + /** + * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null} + * to use the default. + */ + public void setLayoutInflater(LayoutInflater inflater) { + mInflater = inflater; + } + + /** + * Get current {@link LayoutInflater} used in {@link #inflate()}. + */ + public LayoutInflater getLayoutInflater() { + return mInflater; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(0, 0); + } + + @Override + public void draw(Canvas canvas) { + } + + @Override + protected void dispatchDraw(Canvas canvas) { + } + + /** + * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE}, + * {@link #inflate()} is invoked and this StubbedView is replaced in its parent + * by the inflated layout resource. After that calls to this function are passed + * through to the inflated view. + * + * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. + * + * @see #inflate() + */ + @Override + public void setVisibility(int visibility) { + if (mInflatedViewRef != null) { + View view = mInflatedViewRef.get(); + if (view != null) { + view.setVisibility(visibility); + } else { + throw new IllegalStateException("setVisibility called on un-referenced view"); + } + } else { + super.setVisibility(visibility); + if (visibility == VISIBLE || visibility == INVISIBLE) { + inflate(); + } + } + } + + /** + * Inflates the layout resource identified by {@link #getLayoutResource()} + * and replaces this StubbedView in its parent by the inflated layout resource. + * + * @return The inflated layout resource. + * + */ + public View inflate() { + final ViewParent viewParent = getParent(); + + if (viewParent != null && viewParent instanceof ViewGroup) { + if (mLayoutResource != 0) { + final ViewGroup parent = (ViewGroup) viewParent; + final LayoutInflater factory; + if (mInflater != null) { + factory = mInflater; + } else { + factory = LayoutInflater.from(getContext()); + } + final View view = factory.inflate(mLayoutResource, parent, + false); + + if (mInflatedId != NO_ID) { + view.setId(mInflatedId); + } + + final int index = parent.indexOfChild(this); + parent.removeViewInLayout(this); + + final ViewGroup.LayoutParams layoutParams = getLayoutParams(); + if (layoutParams != null) { + parent.addView(view, index, layoutParams); + } else { + parent.addView(view, index); + } + + mInflatedViewRef = new WeakReference(view); + + if (mInflateListener != null) { + mInflateListener.onInflate(this, view); + } + + return view; + } else { + throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); + } + } else { + throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); + } + } + + /** + * Specifies the inflate listener to be notified after this ViewStub successfully + * inflated its layout resource. + * + * @param inflateListener The OnInflateListener to notify of successful inflation. + * + * @see android.view.ViewStub.OnInflateListener + */ + public void setOnInflateListener(OnInflateListener inflateListener) { + mInflateListener = inflateListener; + } + + /** + * Listener used to receive a notification after a ViewStub has successfully + * inflated its layout resource. + * + * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener) + */ + public static interface OnInflateListener { + /** + * Invoked after a ViewStub successfully inflated its layout resource. + * This method is invoked after the inflated view was added to the + * hierarchy but before the layout pass. + * + * @param stub The ViewStub that initiated the inflation. + * @param inflated The inflated View. + */ + void onInflate(ViewStubCompat stub, View inflated); + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ViewUtils.java b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ViewUtils.java new file mode 100644 index 0000000000..98e55d55f3 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/internal/widget/ViewUtils.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2014 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 android.support.v7.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.os.Build; +import android.support.v4.view.ViewCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.ContextThemeWrapper; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @hide + */ +public class ViewUtils { + private static final String TAG = "ViewUtils"; + + private static Method sComputeFitSystemWindowsMethod; + + static { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + try { + sComputeFitSystemWindowsMethod = View.class.getDeclaredMethod( + "computeFitSystemWindows", Rect.class, Rect.class); + if (!sComputeFitSystemWindowsMethod.isAccessible()) { + sComputeFitSystemWindowsMethod.setAccessible(true); + } + } catch (NoSuchMethodException e) { + Log.d(TAG, "Could not find method computeFitSystemWindows. Oh well."); + } + } + } + + private ViewUtils() {} + + public static boolean isLayoutRtl(View view) { + return ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL; + } + + /** + * Merge two states as returned by {@link ViewCompat#getMeasuredState(android.view.View)} ()}. + * @param curState The current state as returned from a view or the result + * of combining multiple views. + * @param newState The new view state to combine. + * @return Returns a new integer reflecting the combination of the two + * states. + */ + public static int combineMeasuredStates(int curState, int newState) { + return curState | newState; + } + + /** + * Allow calling the hidden method {@code computeFitSystemWindows(Rect, Rect)} through + * reflection on {@code view}. + */ + public static void computeFitSystemWindows(View view, Rect inoutInsets, Rect outLocalInsets) { + if (sComputeFitSystemWindowsMethod != null) { + try { + sComputeFitSystemWindowsMethod.invoke(view, inoutInsets, outLocalInsets); + } catch (Exception e) { + Log.d(TAG, "Could not invoke computeFitSystemWindows", e); + } + } + } + + /** + * Allow calling the hidden method {@code makeOptionalFitsSystem()} through reflection on + * {@code view}. + */ + public static void makeOptionalFitsSystemWindows(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + try { + // We need to use getMethod() for makeOptionalFitsSystemWindows since both View + // and ViewGroup implement the method + Method method = view.getClass().getMethod("makeOptionalFitsSystemWindows"); + if (!method.isAccessible()) { + method.setAccessible(true); + } + method.invoke(view); + } catch (NoSuchMethodException e) { + Log.d(TAG, "Could not find method makeOptionalFitsSystemWindows. Oh well..."); + } catch (InvocationTargetException e) { + Log.d(TAG, "Could not invoke makeOptionalFitsSystemWindows", e); + } catch (IllegalAccessException e) { + Log.d(TAG, "Could not invoke makeOptionalFitsSystemWindows", e); + } + } + } + + /** + * Allows us to emulate the {@code android:theme} attribute for devices before L. + */ + public static Context themifyContext(Context context, AttributeSet attrs, + boolean useAndroidTheme, boolean useAppTheme) { + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0); + int themeId = 0; + if (useAndroidTheme) { + // First try reading android:theme if enabled + themeId = a.getResourceId(R.styleable.View_android_theme, 0); + } + if (useAppTheme && themeId == 0) { + // ...if that didn't work, try reading app:theme (for legacy reasons) if enabled + themeId = a.getResourceId(R.styleable.View_theme, 0); + + if (themeId != 0) { + Log.i(TAG, "app:theme is now deprecated. Please move to using android:theme instead."); + } + } + a.recycle(); + + if (themeId != 0 && (!(context instanceof ContextThemeWrapper) + || ((ContextThemeWrapper) context).getThemeResId() != themeId)) { + // If the context isn't a ContextThemeWrapperCompat, or it is but does not have + // the same theme as we need, wrap it in a new wrapper + context = new ContextThemeWrapper(context, themeId); + } + return context; + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/view/ActionMode.java b/eclipse-compile/appcompat/src/android/support/v7/view/ActionMode.java new file mode 100644 index 0000000000..ff9cd495f4 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/view/ActionMode.java @@ -0,0 +1,279 @@ +/* + * 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 android.support.v7.view; + +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +/** + * Represents a contextual mode of the user interface. Action modes can be used to provide + * alternative interaction modes and replace parts of the normal UI until finished. + * Examples of good action modes include text selection and contextual actions. + *

+ * + *

Developer Guides

+ *

For information about how to provide contextual actions with {@code ActionMode}, + * read the Menus + * developer guide.

+ * + *
+ */ +public abstract class ActionMode { + + private Object mTag; + private boolean mTitleOptionalHint; + + /** + * 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 whether or not the title/subtitle display for this action mode + * is optional. + * + *

In many cases the supplied title for an action mode is merely + * meant to add context and is not strictly required for the action + * mode to be useful. If the title is optional, the system may choose + * to hide the title entirely rather than truncate it due to a lack + * of available space.

+ * + *

Note that this is merely a hint; the underlying implementation + * may choose to ignore this setting under some circumstances.

+ * + * @param titleOptional true if the title only presents optional information. + */ + public void setTitleOptionalHint(boolean titleOptional) { + mTitleOptionalHint = titleOptional; + } + + /** + * @return true if this action mode has been given a hint to consider the + * title/subtitle display to be optional. + * + * @see #setTitleOptionalHint(boolean) + * @see #isTitleOptional() + */ + public boolean getTitleOptionalHint() { + return mTitleOptionalHint; + } + + /** + * @return true if this action mode considers the title and subtitle fields + * as optional. Optional titles may not be displayed to the user. + */ + public boolean isTitleOptional() { + return false; + } + + /** + * 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 android.support.v7.app.AppCompatDelegate#startSupportActionMode(Callback)} (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); + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/view/CollapsibleActionView.java b/eclipse-compile/appcompat/src/android/support/v7/view/CollapsibleActionView.java new file mode 100644 index 0000000000..3f01c5cb84 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/view/CollapsibleActionView.java @@ -0,0 +1,42 @@ +/* + * 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 android.support.v7.view; + +import android.support.v4.view.MenuItemCompat.OnActionExpandListener; + +/** + * When a {@link android.view.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 android.support.v4.view.MenuItemCompat} 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 + * android.support.v4.view.MenuItemCompat#expandActionView(android.view.MenuItem)}. + */ + public void onActionViewExpanded(); + + /** + * Called when this view is collapsed as an action view. See {@link + * android.support.v4.view.MenuItemCompat#collapseActionView(android.view.MenuItem)}. + */ + public void onActionViewCollapsed(); +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java b/eclipse-compile/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java new file mode 100644 index 0000000000..054fb7875f --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java @@ -0,0 +1,769 @@ +/* + * Copyright (C) 2014 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 android.support.v7.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v4.view.ActionProvider; +import android.support.v4.view.GravityCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.transition.ActionBarTransition; +import android.support.v7.internal.view.ActionBarPolicy; +import android.support.v7.internal.view.renamemenu.ActionMenuItemView; +import android.support.v7.internal.view.renamemenu.BaseMenuPresenter; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuItemImpl; +import android.support.v7.internal.view.renamemenu.MenuPopupHelper; +import android.support.v7.internal.view.renamemenu.MenuView; +import android.support.v7.internal.view.renamemenu.SubMenuBuilder; +import android.support.v7.internal.widget.TintImageView; +import android.util.SparseBooleanArray; +import android.view.MenuItem; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * MenuPresenter for building action menus as seen in the action bar and action modes. + * + * @hide + */ +public class ActionMenuPresenter extends BaseMenuPresenter + implements ActionProvider.SubUiVisibilityListener { + + 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; + private ActionMenuPopupCallback mPopupCallback; + + final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); + int mOpenSubMenuId; + + public ActionMenuPresenter(Context context) { + super(context, R.layout.abc_action_menu_layout, R.layout.abc_action_menu_item_layout); + } + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + super.initForMenu(context, menu); + + final Resources res = context.getResources(); + + final ActionBarPolicy abp = ActionBarPolicy.get(context); + if (!mReserveOverflowSet) { + mReserveOverflow = abp.showsOverflowMenuButton(); + } + + if (!mWidthLimitSet) { + mWidthLimit = abp.getEmbeddedMenuWidthLimit(); + } + + // Measure for initial configuration + if (!mMaxItemsSet) { + mMaxItems = abp.getMaxActionButtons(); + } + + 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 void onConfigurationChanged(Configuration newConfig) { + if (!mMaxItemsSet) { + mMaxItems = mContext.getResources().getInteger( + R.integer.abc_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(final MenuItemImpl item, View convertView, ViewGroup parent) { + View actionView = item.getActionView(); + if (actionView == null || item.hasCollapsibleActionView()) { + 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; + final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView; + actionItemView.setItemInvoker(menuView); + + if (mPopupCallback == null) { + mPopupCallback = new ActionMenuPopupCallback(); + } + actionItemView.setPopupCallback(mPopupCallback); + } + + @Override + public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) { + return item.isActionButton(); + } + + @Override + public void updateMenuView(boolean cleared) { + final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent(); + if (menuViewParent != null) { + ActionBarTransition.beginDelayedTransition(menuViewParent); + } + super.updateMenuView(cleared); + + ((View) mMenuView).requestLayout(); + + 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).getSupportActionProvider(); + 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 && !mMenu.getNonActionItems().isEmpty()) { + 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(); + } + + public boolean isOverflowMenuShowPending() { + return mPostedOpenRunnable != null || isOverflowMenuShowing(); + } + + /** + * @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); + } else { + // Neither requires nor requests an action button. + item.setIsActionButton(false); + } + } + 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); + } + } + + public void setMenuView(ActionMenuView menuView) { + mMenuView = menuView; + menuView.initialize(mMenu); + } + + 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); + } + + 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 TintImageView implements ActionMenuView.ActionMenuChildView { + private final float[] mTempPts = new float[2]; + + public OverflowMenuButton(Context context) { + super(context, null, R.attr.actionOverflowButtonStyle); + + setClickable(true); + setFocusable(true); + setVisibility(VISIBLE); + setEnabled(true); + + setOnTouchListener(new ListPopupWindow.ForwardingListener(this) { + @Override + public ListPopupWindow getPopup() { + if (mOverflowPopup == null) { + return null; + } + + return mOverflowPopup.getPopup(); + } + + @Override + public boolean onForwardingStarted() { + showOverflowMenu(); + return true; + } + + @Override + public boolean onForwardingStopped() { + // Displaying the popup occurs asynchronously, so wait for + // the runnable to finish before deciding whether to stop + // forwarding. + if (mPostedOpenRunnable != null) { + return false; + } + + hideOverflowMenu(); + return true; + } + }); + } + + @Override + public boolean performClick() { + if (super.performClick()) { + return true; + } + + playSoundEffect(SoundEffectConstants.CLICK); + showOverflowMenu(); + return true; + } + + @Override + public boolean needsDividerBefore() { + return false; + } + + @Override + public boolean needsDividerAfter() { + return false; + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + final boolean changed = super.setFrame(l, t, r, b); + + // Set up the hotspot bounds to be centered on the image. + final Drawable d = getDrawable(); + final Drawable bg = getBackground(); + if (d != null && bg != null) { + final int width = getWidth(); + final int height = getHeight(); + final int halfEdge = Math.max(width, height) / 2; + final int offsetX = getPaddingLeft() - getPaddingRight(); + final int offsetY = getPaddingTop() - getPaddingBottom(); + final int centerX = (width + offsetX) / 2; + final int centerY = (height + offsetY) / 2; + DrawableCompat.setHotspotBounds(bg, centerX - halfEdge, centerY - halfEdge, + centerX + halfEdge, centerY + halfEdge); + } + + return changed; + } + } + + private class OverflowPopup extends MenuPopupHelper { + + public OverflowPopup(Context context, MenuBuilder menu, View anchorView, + boolean overflowOnly) { + super(context, menu, anchorView, overflowOnly, R.attr.actionOverflowMenuStyle); + setGravity(GravityCompat.END); + setCallback(mPopupPresenterCallback); + } + + @Override + public void onDismiss() { + super.onDismiss(); + mMenu.close(); + mOverflowPopup = null; + } + } + + private class ActionButtonSubmenu extends MenuPopupHelper { + private SubMenuBuilder mSubMenu; + + public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) { + super(context, subMenu, null, false, + R.attr.actionOverflowMenuStyle); + 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 Callback { + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + if (subMenu == null) return false; + + mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId(); + final Callback cb = getCallback(); + return cb != null ? cb.onOpenSubMenu(subMenu) : false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (menu instanceof SubMenuBuilder) { + ((SubMenuBuilder) menu).getRootMenu().close(false); + } + final Callback cb = getCallback(); + if (cb != null) { + cb.onCloseMenu(menu, allMenusAreClosing); + } + } + } + + 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; + } + } + + private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback { + @Override + public ListPopupWindow getPopup() { + return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null; + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/ActionMenuView.java b/eclipse-compile/appcompat/src/android/support/v7/widget/ActionMenuView.java new file mode 100644 index 0000000000..811f468186 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/ActionMenuView.java @@ -0,0 +1,811 @@ +/* + * Copyright (C) 2014 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 android.support.v7.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; +import android.support.v7.internal.view.renamemenu.ActionMenuItemView; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuItemImpl; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.support.v7.internal.view.renamemenu.MenuView; +import android.support.v7.internal.widget.ViewUtils; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; + +/** + * ActionMenuView is a presentation of a series of menu options as a View. It provides + * several top level options as action buttons while spilling remaining options over as + * items in an overflow menu. This allows applications to present packs of actions inline with + * specific or repeating content. + */ +public class ActionMenuView extends LinearLayoutCompat implements MenuBuilder.ItemInvoker, + MenuView { + + private static final String TAG = "ActionMenuView"; + + static final int MIN_CELL_SIZE = 56; // dips + static final int GENERATED_ITEM_PADDING = 4; // dips + + private MenuBuilder mMenu; + + private Context mContext; + + /** Context against which to inflate popup menus. */ + private Context mPopupContext; + + /** Theme resource against which to inflate popup menus. */ + private int mPopupTheme; + + private boolean mReserveOverflow; + private ActionMenuPresenter mPresenter; + private MenuPresenter.Callback mActionMenuPresenterCallback; + private MenuBuilder.Callback mMenuBuilderCallback; + private boolean mFormatItems; + private int mFormatItemsWidth; + private int mMinCellSize; + private int mGeneratedItemPadding; + + private OnMenuItemClickListener mOnMenuItemClickListener; + + public ActionMenuView(Context context) { + this(context, null); + } + + public ActionMenuView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + setBaselineAligned(false); + final float density = context.getResources().getDisplayMetrics().density; + mMinCellSize = (int) (MIN_CELL_SIZE * density); + mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density); + mPopupContext = context; + mPopupTheme = 0; + } + + /** + * Specifies the theme to use when inflating popup menus. By default, uses + * the same theme as the action menu view itself. + * + * @param resId theme used to inflate popup menus + * @see #getPopupTheme() + */ + public void setPopupTheme(int resId) { + if (mPopupTheme != resId) { + mPopupTheme = resId; + if (resId == 0) { + mPopupContext = mContext; + } else { + mPopupContext = new ContextThemeWrapper(mContext, resId); + } + } + } + + /** + * @return resource identifier of the theme used to inflate popup menus, or + * 0 if menus are inflated against the action menu view theme + * @see #setPopupTheme(int) + */ + public int getPopupTheme() { + return mPopupTheme; + } + + /** + * @param presenter Menu presenter used to display popup menu + * @hide + */ + public void setPresenter(ActionMenuPresenter presenter) { + mPresenter = presenter; + mPresenter.setMenuView(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (Build.VERSION.SDK_INT >= 8) { + super.onConfigurationChanged(newConfig); + } + + if (mPresenter != null) { + mPresenter.updateMenuView(false); + + if (mPresenter.isOverflowMenuShowing()) { + mPresenter.hideOverflowMenu(); + mPresenter.showOverflowMenu(); + } + } + } + + public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { + mOnMenuItemClickListener = listener; + } + + @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.getSize(widthMeasureSpec); + if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) { + mFormatItemsWidth = widthSize; + mMenu.onItemsChanged(true); + } + + final int childCount = getChildCount(); + if (mFormatItems && childCount > 0) { + onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec); + } else { + // Previous measurement at exact format may have set margins - reset them. + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.leftMargin = lp.rightMargin = 0; + } + 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(); + + final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding, + ViewGroup.LayoutParams.WRAP_CONTENT); + + 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, + itemHeightSpec, 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) { + 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), + itemHeightSpec); + } + } + + if (heightMode != MeasureSpec.EXACTLY) { + heightSize = maxChildHeight; + } + + setMeasuredDimension(widthSize, heightSize); + } + + /** + * 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); + + final ActionMenuItemView itemView = child instanceof ActionMenuItemView ? + (ActionMenuItemView) child : null; + final boolean hasText = itemView != null && itemView.hasText(); + + int cellsUsed = 0; + if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) { + 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++; + if (hasText && cellsUsed < 2) cellsUsed = 2; + } + + final boolean expandable = !lp.isOverflowButton && 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 = (bottom - top) / 2; + final int dividerWidth = getDividerWidth(); + int overflowWidth = 0; + int nonOverflowWidth = 0; + int nonOverflowCount = 0; + int widthRemaining = right - left - getPaddingRight() - getPaddingLeft(); + boolean hasOverflow = false; + final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this); + 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 (hasSupportDividerBeforeChildAt(i)) { + overflowWidth += dividerWidth; + } + int height = v.getMeasuredHeight(); + int r; + int l; + if (isLayoutRtl) { + l = getPaddingLeft() + p.leftMargin; + r = l + overflowWidth; + } else { + r = getWidth() - getPaddingRight() - p.rightMargin; + 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; + nonOverflowWidth += size; + widthRemaining -= size; + if (hasSupportDividerBeforeChildAt(i)) { + 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); + + if (isLayoutRtl) { + int startRight = getWidth() - getPaddingRight(); + 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; + } + + startRight -= lp.rightMargin; + int width = v.getMeasuredWidth(); + int height = v.getMeasuredHeight(); + int t = midVertical - height / 2; + v.layout(startRight - width, t, startRight, t + height); + startRight -= width + lp.leftMargin + spacerSize; + } + } else { + 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(); + dismissPopupMenus(); + } + + /** @hide */ + public boolean isOverflowReserved() { + return mReserveOverflow; + } + + /** @hide */ + 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 != null) { + final LayoutParams result = p instanceof LayoutParams + ? new LayoutParams((LayoutParams) p) + : new 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; + } + + /** @hide */ + public LayoutParams generateOverflowButtonLayoutParams() { + LayoutParams result = generateDefaultLayoutParams(); + result.isOverflowButton = true; + return result; + } + + /** @hide */ + public boolean invokeItem(MenuItemImpl item) { + return mMenu.performItemAction(item, 0); + } + + /** @hide */ + public int getWindowAnimations() { + return 0; + } + + /** @hide */ + public void initialize(MenuBuilder menu) { + mMenu = menu; + } + + /** + * Returns the Menu object that this ActionMenuView is currently presenting. + * + *

Applications should use this method to obtain the ActionMenuView's Menu object + * and inflate or add content to it as necessary.

+ * + * @return the Menu presented by this view + */ + public Menu getMenu() { + if (mMenu == null) { + final Context context = getContext(); + mMenu = new MenuBuilder(context); + mMenu.setCallback(new MenuBuilderCallback()); + mPresenter = new ActionMenuPresenter(context); + mPresenter.setReserveOverflow(true); + mPresenter.setCallback(mActionMenuPresenterCallback != null + ? mActionMenuPresenterCallback : new ActionMenuPresenterCallback()); + mMenu.addMenuPresenter(mPresenter, mPopupContext); + mPresenter.setMenuView(this); + } + + return mMenu; + } + + /** + * Must be called before the first call to getMenu() + * @hide + */ + public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) { + mActionMenuPresenterCallback = pcb; + mMenuBuilderCallback = mcb; + } + + /** + * Returns the current menu or null if one has not yet been configured. + * @hide Internal use only for action bar integration + */ + public MenuBuilder peekMenu() { + return mMenu; + } + + /** + * Show the overflow items from the associated menu. + * + * @return true if the menu was able to be shown, false otherwise + */ + public boolean showOverflowMenu() { + return mPresenter != null && mPresenter.showOverflowMenu(); + } + + /** + * Hide the overflow items from the associated menu. + * + * @return true if the menu was able to be hidden, false otherwise + */ + public boolean hideOverflowMenu() { + return mPresenter != null && mPresenter.hideOverflowMenu(); + } + + /** + * Check whether the overflow menu is currently showing. This may not reflect + * a pending show operation in progress. + * + * @return true if the overflow menu is currently showing + */ + public boolean isOverflowMenuShowing() { + return mPresenter != null && mPresenter.isOverflowMenuShowing(); + } + + /** @hide */ + public boolean isOverflowMenuShowPending() { + return mPresenter != null && mPresenter.isOverflowMenuShowPending(); + } + + /** + * Dismiss any popups associated with this menu view. + */ + public void dismissPopupMenus() { + if (mPresenter != null) { + mPresenter.dismissPopupMenus(); + } + } + + /** + * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public. + */ + protected boolean hasSupportDividerBeforeChildAt(int childIndex) { + if (childIndex == 0) { + return false; + } + 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; + } + + /** @hide */ + public void setExpandedActionViewsExclusive(boolean exclusive) { + mPresenter.setExpandedActionViewsExclusive(exclusive); + } + + /** + * Interface responsible for receiving menu item click events if the items themselves + * do not have individual item click listeners. + */ + public interface OnMenuItemClickListener { + /** + * This method will be invoked when a menu item is clicked if the item itself did + * not already handle the event. + * + * @param item {@link MenuItem} that was clicked + * @return true if the event was handled, false otherwise. + */ + public boolean onMenuItemClick(MenuItem item); + } + + private class MenuBuilderCallback implements MenuBuilder.Callback { + @Override + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + return mOnMenuItemClickListener != null && + mOnMenuItemClickListener.onMenuItemClick(item); + } + + @Override + public void onMenuModeChange(MenuBuilder menu) { + if (mMenuBuilderCallback != null) { + mMenuBuilderCallback.onMenuModeChange(menu); + } + } + } + + private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback { + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + } + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + return false; + } + } + + /** @hide */ + public interface ActionMenuChildView { + public boolean needsDividerBefore(); + public boolean needsDividerAfter(); + } + + public static class LayoutParams extends LinearLayoutCompat.LayoutParams { + + @ViewDebug.ExportedProperty() + public boolean isOverflowButton; + + @ViewDebug.ExportedProperty() + public int cellsUsed; + + @ViewDebug.ExportedProperty() + public int extraPixels; + + @ViewDebug.ExportedProperty() + public boolean expandable; + + @ViewDebug.ExportedProperty() + public boolean preventEdgeOffset; + + boolean expanded; + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(ViewGroup.LayoutParams other) { + super(other); + } + + public LayoutParams(LayoutParams other) { + super((ViewGroup.LayoutParams) other); + isOverflowButton = other.isOverflowButton; + } + + public LayoutParams(int width, int height) { + super(width, height); + isOverflowButton = false; + } + + LayoutParams(int width, int height, boolean isOverflowButton) { + super(width, height); + this.isOverflowButton = isOverflowButton; + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java b/eclipse-compile/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java new file mode 100644 index 0000000000..b89115c0aa --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java @@ -0,0 +1,1837 @@ +/* + * Copyright (C) 2014 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 android.support.v7.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.IntDef; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.ViewCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.widget.TintTypedArray; +import android.support.v7.internal.widget.ViewUtils; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + * A Layout that arranges its children in a single column or a single row. The direction of + * the row can be set by calling {@link #setOrientation(int) setOrientation()}. + * You can also specify gravity, which specifies the alignment of all the child elements by + * calling {@link #setGravity(int) setGravity()} or specify that specific children + * grow to fill up any remaining space in the layout by setting the weight member of + * {@link LinearLayoutCompat.LayoutParams LinearLayoutCompat.LayoutParams}. + * The default orientation is horizontal. + * + *

See the Linear Layout + * guide.

+ * + *

+ * Also see {@link LinearLayoutCompat.LayoutParams} for layout attributes

+ */ +public class LinearLayoutCompat extends ViewGroup { + /** @hide */ + @IntDef({HORIZONTAL, VERTICAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface OrientationMode {} + + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + + /** @hide */ + @IntDef(flag = true, + value = { + SHOW_DIVIDER_NONE, + SHOW_DIVIDER_BEGINNING, + SHOW_DIVIDER_MIDDLE, + SHOW_DIVIDER_END + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DividerMode {} + + /** + * 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; + + /** + * Whether the children of this layout are baseline aligned. Only applicable + * if {@link #mOrientation} is horizontal. + */ + private boolean mBaselineAligned = true; + + /** + * If this layout is part of another layout that is baseline aligned, + * use the child at this index as the baseline. + * + * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned + * with whether the children of this layout are baseline aligned. + */ + private int mBaselineAlignedChildIndex = -1; + + /** + * The additional offset to the child's baseline. + * We'll calculate the baseline of this layout as we measure vertically; for + * horizontal linear layouts, the offset of 0 is appropriate. + */ + private int mBaselineChildTop = 0; + + private int mOrientation; + + private int mGravity = GravityCompat.START | Gravity.TOP; + + private int mTotalLength; + + private float mWeightSum; + + private boolean mUseLargestChild; + + private int[] mMaxAscent; + private int[] mMaxDescent; + + private static final int VERTICAL_GRAVITY_COUNT = 4; + + private static final int INDEX_CENTER_VERTICAL = 0; + private static final int INDEX_TOP = 1; + private static final int INDEX_BOTTOM = 2; + private static final int INDEX_FILL = 3; + + private Drawable mDivider; + private int mDividerWidth; + private int mDividerHeight; + private int mShowDividers; + private int mDividerPadding; + + public LinearLayoutCompat(Context context) { + this(context, null); + } + + public LinearLayoutCompat(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, + R.styleable.LinearLayoutCompat, defStyleAttr, 0); + + int index = a.getInt(R.styleable.LinearLayoutCompat_android_orientation, -1); + if (index >= 0) { + setOrientation(index); + } + + index = a.getInt(R.styleable.LinearLayoutCompat_android_gravity, -1); + if (index >= 0) { + setGravity(index); + } + + boolean baselineAligned = a.getBoolean(R.styleable.LinearLayoutCompat_android_baselineAligned, true); + if (!baselineAligned) { + setBaselineAligned(baselineAligned); + } + + mWeightSum = a.getFloat(R.styleable.LinearLayoutCompat_android_weightSum, -1.0f); + + mBaselineAlignedChildIndex = + a.getInt(R.styleable.LinearLayoutCompat_android_baselineAlignedChildIndex, -1); + + mUseLargestChild = a.getBoolean(R.styleable.LinearLayoutCompat_measureWithLargestChild, false); + + setDividerDrawable(a.getDrawable(R.styleable.LinearLayoutCompat_divider)); + mShowDividers = a.getInt(R.styleable.LinearLayoutCompat_showDividers, SHOW_DIVIDER_NONE); + mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayoutCompat_dividerPadding, 0); + + a.recycle(); + } + + /** + * Set how dividers should be shown between items in this layout + * + * @param showDividers One or more of {@link #SHOW_DIVIDER_BEGINNING}, + * {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END}, + * or {@link #SHOW_DIVIDER_NONE} to show no dividers. + */ + public void setShowDividers(@DividerMode int showDividers) { + if (showDividers != mShowDividers) { + requestLayout(); + } + mShowDividers = showDividers; + } + + @Override + public boolean shouldDelayChildPressedState() { + return false; + } + + /** + * @return A flag set indicating how dividers should be shown around items. + * @see #setShowDividers(int) + */ + @DividerMode + public int getShowDividers() { + return mShowDividers; + } + + /** + * @return the divider Drawable that will divide each item. + * + * @see #setDividerDrawable(Drawable) + */ + public Drawable getDividerDrawable() { + return mDivider; + } + + /** + * 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(); + mDividerHeight = divider.getIntrinsicHeight(); + } else { + mDividerWidth = 0; + mDividerHeight = 0; + } + setWillNotDraw(divider == null); + requestLayout(); + } + + /** + * Set padding displayed on both ends of dividers. + * + * @param padding Padding value in pixels that will be applied to each end + * + * @see #setShowDividers(int) + * @see #setDividerDrawable(Drawable) + * @see #getDividerPadding() + */ + public void setDividerPadding(int padding) { + mDividerPadding = padding; + } + + /** + * Get the padding size used to inset dividers in pixels + * + * @see #setShowDividers(int) + * @see #setDividerDrawable(Drawable) + * @see #setDividerPadding(int) + */ + public int getDividerPadding() { + return mDividerPadding; + } + + /** + * Get the width of the current divider drawable. + * + * @hide Used internally by framework. + */ + public int getDividerWidth() { + return mDividerWidth; + } + + @Override + protected void onDraw(Canvas canvas) { + if (mDivider == null) { + return; + } + + if (mOrientation == VERTICAL) { + drawDividersVertical(canvas); + } else { + drawDividersHorizontal(canvas); + } + } + + void drawDividersVertical(Canvas canvas) { + final int count = getVirtualChildCount(); + for (int i = 0; i < count; i++) { + final View child = getVirtualChildAt(i); + + if (child != null && child.getVisibility() != GONE) { + if (hasDividerBeforeChildAt(i)) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int top = child.getTop() - lp.topMargin - mDividerHeight; + drawHorizontalDivider(canvas, top); + } + } + } + + if (hasDividerBeforeChildAt(count)) { + final View child = getVirtualChildAt(count - 1); + int bottom = 0; + if (child == null) { + bottom = getHeight() - getPaddingBottom() - mDividerHeight; + } else { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + bottom = child.getBottom() + lp.bottomMargin; + } + drawHorizontalDivider(canvas, bottom); + } + } + + void drawDividersHorizontal(Canvas canvas) { + final int count = getVirtualChildCount(); + final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this); + for (int i = 0; i < count; i++) { + final View child = getVirtualChildAt(i); + + if (child != null && child.getVisibility() != GONE) { + if (hasDividerBeforeChildAt(i)) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int position; + if (isLayoutRtl) { + position = child.getRight() + lp.rightMargin; + } else { + position = child.getLeft() - lp.leftMargin - mDividerWidth; + } + drawVerticalDivider(canvas, position); + } + } + } + + if (hasDividerBeforeChildAt(count)) { + final View child = getVirtualChildAt(count - 1); + int position; + if (child == null) { + if (isLayoutRtl) { + position = getPaddingLeft(); + } else { + position = getWidth() - getPaddingRight() - mDividerWidth; + } + } else { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (isLayoutRtl) { + position = child.getLeft() - lp.leftMargin - mDividerWidth; + } else { + position = child.getRight() + lp.rightMargin; + } + } + drawVerticalDivider(canvas, position); + } + } + + void drawHorizontalDivider(Canvas canvas, int top) { + mDivider.setBounds(getPaddingLeft() + mDividerPadding, top, + getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight); + mDivider.draw(canvas); + } + + void drawVerticalDivider(Canvas canvas, int left) { + mDivider.setBounds(left, getPaddingTop() + mDividerPadding, + left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding); + mDivider.draw(canvas); + } + + /** + *

Indicates whether widgets contained within this layout are aligned + * on their baseline or not.

+ * + * @return true when widgets are baseline-aligned, false otherwise + */ + public boolean isBaselineAligned() { + return mBaselineAligned; + } + + /** + *

Defines whether widgets contained in this layout are + * baseline-aligned or not.

+ * + * @param baselineAligned true to align widgets on their baseline, + * false otherwise + */ + public void setBaselineAligned(boolean baselineAligned) { + mBaselineAligned = baselineAligned; + } + + /** + * When true, all children with a weight will be considered having + * the minimum size of the largest child. If false, all children are + * measured normally. + * + * @return True to measure children with a weight using the minimum + * size of the largest child, false otherwise. + */ + public boolean isMeasureWithLargestChildEnabled() { + return mUseLargestChild; + } + + /** + * When set to true, all children with a weight will be considered having + * the minimum size of the largest child. If false, all children are + * measured normally. + * + * Disabled by default. + * + * @param enabled True to measure children with a weight using the + * minimum size of the largest child, false otherwise. + */ + public void setMeasureWithLargestChildEnabled(boolean enabled) { + mUseLargestChild = enabled; + } + + @Override + public int getBaseline() { + if (mBaselineAlignedChildIndex < 0) { + return super.getBaseline(); + } + + if (getChildCount() <= mBaselineAlignedChildIndex) { + throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " + + "set to an index that is out of bounds."); + } + + final View child = getChildAt(mBaselineAlignedChildIndex); + final int childBaseline = child.getBaseline(); + + if (childBaseline == -1) { + if (mBaselineAlignedChildIndex == 0) { + // this is just the default case, safe to return -1 + return -1; + } + // the user picked an index that points to something that doesn't + // know how to calculate its baseline. + throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " + + "points to a View that doesn't know how to get its baseline."); + } + + // TODO: This should try to take into account the virtual offsets + // (See getNextLocationOffset and getLocationOffset) + // We should add to childTop: + // sum([getNextLocationOffset(getChildAt(i)) / i < mBaselineAlignedChildIndex]) + // and also add: + // getLocationOffset(child) + int childTop = mBaselineChildTop; + + if (mOrientation == VERTICAL) { + final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; + if (majorGravity != Gravity.TOP) { + switch (majorGravity) { + case Gravity.BOTTOM: + childTop = getBottom() - getTop() - getPaddingBottom() - mTotalLength; + break; + + case Gravity.CENTER_VERTICAL: + childTop += ((getBottom() - getTop() - getPaddingTop() - getPaddingBottom()) - + mTotalLength) / 2; + break; + } + } + } + + LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); + return childTop + lp.topMargin + childBaseline; + } + + /** + * @return The index of the child that will be used if this layout is + * part of a larger layout that is baseline aligned, or -1 if none has + * been set. + */ + public int getBaselineAlignedChildIndex() { + return mBaselineAlignedChildIndex; + } + + /** + * @param i The index of the child that will be used if this layout is + * part of a larger layout that is baseline aligned. + */ + public void setBaselineAlignedChildIndex(int i) { + if ((i < 0) || (i >= getChildCount())) { + throw new IllegalArgumentException("base aligned child index out " + + "of range (0, " + getChildCount() + ")"); + } + mBaselineAlignedChildIndex = i; + } + + /** + *

Returns the view at the specified index. This method can be overriden + * to take into account virtual children. Refer to + * {@link android.widget.TableLayout} and {@link android.widget.TableRow} + * for an example.

+ * + * @param index the child's index + * @return the child at the specified index + */ + View getVirtualChildAt(int index) { + return getChildAt(index); + } + + /** + *

Returns the virtual number of children. This number might be different + * than the actual number of children if the layout can hold virtual + * children. Refer to + * {@link android.widget.TableLayout} and {@link android.widget.TableRow} + * for an example.

+ * + * @return the virtual number of children + */ + int getVirtualChildCount() { + return getChildCount(); + } + + /** + * Returns the desired weights sum. + * + * @return A number greater than 0.0f if the weight sum is defined, or + * a number lower than or equals to 0.0f if not weight sum is + * to be used. + */ + public float getWeightSum() { + return mWeightSum; + } + + /** + * Defines the desired weights sum. If unspecified the weights sum is computed + * at layout time by adding the layout_weight of each child. + * + * This can be used for instance to give a single child 50% of the total + * available space by giving it a layout_weight of 0.5 and setting the + * weightSum to 1.0. + * + * @param weightSum a number greater than 0.0f, or a number lower than or equals + * to 0.0f if the weight sum should be computed from the children's + * layout_weight + */ + public void setWeightSum(float weightSum) { + mWeightSum = Math.max(0.0f, weightSum); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mOrientation == VERTICAL) { + measureVertical(widthMeasureSpec, heightMeasureSpec); + } else { + measureHorizontal(widthMeasureSpec, heightMeasureSpec); + } + } + + /** + * 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; + } + + /** + * Measures the children when the orientation of this LinearLayout is set + * to {@link #VERTICAL}. + * + * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. + * @param heightMeasureSpec Vertical space requirements as imposed by the parent. + * + * @see #getOrientation() + * @see #setOrientation(int) + * @see #onMeasure(int, int) + */ + void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { + mTotalLength = 0; + int maxWidth = 0; + int childState = 0; + int alternativeMaxWidth = 0; + int weightedMaxWidth = 0; + boolean allFillParent = true; + float totalWeight = 0; + + final int count = getVirtualChildCount(); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + boolean matchWidth = false; + boolean skippedMeasure = false; + + final int baselineChildIndex = mBaselineAlignedChildIndex; + final boolean useLargestChild = mUseLargestChild; + + int largestChildHeight = Integer.MIN_VALUE; + + // See how tall everyone is. Also remember max width. + for (int i = 0; i < count; ++i) { + final View child = getVirtualChildAt(i); + + if (child == null) { + mTotalLength += measureNullChild(i); + continue; + } + + if (child.getVisibility() == View.GONE) { + i += getChildrenSkipCount(child, i); + continue; + } + + if (hasDividerBeforeChildAt(i)) { + mTotalLength += mDividerHeight; + } + + LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); + + totalWeight += lp.weight; + + if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { + // Optimization: don't bother measuring children who are going to use + // leftover space. These views will get measured again down below if + // there is any leftover space. + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); + skippedMeasure = true; + } else { + int oldHeight = Integer.MIN_VALUE; + + if (lp.height == 0 && lp.weight > 0) { + // heightMode is either UNSPECIFIED or AT_MOST, and this + // child wanted to stretch to fill available space. + // Translate that to WRAP_CONTENT so that it does not end up + // with a height of 0 + oldHeight = 0; + lp.height = LayoutParams.WRAP_CONTENT; + } + + // Determine how big this child would like to be. If this or + // previous children have given a weight, then we allow it to + // use all available space (and we will shrink things later + // if needed). + measureChildBeforeLayout( + child, i, widthMeasureSpec, 0, heightMeasureSpec, + totalWeight == 0 ? mTotalLength : 0); + + if (oldHeight != Integer.MIN_VALUE) { + lp.height = oldHeight; + } + + final int childHeight = child.getMeasuredHeight(); + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + + lp.bottomMargin + getNextLocationOffset(child)); + + if (useLargestChild) { + largestChildHeight = Math.max(childHeight, largestChildHeight); + } + } + + /** + * If applicable, compute the additional offset to the child's baseline + * we'll need later when asked {@link #getBaseline}. + */ + if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) { + mBaselineChildTop = mTotalLength; + } + + // if we are trying to use a child index for our baseline, the above + // book keeping only works if there are no children above it with + // weight. fail fast to aid the developer. + if (i < baselineChildIndex && lp.weight > 0) { + throw new RuntimeException("A child of LinearLayout with index " + + "less than mBaselineAlignedChildIndex has weight > 0, which " + + "won't work. Either remove the weight, or don't set " + + "mBaselineAlignedChildIndex."); + } + + boolean matchWidthLocally = false; + if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) { + // The width of the linear layout will scale, and at least one + // child said it wanted to match our width. Set a flag + // indicating that we need to remeasure at least that view when + // we know our width. + matchWidth = true; + matchWidthLocally = true; + } + + final int margin = lp.leftMargin + lp.rightMargin; + final int measuredWidth = child.getMeasuredWidth() + margin; + maxWidth = Math.max(maxWidth, measuredWidth); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(child)); + + allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT; + if (lp.weight > 0) { + /* + * Widths of weighted Views are bogus if we end up + * remeasuring, so keep them separate. + */ + weightedMaxWidth = Math.max(weightedMaxWidth, + matchWidthLocally ? margin : measuredWidth); + } else { + alternativeMaxWidth = Math.max(alternativeMaxWidth, + matchWidthLocally ? margin : measuredWidth); + } + + i += getChildrenSkipCount(child, i); + } + + if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { + mTotalLength += mDividerHeight; + } + + if (useLargestChild && + (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) { + mTotalLength = 0; + + for (int i = 0; i < count; ++i) { + final View child = getVirtualChildAt(i); + + if (child == null) { + mTotalLength += measureNullChild(i); + continue; + } + + if (child.getVisibility() == GONE) { + i += getChildrenSkipCount(child, i); + continue; + } + + final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) + child.getLayoutParams(); + // Account for negative margins + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); + } + } + + // Add in our padding + mTotalLength += getPaddingTop() + getPaddingBottom(); + + int heightSize = mTotalLength; + + // Check against our minimum height + heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); + + // Reconcile our calculated size with the heightMeasureSpec + int heightSizeAndState = ViewCompat.resolveSizeAndState(heightSize, heightMeasureSpec, 0); + heightSize = heightSizeAndState & ViewCompat.MEASURED_SIZE_MASK; + + // Either expand children with weight to take up available space or + // shrink them if they extend beyond our current bounds. If we skipped + // measurement on any children, we need to measure them now. + int delta = heightSize - mTotalLength; + if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { + float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; + + mTotalLength = 0; + + for (int i = 0; i < count; ++i) { + final View child = getVirtualChildAt(i); + + if (child.getVisibility() == View.GONE) { + continue; + } + + LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); + + float childExtra = lp.weight; + if (childExtra > 0) { + // Child said it could absorb extra space -- give him his share + int share = (int) (childExtra * delta / weightSum); + weightSum -= childExtra; + delta -= share; + + final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, + getPaddingLeft() + getPaddingRight() + + lp.leftMargin + lp.rightMargin, lp.width); + + // TODO: Use a field like lp.isMeasured to figure out if this + // child has been previously measured + if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { + // child was measured once already above... + // base new measurement on stored values + int childHeight = child.getMeasuredHeight() + share; + if (childHeight < 0) { + childHeight = 0; + } + + child.measure(childWidthMeasureSpec, + MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); + } else { + // child was skipped in the loop above. + // Measure for this first time here + child.measure(childWidthMeasureSpec, + MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, + MeasureSpec.EXACTLY)); + } + + // Child may now not fit in vertical dimension. + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(child) & (ViewCompat.MEASURED_STATE_MASK + >> ViewCompat.MEASURED_HEIGHT_STATE_SHIFT)); + } + + final int margin = lp.leftMargin + lp.rightMargin; + final int measuredWidth = child.getMeasuredWidth() + margin; + maxWidth = Math.max(maxWidth, measuredWidth); + + boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY && + lp.width == LayoutParams.MATCH_PARENT; + + alternativeMaxWidth = Math.max(alternativeMaxWidth, + matchWidthLocally ? margin : measuredWidth); + + allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT; + + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() + + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); + } + + // Add in our padding + mTotalLength += getPaddingTop() + getPaddingBottom(); + // TODO: Should we recompute the heightSpec based on the new total length? + } else { + alternativeMaxWidth = Math.max(alternativeMaxWidth, + weightedMaxWidth); + + + // We have no limit, so make all weighted views as tall as the largest child. + // Children will have already been measured once. + if (useLargestChild && heightMode != MeasureSpec.EXACTLY) { + for (int i = 0; i < count; i++) { + final View child = getVirtualChildAt(i); + + if (child == null || child.getVisibility() == View.GONE) { + continue; + } + + final LinearLayoutCompat.LayoutParams lp = + (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); + + float childExtra = lp.weight; + if (childExtra > 0) { + child.measure( + MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(largestChildHeight, + MeasureSpec.EXACTLY)); + } + } + } + } + + if (!allFillParent && widthMode != MeasureSpec.EXACTLY) { + maxWidth = alternativeMaxWidth; + } + + maxWidth += getPaddingLeft() + getPaddingRight(); + + // Check against our minimum width + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension(ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + heightSizeAndState); + + if (matchWidth) { + forceUniformWidth(count, heightMeasureSpec); + } + } + + private void forceUniformWidth(int count, int heightMeasureSpec) { + // Pretend that the linear layout has an exact size. + int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), + MeasureSpec.EXACTLY); + for (int i = 0; i< count; ++i) { + final View child = getVirtualChildAt(i); + if (child.getVisibility() != GONE) { + LinearLayoutCompat.LayoutParams lp = ((LinearLayoutCompat.LayoutParams)child.getLayoutParams()); + + if (lp.width == LayoutParams.MATCH_PARENT) { + // Temporarily force children to reuse their old measured height + // FIXME: this may not be right for something like wrapping text? + int oldHeight = lp.height; + lp.height = child.getMeasuredHeight(); + + // Remeasue with new dimensions + measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0); + lp.height = oldHeight; + } + } + } + } + + /** + * Measures the children when the orientation of this LinearLayout is set + * to {@link #HORIZONTAL}. + * + * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. + * @param heightMeasureSpec Vertical space requirements as imposed by the parent. + * + * @see #getOrientation() + * @see #setOrientation(int) + * @see #onMeasure(int, int) + */ + void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { + mTotalLength = 0; + int maxHeight = 0; + int childState = 0; + int alternativeMaxHeight = 0; + int weightedMaxHeight = 0; + boolean allFillParent = true; + float totalWeight = 0; + + final int count = getVirtualChildCount(); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + boolean matchHeight = false; + boolean skippedMeasure = false; + + if (mMaxAscent == null || mMaxDescent == null) { + mMaxAscent = new int[VERTICAL_GRAVITY_COUNT]; + mMaxDescent = new int[VERTICAL_GRAVITY_COUNT]; + } + + final int[] maxAscent = mMaxAscent; + final int[] maxDescent = mMaxDescent; + + maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; + maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1; + + final boolean baselineAligned = mBaselineAligned; + final boolean useLargestChild = mUseLargestChild; + + final boolean isExactly = widthMode == MeasureSpec.EXACTLY; + + int largestChildWidth = Integer.MIN_VALUE; + + // See how wide everyone is. Also remember max height. + for (int i = 0; i < count; ++i) { + final View child = getVirtualChildAt(i); + + if (child == null) { + mTotalLength += measureNullChild(i); + continue; + } + + if (child.getVisibility() == GONE) { + i += getChildrenSkipCount(child, i); + continue; + } + + if (hasDividerBeforeChildAt(i)) { + mTotalLength += mDividerWidth; + } + + final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) + child.getLayoutParams(); + + totalWeight += lp.weight; + + if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) { + // Optimization: don't bother measuring children who are going to use + // leftover space. These views will get measured again down below if + // there is any leftover space. + if (isExactly) { + mTotalLength += lp.leftMargin + lp.rightMargin; + } else { + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + + lp.leftMargin + lp.rightMargin); + } + + // Baseline alignment requires to measure widgets to obtain the + // baseline offset (in particular for TextViews). The following + // defeats the optimization mentioned above. Allow the child to + // use as much space as it wants because we can shrink things + // later (and re-measure). + if (baselineAligned) { + final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + child.measure(freeSpec, freeSpec); + } else { + skippedMeasure = true; + } + } else { + int oldWidth = Integer.MIN_VALUE; + + if (lp.width == 0 && lp.weight > 0) { + // widthMode is either UNSPECIFIED or AT_MOST, and this + // child + // wanted to stretch to fill available space. Translate that to + // WRAP_CONTENT so that it does not end up with a width of 0 + oldWidth = 0; + lp.width = LayoutParams.WRAP_CONTENT; + } + + // Determine how big this child would like to be. If this or + // previous children have given a weight, then we allow it to + // use all available space (and we will shrink things later + // if needed). + measureChildBeforeLayout(child, i, widthMeasureSpec, + totalWeight == 0 ? mTotalLength : 0, + heightMeasureSpec, 0); + + if (oldWidth != Integer.MIN_VALUE) { + lp.width = oldWidth; + } + + final int childWidth = child.getMeasuredWidth(); + if (isExactly) { + mTotalLength += childWidth + lp.leftMargin + lp.rightMargin + + getNextLocationOffset(child); + } else { + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + + lp.rightMargin + getNextLocationOffset(child)); + } + + if (useLargestChild) { + largestChildWidth = Math.max(childWidth, largestChildWidth); + } + } + + boolean matchHeightLocally = false; + if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) { + // The height of the linear layout will scale, and at least one + // child said it wanted to match our height. Set a flag indicating that + // we need to remeasure at least that view when we know our height. + matchHeight = true; + matchHeightLocally = true; + } + + final int margin = lp.topMargin + lp.bottomMargin; + final int childHeight = child.getMeasuredHeight() + margin; + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(child)); + + if (baselineAligned) { + final int childBaseline = child.getBaseline(); + if (childBaseline != -1) { + // Translates the child's vertical gravity into an index + // in the range 0..VERTICAL_GRAVITY_COUNT + final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) + & Gravity.VERTICAL_GRAVITY_MASK; + final int index = ((gravity >> Gravity.AXIS_Y_SHIFT) + & ~Gravity.AXIS_SPECIFIED) >> 1; + + maxAscent[index] = Math.max(maxAscent[index], childBaseline); + maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline); + } + } + + maxHeight = Math.max(maxHeight, childHeight); + + allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT; + if (lp.weight > 0) { + /* + * Heights of weighted Views are bogus if we end up + * remeasuring, so keep them separate. + */ + weightedMaxHeight = Math.max(weightedMaxHeight, + matchHeightLocally ? margin : childHeight); + } else { + alternativeMaxHeight = Math.max(alternativeMaxHeight, + matchHeightLocally ? margin : childHeight); + } + + i += getChildrenSkipCount(child, i); + } + + if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { + mTotalLength += mDividerWidth; + } + + // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, + // the most common case + if (maxAscent[INDEX_TOP] != -1 || + maxAscent[INDEX_CENTER_VERTICAL] != -1 || + maxAscent[INDEX_BOTTOM] != -1 || + maxAscent[INDEX_FILL] != -1) { + final int ascent = Math.max(maxAscent[INDEX_FILL], + Math.max(maxAscent[INDEX_CENTER_VERTICAL], + Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM]))); + final int descent = Math.max(maxDescent[INDEX_FILL], + Math.max(maxDescent[INDEX_CENTER_VERTICAL], + Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM]))); + maxHeight = Math.max(maxHeight, ascent + descent); + } + + if (useLargestChild && + (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) { + mTotalLength = 0; + + for (int i = 0; i < count; ++i) { + final View child = getVirtualChildAt(i); + + if (child == null) { + mTotalLength += measureNullChild(i); + continue; + } + + if (child.getVisibility() == GONE) { + i += getChildrenSkipCount(child, i); + continue; + } + + final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) + child.getLayoutParams(); + if (isExactly) { + mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin + + getNextLocationOffset(child); + } else { + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + largestChildWidth + + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); + } + } + } + + // Add in our padding + mTotalLength += getPaddingLeft() + getPaddingRight(); + + int widthSize = mTotalLength; + + // Check against our minimum width + widthSize = Math.max(widthSize, getSuggestedMinimumWidth()); + + // Reconcile our calculated size with the widthMeasureSpec + int widthSizeAndState = ViewCompat.resolveSizeAndState(widthSize, widthMeasureSpec, 0); + widthSize = widthSizeAndState & ViewCompat.MEASURED_SIZE_MASK; + + // Either expand children with weight to take up available space or + // shrink them if they extend beyond our current bounds. If we skipped + // measurement on any children, we need to measure them now. + int delta = widthSize - mTotalLength; + if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { + float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; + + maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; + maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1; + maxHeight = -1; + + mTotalLength = 0; + + for (int i = 0; i < count; ++i) { + final View child = getVirtualChildAt(i); + + if (child == null || child.getVisibility() == View.GONE) { + continue; + } + + final LinearLayoutCompat.LayoutParams lp = + (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); + + float childExtra = lp.weight; + if (childExtra > 0) { + // Child said it could absorb extra space -- give him his share + int share = (int) (childExtra * delta / weightSum); + weightSum -= childExtra; + delta -= share; + + final int childHeightMeasureSpec = getChildMeasureSpec( + heightMeasureSpec, + getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, + lp.height); + + // TODO: Use a field like lp.isMeasured to figure out if this + // child has been previously measured + if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) { + // child was measured once already above ... base new measurement + // on stored values + int childWidth = child.getMeasuredWidth() + share; + if (childWidth < 0) { + childWidth = 0; + } + + child.measure( + MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), + childHeightMeasureSpec); + } else { + // child was skipped in the loop above. Measure for this first time here + child.measure(MeasureSpec.makeMeasureSpec( + share > 0 ? share : 0, MeasureSpec.EXACTLY), + childHeightMeasureSpec); + } + + // Child may now not fit in horizontal dimension. + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(child) & ViewCompat.MEASURED_STATE_MASK); + } + + if (isExactly) { + mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + + getNextLocationOffset(child); + } else { + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() + + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); + } + + boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY && + lp.height == LayoutParams.MATCH_PARENT; + + final int margin = lp.topMargin + lp .bottomMargin; + int childHeight = child.getMeasuredHeight() + margin; + maxHeight = Math.max(maxHeight, childHeight); + alternativeMaxHeight = Math.max(alternativeMaxHeight, + matchHeightLocally ? margin : childHeight); + + allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT; + + if (baselineAligned) { + final int childBaseline = child.getBaseline(); + if (childBaseline != -1) { + // Translates the child's vertical gravity into an index in the range 0..2 + final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) + & Gravity.VERTICAL_GRAVITY_MASK; + final int index = ((gravity >> Gravity.AXIS_Y_SHIFT) + & ~Gravity.AXIS_SPECIFIED) >> 1; + + maxAscent[index] = Math.max(maxAscent[index], childBaseline); + maxDescent[index] = Math.max(maxDescent[index], + childHeight - childBaseline); + } + } + } + + // Add in our padding + mTotalLength += getPaddingLeft() + getPaddingRight(); + // TODO: Should we update widthSize with the new total length? + + // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, + // the most common case + if (maxAscent[INDEX_TOP] != -1 || + maxAscent[INDEX_CENTER_VERTICAL] != -1 || + maxAscent[INDEX_BOTTOM] != -1 || + maxAscent[INDEX_FILL] != -1) { + final int ascent = Math.max(maxAscent[INDEX_FILL], + Math.max(maxAscent[INDEX_CENTER_VERTICAL], + Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM]))); + final int descent = Math.max(maxDescent[INDEX_FILL], + Math.max(maxDescent[INDEX_CENTER_VERTICAL], + Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM]))); + maxHeight = Math.max(maxHeight, ascent + descent); + } + } else { + alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight); + + // We have no limit, so make all weighted views as wide as the largest child. + // Children will have already been measured once. + if (useLargestChild && widthMode != MeasureSpec.EXACTLY) { + for (int i = 0; i < count; i++) { + final View child = getVirtualChildAt(i); + + if (child == null || child.getVisibility() == View.GONE) { + continue; + } + + final LinearLayoutCompat.LayoutParams lp = + (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); + + float childExtra = lp.weight; + if (childExtra > 0) { + child.measure( + MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), + MeasureSpec.EXACTLY)); + } + } + } + } + + if (!allFillParent && heightMode != MeasureSpec.EXACTLY) { + maxHeight = alternativeMaxHeight; + } + + maxHeight += getPaddingTop() + getPaddingBottom(); + + // Check against our minimum height + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + + setMeasuredDimension(widthSizeAndState | (childState&ViewCompat.MEASURED_STATE_MASK), + ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec, + (childState<Returns the number of children to skip after measuring/laying out + * the specified child.

+ * + * @param child the child after which we want to skip children + * @param index the index of the child after which we want to skip children + * @return the number of children to skip, 0 by default + */ + int getChildrenSkipCount(View child, int index) { + return 0; + } + + /** + *

Returns the size (width or height) that should be occupied by a null + * child.

+ * + * @param childIndex the index of the null child + * @return the width or height of the child depending on the orientation + */ + int measureNullChild(int childIndex) { + return 0; + } + + /** + *

Measure the child according to the parent's measure specs. This + * method should be overriden by subclasses to force the sizing of + * children. This method is called by {@link #measureVertical(int, int)} and + * {@link #measureHorizontal(int, int)}.

+ * + * @param child the child to measure + * @param childIndex the index of the child in this view + * @param widthMeasureSpec horizontal space requirements as imposed by the parent + * @param totalWidth extra space that has been used up by the parent horizontally + * @param heightMeasureSpec vertical space requirements as imposed by the parent + * @param totalHeight extra space that has been used up by the parent vertically + */ + void measureChildBeforeLayout(View child, int childIndex, + int widthMeasureSpec, int totalWidth, int heightMeasureSpec, + int totalHeight) { + measureChildWithMargins(child, widthMeasureSpec, totalWidth, + heightMeasureSpec, totalHeight); + } + + /** + *

Return the location offset of the specified child. This can be used + * by subclasses to change the location of a given widget.

+ * + * @param child the child for which to obtain the location offset + * @return the location offset in pixels + */ + int getLocationOffset(View child) { + return 0; + } + + /** + *

Return the size offset of the next sibling of the specified child. + * This can be used by subclasses to change the location of the widget + * following child.

+ * + * @param child the child whose next sibling will be moved + * @return the location offset of the next child in pixels + */ + int getNextLocationOffset(View child) { + return 0; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (mOrientation == VERTICAL) { + layoutVertical(l, t, r, b); + } else { + layoutHorizontal(l, t, r, b); + } + } + + /** + * Position the children during a layout pass if the orientation of this + * LinearLayout is set to {@link #VERTICAL}. + * + * @see #getOrientation() + * @see #setOrientation(int) + * @see #onLayout(boolean, int, int, int, int) + * @param left + * @param top + * @param right + * @param bottom + */ + void layoutVertical(int left, int top, int right, int bottom) { + final int paddingLeft = getPaddingLeft(); + + int childTop; + int childLeft; + + // Where right end of child should go + final int width = right - left; + int childRight = width - getPaddingRight(); + + // Space available for child + int childSpace = width - paddingLeft - getPaddingRight(); + + final int count = getVirtualChildCount(); + + final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; + final int minorGravity = mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK; + + switch (majorGravity) { + case Gravity.BOTTOM: + // mTotalLength contains the padding already + childTop = getPaddingTop() + bottom - top - mTotalLength; + break; + + // mTotalLength contains the padding already + case Gravity.CENTER_VERTICAL: + childTop = getPaddingTop() + (bottom - top - mTotalLength) / 2; + break; + + case Gravity.TOP: + default: + childTop = getPaddingTop(); + break; + } + + for (int i = 0; i < count; i++) { + final View child = getVirtualChildAt(i); + if (child == null) { + childTop += measureNullChild(i); + } else if (child.getVisibility() != GONE) { + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + + final LinearLayoutCompat.LayoutParams lp = + (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); + + int gravity = lp.gravity; + if (gravity < 0) { + gravity = minorGravity; + } + final int layoutDirection = ViewCompat.getLayoutDirection(this); + final int absoluteGravity = GravityCompat.getAbsoluteGravity(gravity, + layoutDirection); + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + childLeft = paddingLeft + ((childSpace - childWidth) / 2) + + lp.leftMargin - lp.rightMargin; + break; + + case Gravity.RIGHT: + childLeft = childRight - childWidth - lp.rightMargin; + break; + + case Gravity.LEFT: + default: + childLeft = paddingLeft + lp.leftMargin; + break; + } + + if (hasDividerBeforeChildAt(i)) { + childTop += mDividerHeight; + } + + childTop += lp.topMargin; + setChildFrame(child, childLeft, childTop + getLocationOffset(child), + childWidth, childHeight); + childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); + + i += getChildrenSkipCount(child, i); + } + } + } + + /** + * Position the children during a layout pass if the orientation of this + * LinearLayout is set to {@link #HORIZONTAL}. + * + * @see #getOrientation() + * @see #setOrientation(int) + * @see #onLayout(boolean, int, int, int, int) + * @param left + * @param top + * @param right + * @param bottom + */ + void layoutHorizontal(int left, int top, int right, int bottom) { + final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this); + final int paddingTop = getPaddingTop(); + + int childTop; + int childLeft; + + // Where bottom of child should go + final int height = bottom - top; + int childBottom = height - getPaddingBottom(); + + // Space available for child + int childSpace = height - paddingTop - getPaddingBottom(); + + final int count = getVirtualChildCount(); + + final int majorGravity = mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK; + final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; + + final boolean baselineAligned = mBaselineAligned; + + final int[] maxAscent = mMaxAscent; + final int[] maxDescent = mMaxDescent; + + final int layoutDirection = ViewCompat.getLayoutDirection(this); + switch (GravityCompat.getAbsoluteGravity(majorGravity, layoutDirection)) { + case Gravity.RIGHT: + // mTotalLength contains the padding already + childLeft = getPaddingLeft() + right - left - mTotalLength; + break; + + case Gravity.CENTER_HORIZONTAL: + // mTotalLength contains the padding already + childLeft = getPaddingLeft() + (right - left - mTotalLength) / 2; + break; + + case Gravity.LEFT: + default: + childLeft = getPaddingLeft(); + break; + } + + int start = 0; + int dir = 1; + //In case of RTL, start drawing from the last child. + if (isLayoutRtl) { + start = count - 1; + dir = -1; + } + + for (int i = 0; i < count; i++) { + int childIndex = start + dir * i; + final View child = getVirtualChildAt(childIndex); + + if (child == null) { + childLeft += measureNullChild(childIndex); + } else if (child.getVisibility() != GONE) { + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + int childBaseline = -1; + + final LinearLayoutCompat.LayoutParams lp = + (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); + + if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) { + childBaseline = child.getBaseline(); + } + + int gravity = lp.gravity; + if (gravity < 0) { + gravity = minorGravity; + } + + switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { + case Gravity.TOP: + childTop = paddingTop + lp.topMargin; + if (childBaseline != -1) { + childTop += maxAscent[INDEX_TOP] - childBaseline; + } + break; + + case Gravity.CENTER_VERTICAL: + // Removed support for baseline alignment when layout_gravity or + // gravity == center_vertical. See bug #1038483. + // Keep the code around if we need to re-enable this feature + // if (childBaseline != -1) { + // // Align baselines vertically only if the child is smaller than us + // if (childSpace - childHeight > 0) { + // childTop = paddingTop + (childSpace / 2) - childBaseline; + // } else { + // childTop = paddingTop + (childSpace - childHeight) / 2; + // } + // } else { + childTop = paddingTop + ((childSpace - childHeight) / 2) + + lp.topMargin - lp.bottomMargin; + break; + + case Gravity.BOTTOM: + childTop = childBottom - childHeight - lp.bottomMargin; + if (childBaseline != -1) { + int descent = child.getMeasuredHeight() - childBaseline; + childTop -= (maxDescent[INDEX_BOTTOM] - descent); + } + break; + default: + childTop = paddingTop; + break; + } + + if (hasDividerBeforeChildAt(childIndex)) { + childLeft += mDividerWidth; + } + + childLeft += lp.leftMargin; + setChildFrame(child, childLeft + getLocationOffset(child), childTop, + childWidth, childHeight); + childLeft += childWidth + lp.rightMargin + + getNextLocationOffset(child); + + i += getChildrenSkipCount(child, childIndex); + } + } + } + + private void setChildFrame(View child, int left, int top, int width, int height) { + child.layout(left, top, left + width, top + height); + } + + /** + * Should the layout be a column or a row. + * @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default + * value is {@link #HORIZONTAL}. + */ + public void setOrientation(@OrientationMode int orientation) { + if (mOrientation != orientation) { + mOrientation = orientation; + requestLayout(); + } + } + + /** + * Returns the current orientation. + * + * @return either {@link #HORIZONTAL} or {@link #VERTICAL} + */ + @OrientationMode + public int getOrientation() { + return mOrientation; + } + + /** + * Describes how the child views are positioned. Defaults to GRAVITY_TOP. If + * this layout has a VERTICAL orientation, this controls where all the child + * views are placed if there is extra vertical space. If this layout has a + * HORIZONTAL orientation, this controls the alignment of the children. + * + * @param gravity See {@link android.view.Gravity} + */ + public void setGravity(int gravity) { + if (mGravity != gravity) { + if ((gravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { + gravity |= GravityCompat.START; + } + + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.TOP; + } + + mGravity = gravity; + requestLayout(); + } + } + + public void setHorizontalGravity(int horizontalGravity) { + final int gravity = horizontalGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK; + if ((mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) { + mGravity = (mGravity & ~GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity; + requestLayout(); + } + } + + public void setVerticalGravity(int verticalGravity) { + final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK; + if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) { + mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity; + requestLayout(); + } + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LinearLayoutCompat.LayoutParams(getContext(), attrs); + } + + /** + * Returns a set of layout parameters with a width of + * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} + * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} + * when the layout's orientation is {@link #VERTICAL}. When the orientation is + * {@link #HORIZONTAL}, the width is set to {@link LayoutParams#WRAP_CONTENT} + * and the height to {@link LayoutParams#WRAP_CONTENT}. + */ + @Override + protected LayoutParams generateDefaultLayoutParams() { + if (mOrientation == HORIZONTAL) { + return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } else if (mOrientation == VERTICAL) { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + return null; + } + + @Override + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + + // Override to allow type-checking of LayoutParams. + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LinearLayoutCompat.LayoutParams; + } + + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + if (Build.VERSION.SDK_INT >= 14) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(LinearLayoutCompat.class.getName()); + } + } + + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + if (Build.VERSION.SDK_INT >= 14) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(LinearLayoutCompat.class.getName()); + } + } + + /** + * Per-child layout information associated with ViewLinearLayout. + */ + public static class LayoutParams extends ViewGroup.MarginLayoutParams { + /** + * Indicates how much of the extra space in the LinearLayout will be + * allocated to the view associated with these LayoutParams. Specify + * 0 if the view should not be stretched. Otherwise the extra pixels + * will be pro-rated among all views whose weight is greater than 0. + */ + public float weight; + + /** + * Gravity for the view associated with these LayoutParams. + * + * @see android.view.Gravity + */ + public int gravity = -1; + + /** + * {@inheritDoc} + */ + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + TypedArray a = + c.obtainStyledAttributes(attrs, R.styleable.LinearLayoutCompat_Layout); + + weight = a.getFloat(R.styleable.LinearLayoutCompat_Layout_android_layout_weight, 0); + gravity = a.getInt(R.styleable.LinearLayoutCompat_Layout_android_layout_gravity, -1); + + a.recycle(); + } + + /** + * {@inheritDoc} + */ + public LayoutParams(int width, int height) { + super(width, height); + weight = 0; + } + + /** + * Creates a new set of layout parameters with the specified width, height + * and weight. + * + * @param width the width, either {@link #MATCH_PARENT}, + * {@link #WRAP_CONTENT} or a fixed size in pixels + * @param height the height, either {@link #MATCH_PARENT}, + * {@link #WRAP_CONTENT} or a fixed size in pixels + * @param weight the weight + */ + public LayoutParams(int width, int height, float weight) { + super(width, height); + this.weight = weight; + } + + /** + * {@inheritDoc} + */ + public LayoutParams(ViewGroup.LayoutParams p) { + super(p); + } + + /** + * {@inheritDoc} + */ + public LayoutParams(ViewGroup.MarginLayoutParams source) { + super(source); + } + + /** + * Copy constructor. Clones the width, height, margin values, weight, + * and gravity of the source. + * + * @param source The layout params to copy from. + */ + public LayoutParams(LayoutParams source) { + super(source); + + this.weight = source.weight; + this.gravity = source.gravity; + } + } +} diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/eclipse-compile/appcompat/src/android/support/v7/widget/ListPopupWindow.java new file mode 100644 index 0000000000..0d2bcb8720 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/ListPopupWindow.java @@ -0,0 +1,1780 @@ +/* + * Copyright (C) 2014 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 android.support.v7.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; +import android.os.SystemClock; +import android.support.v4.text.TextUtilsCompat; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.support.v4.widget.ListViewAutoScrollHelper; +import android.support.v4.widget.PopupWindowCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.widget.AppCompatPopupWindow; +import android.support.v7.internal.widget.ListViewCompat; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; +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; + +import java.lang.reflect.Method; +import java.util.Locale; + +/** + * Static library support version of the framework's {@link android.widget.ListPopupWindow}. + * Used to write apps that run on platforms prior to Android L. When running + * on Android L or above, this implementation is still used; it does not try + * to switch to the framework's implementation. See the framework SDK + * documentation for a class overview. + * + * @see android.widget.ListPopupWindow + */ +public class ListPopupWindow { + private static final String TAG = "ListPopupWindow"; + private static final boolean DEBUG = false; + + /** + * 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 static Method sClipToWindowEnabledMethod; + + static { + try { + sClipToWindowEnabledMethod = PopupWindow.class.getDeclaredMethod( + "setClipToScreenEnabled", boolean.class); + } catch (NoSuchMethodException e) { + Log.i(TAG, "Could not find method setClipToScreenEnabled() on PopupWindow. Oh well."); + } + } + + 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 mDropDownGravity = Gravity.NO_GRAVITY; + + private boolean mDropDownAlwaysVisible = false; + private boolean mForceIgnoreOutsideTouch = false; + 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 Runnable mShowDropDownRunnable; + + private Handler mHandler = new Handler(); + + private Rect mTempRect = new Rect(); + + private boolean mModal; + + private int mLayoutDirection; + + /** + * The provided prompt view should appear above list content. + * + * @see #setPromptPosition(int) + * @see #getPromptPosition() + * @see #setPromptView(View) + */ + public static final int POSITION_PROMPT_ABOVE = 0; + + /** + * The provided prompt view should appear below list content. + * + * @see #setPromptPosition(int) + * @see #getPromptPosition() + * @see #setPromptView(View) + */ + public static final int POSITION_PROMPT_BELOW = 1; + + /** + * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}. + * If used to specify a popup width, the popup will match the width of the anchor view. + * If used to specify a popup height, the popup will fill available space. + */ + public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT; + + /** + * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}. + * If used to specify a popup width, the popup will use the width of its content. + */ + public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT; + + /** + * Mode for {@link #setInputMethodMode(int)}: the requirements for the + * input method should be based on the focusability of the popup. That is + * if it is focusable than it needs to work with the input method, else + * it doesn't. + */ + public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE; + + /** + * Mode for {@link #setInputMethodMode(int)}: this popup always needs to + * work with an input method, regardless of whether it is focusable. This + * means that it will always be displayed so that the user can also operate + * the input method while it is shown. + */ + public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED; + + /** + * Mode for {@link #setInputMethodMode(int)}: this popup never needs to + * work with an input method, regardless of whether it is focusable. This + * means that it will always be displayed to use as much space on the + * screen as needed, regardless of whether this covers the input method. + */ + public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED; + + /** + * Create a new, empty popup window capable of displaying items from a ListAdapter. + * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. + * + * @param context Context used for contained views. + */ + public ListPopupWindow(Context context) { + this(context, null, R.attr.listPopupWindowStyle); + } + + /** + * Create a new, empty popup window capable of displaying items from a ListAdapter. + * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. + * + * @param context Context used for contained views. + * @param attrs Attributes from inflating parent views used to style the popup. + */ + public ListPopupWindow(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.listPopupWindowStyle); + } + + /** + * Create a new, empty popup window capable of displaying items from a ListAdapter. + * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. + * + * @param context Context used for contained views. + * @param attrs Attributes from inflating parent views used to style the popup. + * @param defStyleAttr Default style attribute to use for popup content. + */ + public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Create a new, empty popup window capable of displaying items from a ListAdapter. + * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. + * + * @param context Context used for contained views. + * @param attrs Attributes from inflating parent views used to style the popup. + * @param defStyleAttr Style attribute to read for default styling of popup content. + * @param defStyleRes Style resource ID to use for default styling of popup content. + */ + public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + mContext = context; + + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow, + defStyleAttr, defStyleRes); + mDropDownHorizontalOffset = a.getDimensionPixelOffset( + R.styleable.ListPopupWindow_android_dropDownHorizontalOffset, 0); + mDropDownVerticalOffset = a.getDimensionPixelOffset( + R.styleable.ListPopupWindow_android_dropDownVerticalOffset, 0); + if (mDropDownVerticalOffset != 0) { + mDropDownVerticalOffsetSet = true; + } + a.recycle(); + + mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr); + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + + // Set the default layout direction to match the default locale one + final Locale locale = mContext.getResources().getConfiguration().locale; + mLayoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(locale); + } + + /** + * Sets the adapter that provides the data and the views to represent the data + * in this popup window. + * + * @param adapter The adapter to use to create this window's content. + */ + 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); + } + } + + /** + * Set where the optional prompt view should appear. The default is + * {@link #POSITION_PROMPT_ABOVE}. + * + * @param position A position constant declaring where the prompt should be displayed. + * + * @see #POSITION_PROMPT_ABOVE + * @see #POSITION_PROMPT_BELOW + */ + public void setPromptPosition(int position) { + mPromptPosition = position; + } + + /** + * @return Where the optional prompt view should appear. + * + * @see #POSITION_PROMPT_ABOVE + * @see #POSITION_PROMPT_BELOW + */ + public int getPromptPosition() { + return mPromptPosition; + } + + /** + * Set whether this window should be modal when shown. + * + *

If a popup window is modal, it will receive all touch and key input. + * If the user touches outside the popup window's content area the popup window + * will be dismissed. + * + * @param modal {@code true} if the popup window should be modal, {@code false} otherwise. + */ + public void setModal(boolean modal) { + mModal = modal; + mPopup.setFocusable(modal); + } + + /** + * Returns whether the popup window will be modal when shown. + * + * @return {@code true} if the popup window will be modal, {@code false} otherwise. + */ + public boolean isModal() { + return mModal; + } + + /** + * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is + * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we + * ignore outside touch even when the drop down is not set to always visible. + * + * @hide Used only by AutoCompleteTextView to handle some internal special cases. + */ + public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) { + mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch; + } + + /** + * Sets whether the drop-down should remain visible under certain conditions. + * + * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless + * of the size or content of the list. {@link #getBackground()} will fill any space + * that is not used by the list. + * + * @param dropDownAlwaysVisible Whether to keep the drop-down visible. + * + * @hide Only used by AutoCompleteTextView under special conditions. + */ + public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) { + mDropDownAlwaysVisible = dropDownAlwaysVisible; + } + + /** + * @return Whether the drop-down is visible under special conditions. + * + * @hide Only used by AutoCompleteTextView under special conditions. + */ + public boolean isDropDownAlwaysVisible() { + return mDropDownAlwaysVisible; + } + + /** + * Sets the operating mode for the soft input area. + * + * @param mode The desired mode, see + * {@link android.view.WindowManager.LayoutParams#softInputMode} + * for the full list + * + * @see android.view.WindowManager.LayoutParams#softInputMode + * @see #getSoftInputMode() + */ + public void setSoftInputMode(int mode) { + mPopup.setSoftInputMode(mode); + } + + /** + * Returns the current value in {@link #setSoftInputMode(int)}. + * + * @see #setSoftInputMode(int) + * @see android.view.WindowManager.LayoutParams#softInputMode + */ + public int getSoftInputMode() { + return mPopup.getSoftInputMode(); + } + + /** + * Sets a drawable to use as the list item selector. + * + * @param selector List selector drawable to use in the popup. + */ + public void setListSelector(Drawable selector) { + mDropDownListHighlight = selector; + } + + /** + * @return The background drawable for the popup window. + */ + public Drawable getBackground() { + return mPopup.getBackground(); + } + + /** + * Sets a drawable to be the background for the popup window. + * + * @param d A drawable to set as the background. + */ + public void setBackgroundDrawable(Drawable d) { + mPopup.setBackgroundDrawable(d); + } + + /** + * Set an animation style to use when the popup window is shown or dismissed. + * + * @param animationStyle Animation style to use. + */ + public void setAnimationStyle(int animationStyle) { + mPopup.setAnimationStyle(animationStyle); + } + + /** + * Returns the animation style that will be used when the popup window is shown or dismissed. + * + * @return Animation style that will be used. + */ + public int getAnimationStyle() { + return mPopup.getAnimationStyle(); + } + + /** + * Returns the view that will be used to anchor this popup. + * + * @return The popup's anchor view + */ + public View getAnchorView() { + return mDropDownAnchorView; + } + + /** + * Sets the popup's anchor view. This popup will always be positioned relative to the anchor + * view when shown. + * + * @param anchor The view to use as an anchor. + */ + public void setAnchorView(View anchor) { + mDropDownAnchorView = anchor; + } + + /** + * @return The horizontal offset of the popup from its anchor in pixels. + */ + public int getHorizontalOffset() { + return mDropDownHorizontalOffset; + } + + /** + * Set the horizontal offset of this popup from its anchor view in pixels. + * + * @param offset The horizontal offset of the popup from its anchor. + */ + public void setHorizontalOffset(int offset) { + mDropDownHorizontalOffset = offset; + } + + /** + * @return The vertical offset of the popup from its anchor in pixels. + */ + public int getVerticalOffset() { + if (!mDropDownVerticalOffsetSet) { + return 0; + } + return mDropDownVerticalOffset; + } + + /** + * Set the vertical offset of this popup from its anchor view in pixels. + * + * @param offset The vertical offset of the popup from its anchor. + */ + public void setVerticalOffset(int offset) { + mDropDownVerticalOffset = offset; + mDropDownVerticalOffsetSet = true; + } + + /** + * Set the gravity of the dropdown list. This is commonly used to + * set gravity to START or END for alignment with the anchor. + * + * @param gravity Gravity value to use + */ + public void setDropDownGravity(int gravity) { + mDropDownGravity = gravity; + } + + /** + * @return The width of the popup window in pixels. + */ + public int getWidth() { + return mDropDownWidth; + } + + /** + * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT} + * or {@link #WRAP_CONTENT}. + * + * @param width Width of the popup window. + */ + public void setWidth(int width) { + mDropDownWidth = width; + } + + /** + * Sets the width of the popup window by the size of its content. The final width may be + * larger to accommodate styled window dressing. + * + * @param width Desired width of content in pixels. + */ + public void setContentWidth(int width) { + Drawable popupBackground = mPopup.getBackground(); + if (popupBackground != null) { + popupBackground.getPadding(mTempRect); + mDropDownWidth = mTempRect.left + mTempRect.right + width; + } else { + setWidth(width); + } + } + + /** + * @return The height of the popup window in pixels. + */ + public int getHeight() { + return mDropDownHeight; + } + + /** + * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}. + * + * @param height Height of the popup window. + */ + public void setHeight(int height) { + mDropDownHeight = height; + } + + /** + * Sets a listener to receive events when a list item is clicked. + * + * @param clickListener Listener to register + * + * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener) + */ + public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) { + mItemClickListener = clickListener; + } + + /** + * Sets a listener to receive events when a list item is selected. + * + * @param selectedListener Listener to register. + * + * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener) + */ + public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) { + mItemSelectedListener = selectedListener; + } + + /** + * Set a view to act as a user prompt for this popup window. Where the prompt view will appear + * is controlled by {@link #setPromptPosition(int)}. + * + * @param prompt View to use as an informational prompt. + */ + public void setPromptView(View prompt) { + boolean showing = isShowing(); + if (showing) { + removePromptView(); + } + mPromptView = prompt; + if (showing) { + show(); + } + } + + /** + * Post a {@link #show()} call to the UI thread. + */ + public void postShow() { + mHandler.post(mShowDropDownRunnable); + } + + /** + * Show the popup list. If the list is already showing, this method + * will recalculate the popup's size and position. + */ + public void show() { + int height = buildDropDown(); + + int widthSpec = 0; + int heightSpec = 0; + + boolean noInputMethod = isInputMethodNotNeeded(); + + 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 = getAnchorView().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(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); + + mPopup.update(getAnchorView(), 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(getAnchorView().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); + setPopupClipToScreenEnabled(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(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); + mPopup.setTouchInterceptor(mTouchInterceptor); + PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset, + mDropDownVerticalOffset, mDropDownGravity); + mDropDownList.setSelection(ListView.INVALID_POSITION); + + if (!mModal || mDropDownList.isInTouchMode()) { + clearListSelection(); + } + if (!mModal) { + mHandler.post(mHideSelector); + } + } + } + + /** + * Dismiss the popup window. + */ + public void dismiss() { + mPopup.dismiss(); + removePromptView(); + mPopup.setContentView(null); + mDropDownList = null; + mHandler.removeCallbacks(mResizePopupRunnable); + } + + /** + * Set a listener to receive a callback when the popup is dismissed. + * + * @param listener Listener that will be notified when the popup is dismissed. + */ + public void setOnDismissListener(PopupWindow.OnDismissListener listener) { + mPopup.setOnDismissListener(listener); + } + + private void removePromptView() { + if (mPromptView != null) { + final ViewParent parent = mPromptView.getParent(); + if (parent instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) parent; + group.removeView(mPromptView); + } + } + } + + /** + * Control how the popup operates with an input method: one of + * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, + * or {@link #INPUT_METHOD_NOT_NEEDED}. + * + *

If the popup is showing, calling this method will take effect only + * the next time the popup is shown or through a manual call to the {@link #show()} + * method.

+ * + * @see #getInputMethodMode() + * @see #show() + */ + public void setInputMethodMode(int mode) { + mPopup.setInputMethodMode(mode); + } + + /** + * Return the current value in {@link #setInputMethodMode(int)}. + * + * @see #setInputMethodMode(int) + */ + public int getInputMethodMode() { + return mPopup.getInputMethodMode(); + } + + /** + * Set the selected position of the list. + * Only valid when {@link #isShowing()} == {@code true}. + * + * @param position List position to set as selected. + */ + public void setSelection(int position) { + DropDownListView list = mDropDownList; + if (isShowing() && list != null) { + list.mListSelectionHidden = false; + list.setSelection(position); + + if (Build.VERSION.SDK_INT >= 11) { + if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) { + list.setItemChecked(position, true); + } + } + } + } + + /** + * Clear any current list selection. + * Only valid when {@link #isShowing()} == {@code true}. + */ + public void clearListSelection() { + final DropDownListView list = mDropDownList; + if (list != null) { + // WARNING: Please read the comment where mListSelectionHidden is declared + list.mListSelectionHidden = true; + //list.hideSelector(); + list.requestLayout(); + } + } + + /** + * @return {@code true} if the popup is currently showing, {@code false} otherwise. + */ + public boolean isShowing() { + return mPopup.isShowing(); + } + + /** + * @return {@code true} if this popup is configured to assume the user does not need + * to interact with the IME while it is showing, {@code false} otherwise. + */ + public boolean isInputMethodNotNeeded() { + return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED; + } + + /** + * Perform an item click operation on the specified list adapter position. + * + * @param position Adapter position for performing the click + * @return true if the click action could be performed, false if not. + * (e.g. if the popup was not showing, this method would return false.) + */ + public boolean performItemClick(int position) { + if (isShowing()) { + if (mItemClickListener != null) { + final DropDownListView list = mDropDownList; + final View child = list.getChildAt(position - list.getFirstVisiblePosition()); + final ListAdapter adapter = list.getAdapter(); + mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position)); + } + return true; + } + return false; + } + + /** + * @return The currently selected item or null if the popup is not showing. + */ + public Object getSelectedItem() { + if (!isShowing()) { + return null; + } + return mDropDownList.getSelectedItem(); + } + + /** + * @return The position of the currently selected item or {@link ListView#INVALID_POSITION} + * if {@link #isShowing()} == {@code false}. + * + * @see ListView#getSelectedItemPosition() + */ + public int getSelectedItemPosition() { + if (!isShowing()) { + return ListView.INVALID_POSITION; + } + return mDropDownList.getSelectedItemPosition(); + } + + /** + * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID} + * if {@link #isShowing()} == {@code false}. + * + * @see ListView#getSelectedItemId() + */ + public long getSelectedItemId() { + if (!isShowing()) { + return ListView.INVALID_ROW_ID; + } + return mDropDownList.getSelectedItemId(); + } + + /** + * @return The View for the currently selected item or null if + * {@link #isShowing()} == {@code false}. + * + * @see ListView#getSelectedView() + */ + public View getSelectedView() { + if (!isShowing()) { + return null; + } + return mDropDownList.getSelectedView(); + } + + /** + * @return The {@link ListView} displayed within the popup window. + * Only valid when {@link #isShowing()} == {@code true}. + */ + public ListView getListView() { + return mDropDownList; + } + + /** + * The maximum number of list items that can be visible and still have + * the list expand when touched. + * + * @param max Max number of items that can be visible and still allow the list to expand. + */ + void setListItemExpandMax(int max) { + mListItemExpandMaximum = max; + } + + /** + * Filter key down events. By forwarding key down events to this function, + * views using non-modal ListPopupWindow can have it handle key selection of items. + * + * @param keyCode keyCode param passed to the host view's onKeyDown + * @param event event param passed to the host view's onKeyDown + * @return true if the event was handled, false if it was ignored. + * + * @see #setModal(boolean) + */ + public boolean onKeyDown(int keyCode, KeyEvent event) { + // when the drop down is shown, we drive it directly + if (isShowing()) { + // the key events are forwarded to the list in the drop down view + // note that ListView handles space but we don't want that to happen + // also if selection is not currently in the drop down, then don't + // let center or enter presses go there since that would cause it + // to select one of its items + if (keyCode != KeyEvent.KEYCODE_SPACE + && (mDropDownList.getSelectedItemPosition() >= 0 + || !isConfirmKey(keyCode))) { + int curIndex = mDropDownList.getSelectedItemPosition(); + boolean consumed; + + final boolean below = !mPopup.isAboveAnchor(); + + final ListAdapter adapter = mAdapter; + + boolean allEnabled; + int firstItem = Integer.MAX_VALUE; + int lastItem = Integer.MIN_VALUE; + + if (adapter != null) { + allEnabled = adapter.areAllItemsEnabled(); + firstItem = allEnabled ? 0 : + mDropDownList.lookForSelectablePosition(0, true); + lastItem = allEnabled ? adapter.getCount() - 1 : + mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false); + } + + if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) || + (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) { + // When the selection is at the top, we block the key + // event to prevent focus from moving. + clearListSelection(); + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + show(); + return true; + } else { + // WARNING: Please read the comment where mListSelectionHidden + // is declared + mDropDownList.mListSelectionHidden = false; + } + + consumed = mDropDownList.onKeyDown(keyCode, event); + if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed); + + if (consumed) { + // If it handled the key event, then the user is + // navigating in the list, so we should put it in front. + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + // Here's a little trick we need to do to make sure that + // the list view is actually showing its focus indicator, + // by ensuring it has focus and getting its window out + // of touch mode. + mDropDownList.requestFocusFromTouch(); + show(); + + switch (keyCode) { + // avoid passing the focus from the text view to the + // next component + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_UP: + return true; + } + } else { + if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + // when the selection is at the bottom, we block the + // event to avoid going to the next focusable widget + if (curIndex == lastItem) { + return true; + } + } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && + curIndex == firstItem) { + return true; + } + } + } + } + + return false; + } + + /** + * Filter key down events. By forwarding key up events to this function, + * views using non-modal ListPopupWindow can have it handle key selection of items. + * + * @param keyCode keyCode param passed to the host view's onKeyUp + * @param event event param passed to the host view's onKeyUp + * @return true if the event was handled, false if it was ignored. + * + * @see #setModal(boolean) + */ + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) { + boolean consumed = mDropDownList.onKeyUp(keyCode, event); + if (consumed && isConfirmKey(keyCode)) { + // if the list accepts the key events and the key event was a click, the text view + // gets the selected item from the drop down as its content + dismiss(); + } + return consumed; + } + return false; + } + + /** + * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)} + * events to this function, views using ListPopupWindow can have it dismiss the popup + * when the back key is pressed. + * + * @param keyCode keyCode param passed to the host view's onKeyPreIme + * @param event event param passed to the host view's onKeyPreIme + * @return true if the event was handled, false if it was ignored. + * + * @see #setModal(boolean) + */ + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) { + // special case for the back key, we do not even try to send it + // to the drop down list but instead, consume it immediately + final View anchorView = mDropDownAnchorView; + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState(); + if (state != null) { + state.startTracking(event, this); + } + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState(); + if (state != null) { + state.handleUpEvent(event); + } + if (event.isTracking() && !event.isCanceled()) { + dismiss(); + return true; + } + } + } + return false; + } + + /** + * Returns an {@link OnTouchListener} that can be added to the source view + * to implement drag-to-open behavior. Generally, the source view should be + * the same view that was passed to {@link #setAnchorView}. + *

+ * When the listener is set on a view, touching that view and dragging + * outside of its bounds will open the popup window. Lifting will select the + * currently touched list item. + *

+ * Example usage: + *

+     * ListPopupWindow myPopup = new ListPopupWindow(context);
+     * myPopup.setAnchor(myAnchor);
+     * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
+     * myAnchor.setOnTouchListener(dragListener);
+     * 
+ * + * @param src the view on which the resulting listener will be set + * @return a touch listener that controls drag-to-open behavior + */ + public OnTouchListener createDragToOpenListener(View src) { + return new ForwardingListener(src) { + @Override + public ListPopupWindow getPopup() { + return ListPopupWindow.this; + } + }; + } + + /** + *

Builds the popup window's content and returns the height the popup + * should have. Returns -1 when the content already exists.

+ * + * @return the content's height or -1 if content already exists + */ + private int buildDropDown() { + ViewGroup dropDownView; + int otherHeights = 0; + + if (mDropDownList == null) { + Context context = mContext; + + /** + * This Runnable exists for the sole purpose of checking if the view layout has got + * completed and if so call showDropDown to display the drop down. This is used to show + * the drop down as soon as possible after user opens up the search dialog, without + * waiting for the normal UI pipeline to do it's job which is slower than this method. + */ + mShowDropDownRunnable = new Runnable() { + public void run() { + // View layout should be all done before displaying the drop down. + View view = getAnchorView(); + if (view != null && view.getWindowToken() != null) { + show(); + } + } + }; + + 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 a 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: + Log.e(TAG, "Invalid hint position " + mPromptPosition); + 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; + } + } else { + mTempRect.setEmpty(); + } + + // Max height available on the screen for a popup. + boolean ignoreBottomDecorations = + mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; + final int maxHeight = mPopup.getMaxAvailableHeight( + getAnchorView(), mDropDownVerticalOffset /*, ignoreBottomDecorations*/); + + if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { + return maxHeight + padding; + } + + final int childWidthSpec; + switch (mDropDownWidth) { + case ViewGroup.LayoutParams.WRAP_CONTENT: + childWidthSpec = MeasureSpec.makeMeasureSpec( + mContext.getResources().getDisplayMetrics().widthPixels - + (mTempRect.left + mTempRect.right), + MeasureSpec.AT_MOST); + break; + case ViewGroup.LayoutParams.MATCH_PARENT: + childWidthSpec = MeasureSpec.makeMeasureSpec( + mContext.getResources().getDisplayMetrics().widthPixels - + (mTempRect.left + mTempRect.right), + MeasureSpec.EXACTLY); + break; + default: + childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY); + break; + } + + final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec, + 0, DropDownListView.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; + } + + /** + * Abstract class that forwards touch events to a {@link ListPopupWindow}. + * + * @hide + */ + public static abstract class ForwardingListener implements View.OnTouchListener { + /** Scaled touch slop, used for detecting movement outside bounds. */ + private final float mScaledTouchSlop; + + /** Timeout before disallowing intercept on the source's parent. */ + private final int mTapTimeout; + /** Timeout before accepting a long-press to start forwarding. */ + private final int mLongPressTimeout; + + /** Source view from which events are forwarded. */ + private final View mSrc; + + /** Runnable used to prevent conflicts with scrolling parents. */ + private Runnable mDisallowIntercept; + /** Runnable used to trigger forwarding on long-press. */ + private Runnable mTriggerLongPress; + + /** Whether this listener is currently forwarding touch events. */ + private boolean mForwarding; + /** + * Whether forwarding was initiated by a long-press. If so, we won't + * force the window to dismiss when the touch stream ends. + */ + private boolean mWasLongPress; + + /** The id of the first pointer down in the current event stream. */ + private int mActivePointerId; + + /** + * Temporary Matrix instance + */ + private final int[] mTmpLocation = new int[2]; + + public ForwardingListener(View src) { + mSrc = src; + mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop(); + mTapTimeout = ViewConfiguration.getTapTimeout(); + // Use a medium-press timeout. Halfway between tap and long-press. + mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2; + } + + /** + * Returns the popup to which this listener is forwarding events. + *

+ * Override this to return the correct popup. If the popup is displayed + * asynchronously, you may also need to override + * {@link #onForwardingStopped} to prevent premature cancelation of + * forwarding. + * + * @return the popup to which this listener is forwarding events + */ + public abstract ListPopupWindow getPopup(); + + @Override + public boolean onTouch(View v, MotionEvent event) { + final boolean wasForwarding = mForwarding; + final boolean forwarding; + if (wasForwarding) { + if (mWasLongPress) { + // If we started forwarding as a result of a long-press, + // just silently stop forwarding events so that the window + // stays open. + forwarding = onTouchForwarded(event); + } else { + forwarding = onTouchForwarded(event) || !onForwardingStopped(); + } + } else { + forwarding = onTouchObserved(event) && onForwardingStarted(); + + if (forwarding) { + // Make sure we cancel any ongoing source event stream. + final long now = SystemClock.uptimeMillis(); + final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, + 0.0f, 0.0f, 0); + mSrc.onTouchEvent(e); + e.recycle(); + } + } + + mForwarding = forwarding; + return forwarding || wasForwarding; + } + + /** + * Called when forwarding would like to start.

By default, this will show the popup + * returned by {@link #getPopup()}. It may be overridden to perform another action, like + * clicking the source view or preparing the popup before showing it. + * + * @return true to start forwarding, false otherwise + */ + protected boolean onForwardingStarted() { + final ListPopupWindow popup = getPopup(); + if (popup != null && !popup.isShowing()) { + popup.show(); + } + return true; + } + + /** + * Called when forwarding would like to stop.

By default, this will dismiss the popup + * returned by {@link #getPopup()}. It may be overridden to perform some other action. + * + * @return true to stop forwarding, false otherwise + */ + protected boolean onForwardingStopped() { + final ListPopupWindow popup = getPopup(); + if (popup != null && popup.isShowing()) { + popup.dismiss(); + } + return true; + } + + /** + * Observes motion events and determines when to start forwarding. + * + * @param srcEvent motion event in source view coordinates + * @return true to start forwarding motion events, false otherwise + */ + private boolean onTouchObserved(MotionEvent srcEvent) { + final View src = mSrc; + if (!src.isEnabled()) { + return false; + } + + final int actionMasked = MotionEventCompat.getActionMasked(srcEvent); + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = srcEvent.getPointerId(0); + mWasLongPress = false; + + if (mDisallowIntercept == null) { + mDisallowIntercept = new DisallowIntercept(); + } + src.postDelayed(mDisallowIntercept, mTapTimeout); + if (mTriggerLongPress == null) { + mTriggerLongPress = new TriggerLongPress(); + } + src.postDelayed(mTriggerLongPress, mLongPressTimeout); + break; + case MotionEvent.ACTION_MOVE: + final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId); + if (activePointerIndex >= 0) { + final float x = srcEvent.getX(activePointerIndex); + final float y = srcEvent.getY(activePointerIndex); + if (!pointInView(src, x, y, mScaledTouchSlop)) { + clearCallbacks(); + + // Don't let the parent intercept our events. + src.getParent().requestDisallowInterceptTouchEvent(true); + return true; + } + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + clearCallbacks(); + break; + } + + return false; + } + + private void clearCallbacks() { + if (mTriggerLongPress != null) { + mSrc.removeCallbacks(mTriggerLongPress); + } + + if (mDisallowIntercept != null) { + mSrc.removeCallbacks(mDisallowIntercept); + } + } + + private void onLongPress() { + clearCallbacks(); + + final View src = mSrc; + if (!src.isEnabled() || src.isLongClickable()) { + // Ignore long-press if the view is disabled or has its own + // handler. + return; + } + + if (!onForwardingStarted()) { + return; + } + + // Don't let the parent intercept our events. + src.getParent().requestDisallowInterceptTouchEvent(true); + + // Make sure we cancel any ongoing source event stream. + final long now = SystemClock.uptimeMillis(); + final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); + src.onTouchEvent(e); + e.recycle(); + + mForwarding = true; + mWasLongPress = true; + } + + /** + * Handled forwarded motion events and determines when to stop forwarding. + * + * @param srcEvent motion event in source view coordinates + * @return true to continue forwarding motion events, false to cancel + */ + private boolean onTouchForwarded(MotionEvent srcEvent) { + final View src = mSrc; + final ListPopupWindow popup = getPopup(); + if (popup == null || !popup.isShowing()) { + return false; + } + + final DropDownListView dst = popup.mDropDownList; + if (dst == null || !dst.isShown()) { + return false; + } + + // Convert event to destination-local coordinates. + final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent); + toGlobalMotionEvent(src, dstEvent); + toLocalMotionEvent(dst, dstEvent); + + // Forward converted event to destination view, then recycle it. + final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId); + dstEvent.recycle(); + + // Always cancel forwarding when the touch stream ends. + final int action = MotionEventCompat.getActionMasked(srcEvent); + final boolean keepForwarding = action != MotionEvent.ACTION_UP + && action != MotionEvent.ACTION_CANCEL; + + return handled && keepForwarding; + } + + private static boolean pointInView(View view, float localX, float localY, float slop) { + return localX >= -slop && localY >= -slop && + localX < ((view.getRight() - view.getLeft()) + slop) && + localY < ((view.getBottom() - view.getTop()) + slop); + } + + /** + * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations + * (scaleX, scaleY, etc). + */ + private boolean toLocalMotionEvent(View view, MotionEvent event) { + final int[] loc = mTmpLocation; + view.getLocationOnScreen(loc); + event.offsetLocation(-loc[0], -loc[1]); + return true; + } + + /** + * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations + * (scaleX, scaleY, etc). + */ + private boolean toGlobalMotionEvent(View view, MotionEvent event) { + final int[] loc = mTmpLocation; + view.getLocationOnScreen(loc); + event.offsetLocation(loc[0], loc[1]); + return true; + } + + private class DisallowIntercept implements Runnable { + @Override + public void run() { + final ViewParent parent = mSrc.getParent(); + parent.requestDisallowInterceptTouchEvent(true); + } + } + + private class TriggerLongPress implements Runnable { + @Override + public void run() { + onLongPress(); + } + } + } + + /** + *

Wrapper class for a ListView. This wrapper can hijack the focus to + * make sure the list uses the appropriate drawables and states when + * displayed on screen within a drop down. The focus is never actually + * passed to the drop down in this mode; the list only looks focused.

+ */ + private static class DropDownListView extends ListViewCompat { + + /* + * 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; + + /** + * True if this wrapper should fake focus. + */ + private boolean mHijackFocus; + + /** Whether to force drawing of the pressed state selector. */ + private boolean mDrawsInPressedState; + + /** Current drag-to-open click animation, if any. */ + private ViewPropertyAnimatorCompat mClickAnimation; + + /** Helper for drag-to-open auto scrolling. */ + private ListViewAutoScrollHelper mScrollHelper; + + /** + *

Creates a new list view wrapper.

+ * + * @param context this view's context + */ + public DropDownListView(Context context, boolean hijackFocus) { + super(context, null, R.attr.dropDownListViewStyle); + mHijackFocus = hijackFocus; + setCacheColorHint(0); // Transparent, since the background drawable could be anything. + } + + /** + * Handles forwarded events. + * + * @param activePointerId id of the pointer that activated forwarding + * @return whether the event was handled + */ + public boolean onForwardedEvent(MotionEvent event, int activePointerId) { + boolean handledEvent = true; + boolean clearPressedItem = false; + + final int actionMasked = MotionEventCompat.getActionMasked(event); + switch (actionMasked) { + case MotionEvent.ACTION_CANCEL: + handledEvent = false; + break; + case MotionEvent.ACTION_UP: + handledEvent = false; + // $FALL-THROUGH$ + case MotionEvent.ACTION_MOVE: + final int activeIndex = event.findPointerIndex(activePointerId); + if (activeIndex < 0) { + handledEvent = false; + break; + } + + final int x = (int) event.getX(activeIndex); + final int y = (int) event.getY(activeIndex); + final int position = pointToPosition(x, y); + if (position == INVALID_POSITION) { + clearPressedItem = true; + break; + } + + final View child = getChildAt(position - getFirstVisiblePosition()); + setPressedItem(child, position, x, y); + handledEvent = true; + + if (actionMasked == MotionEvent.ACTION_UP) { + clickPressedItem(child, position); + } + break; + } + + // Failure to handle the event cancels forwarding. + if (!handledEvent || clearPressedItem) { + clearPressedItem(); + } + + // Manage automatic scrolling. + if (handledEvent) { + if (mScrollHelper == null) { + mScrollHelper = new ListViewAutoScrollHelper(this); + } + mScrollHelper.setEnabled(true); + mScrollHelper.onTouch(this, event); + } else if (mScrollHelper != null) { + mScrollHelper.setEnabled(false); + } + + return handledEvent; + } + + /** + * Starts an alpha animation on the selector. When the animation ends, + * the list performs a click on the item. + */ + private void clickPressedItem(final View child, final int position) { + final long id = getItemIdAtPosition(position); + performItemClick(child, position, id); + } + + private void clearPressedItem() { + mDrawsInPressedState = false; + setPressed(false); + // This will call through to updateSelectorState() + drawableStateChanged(); + + if (mClickAnimation != null) { + mClickAnimation.cancel(); + mClickAnimation = null; + } + } + + private void setPressedItem(View child, int position, float x, float y) { + mDrawsInPressedState = true; + + // Ordering is essential. First update the pressed state and layout + // the children. This will ensure the selector actually gets drawn. + setPressed(true); + layoutChildren(); + + // Ensure that keyboard focus starts from the last touched position. + setSelection(position); + positionSelectorLikeTouchCompat(position, child, x, y); + + // This needs some explanation. We need to disable the selector for this next call + // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat + // will draw the selector and bad things happen. + setSelectorEnabled(false); + + // Refresh the drawable state to reflect the new pressed state, + // which will also update the selector state. + refreshDrawableState(); + } + + @Override + protected boolean touchModeDrawsInPressedStateCompat() { + return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat(); + } + + @Override + public boolean isInTouchMode() { + // WARNING: Please read the comment where mListSelectionHidden is declared + return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode(); + } + + /** + *

Returns the focus state in the drop down.

+ * + * @return true always if hijacking focus + */ + @Override + public boolean hasWindowFocus() { + return mHijackFocus || super.hasWindowFocus(); + } + + /** + *

Returns the focus state in the drop down.

+ * + * @return true always if hijacking focus + */ + @Override + public boolean isFocused() { + return mHijackFocus || super.isFocused(); + } + + /** + *

Returns the focus state in the drop down.

+ * + * @return true always if hijacking focus + */ + @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(); + } + } + } + + private static boolean isConfirmKey(int keyCode) { + return keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_CENTER; + } + + private void setPopupClipToScreenEnabled(boolean clip) { + if (sClipToWindowEnabledMethod != null) { + try { + sClipToWindowEnabledMethod.invoke(mPopup, clip); + } catch (Exception e) { + Log.i(TAG, "Could not call setClipToScreenEnabled() on PopupWindow. Oh well."); + } + } + } + +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/PopupMenu.java b/eclipse-compile/appcompat/src/android/support/v7/widget/PopupMenu.java new file mode 100644 index 0000000000..17f22a802a --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/PopupMenu.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2014 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 android.support.v7.widget; + + +import android.content.Context; +import android.support.annotation.MenuRes; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.SupportMenuInflater; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuPopupHelper; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.support.v7.internal.view.renamemenu.SubMenuBuilder; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +/** + * Static library support version of the framework's {@link android.widget.PopupMenu}. + * Used to write apps that run on platforms prior to Android 3.0. When running + * on Android 3.0 or above, this implementation is still used; it does not try + * to switch to the framework's implementation. See the framework SDK + * documentation for a class overview. + */ +public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { + private Context mContext; + private MenuBuilder mMenu; + private View mAnchor; + private MenuPopupHelper mPopup; + private OnMenuItemClickListener mMenuItemClickListener; + private OnDismissListener mDismissListener; + private View.OnTouchListener mDragListener; + + /** + * Callback interface used to notify the application that the menu has closed. + */ + public interface OnDismissListener { + /** + * Called when the associated menu has been dismissed. + * + * @param menu The PopupMenu that was dismissed. + */ + public void onDismiss(PopupMenu menu); + } + + /** + * Construct a new PopupMenu. + * + * @param context Context for the PopupMenu. + * @param anchor Anchor view for this popup. The popup will appear below the anchor if there + * is room, or above it if there is not. + */ + public PopupMenu(Context context, View anchor) { + this(context, anchor, Gravity.NO_GRAVITY); + } + + /** + * Constructor to create a new popup menu with an anchor view and alignment + * gravity. + * + * @param context Context the popup menu is running in, through which it + * can access the current theme, resources, etc. + * @param anchor Anchor view for this popup. The popup will appear below + * the anchor if there is room, or above it if there is not. + * @param gravity The {@link Gravity} value for aligning the popup with its + * anchor. + */ + public PopupMenu(Context context, View anchor, int gravity) { + this(context, anchor, gravity, R.attr.popupMenuStyle, 0); + } + + /** + * Constructor a create a new popup menu with a specific style. + * + * @param context Context the popup menu is running in, through which it + * can access the current theme, resources, etc. + * @param anchor Anchor view for this popup. The popup will appear below + * the anchor if there is room, or above it if there is not. + * @param gravity The {@link Gravity} value for aligning the popup with its + * anchor. + * @param popupStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the popup window. Can be 0 to not look for defaults. + * @param popupStyleRes A resource identifier of a style resource that + * supplies default values for the popup window, used only if + * popupStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr, + int popupStyleRes) { + mContext = context; + mMenu = new MenuBuilder(context); + mMenu.setCallback(this); + mAnchor = anchor; + mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes); + mPopup.setGravity(gravity); + mPopup.setCallback(this); + } + + /** + * Returns an {@link android.view.View.OnTouchListener} that can be added to the anchor view + * to implement drag-to-open behavior. + *

+ * When the listener is set on a view, touching that view and dragging + * outside of its bounds will open the popup window. Lifting will select the + * currently touched list item. + *

+ * Example usage: + *

+     * PopupMenu myPopup = new PopupMenu(context, myAnchor);
+     * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener());
+     * 
+ * + * @return a touch listener that controls drag-to-open behavior + */ + public View.OnTouchListener getDragToOpenListener() { + if (mDragListener == null) { + mDragListener = new ListPopupWindow.ForwardingListener(mAnchor) { + @Override + protected boolean onForwardingStarted() { + show(); + return true; + } + + @Override + protected boolean onForwardingStopped() { + dismiss(); + return true; + } + + @Override + public ListPopupWindow getPopup() { + // This will be null until show() is called. + return mPopup.getPopup(); + } + }; + } + + return mDragListener; + } + + /** + * @return the {@link Menu} associated with this popup. Populate the returned Menu with + * items before calling {@link #show()}. + * + * @see #show() + * @see #getMenuInflater() + */ + public Menu getMenu() { + return mMenu; + } + + /** + * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the + * menu returned by {@link #getMenu()}. + * + * @see #getMenu() + */ + public MenuInflater getMenuInflater() { + return new SupportMenuInflater(mContext); + } + + /** + * Inflate a menu resource into this PopupMenu. This is equivalent to calling + * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()). + * @param menuRes Menu resource to inflate + */ + public void inflate(@MenuRes int menuRes) { + getMenuInflater().inflate(menuRes, mMenu); + } + + /** + * Show the menu popup anchored to the view specified during construction. + * @see #dismiss() + */ + public void show() { + mPopup.show(); + } + + /** + * Dismiss the menu popup. + * @see #show() + */ + public void dismiss() { + mPopup.dismiss(); + } + + /** + * Set a listener that will be notified when the user selects an item from the menu. + * + * @param listener Listener to notify + */ + public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { + mMenuItemClickListener = listener; + } + + /** + * Set a listener that will be notified when this menu is dismissed. + * + * @param listener Listener to notify + */ + public void setOnDismissListener(OnDismissListener listener) { + mDismissListener = listener; + } + + /** + * @hide + */ + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + if (mMenuItemClickListener != null) { + return mMenuItemClickListener.onMenuItemClick(item); + } + return false; + } + + /** + * @hide + */ + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (mDismissListener != null) { + mDismissListener.onDismiss(this); + } + } + + /** + * @hide + */ + public boolean onOpenSubMenu(MenuBuilder subMenu) { + if (subMenu == null) return false; + + if (!subMenu.hasVisibleItems()) { + return true; + } + + // Current menu will be dismissed by the normal helper, submenu will be shown in its place. + new MenuPopupHelper(mContext, subMenu, mAnchor).show(); + return true; + } + + /** + * @hide + */ + public void onCloseSubMenu(SubMenuBuilder menu) { + } + + /** + * @hide + */ + public void onMenuModeChange(MenuBuilder menu) { + } + + /** + * Interface responsible for receiving menu item click events if the items themselves + * do not have individual item click listeners. + */ + public interface OnMenuItemClickListener { + /** + * This method will be invoked when a menu item is clicked if the item itself did + * not already handle the event. + * + * @param item {@link MenuItem} that was clicked + * @return true if the event was handled, false otherwise. + */ + public boolean onMenuItemClick(MenuItem item); + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/SearchView.java b/eclipse-compile/appcompat/src/android/support/v7/widget/SearchView.java new file mode 100644 index 0000000000..2eb8778cbc --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/SearchView.java @@ -0,0 +1,1820 @@ +/* + * Copyright (C) 2014 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 android.support.v7.widget; + +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.ActivityNotFoundException; +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.database.Cursor; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.speech.RecognizerIntent; +import android.support.v4.view.KeyEventCompat; +import android.support.v4.widget.CursorAdapter; +import android.support.v7.appcompat.R; +import android.support.v7.internal.widget.TintAutoCompleteTextView; +import android.support.v7.internal.widget.TintManager; +import android.support.v7.internal.widget.TintTypedArray; +import android.support.v7.internal.widget.ViewUtils; +import android.support.v7.view.CollapsibleActionView; +import android.text.Editable; +import android.text.InputType; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.style.ImageSpan; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.AutoCompleteTextView; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import java.lang.reflect.Method; +import java.util.WeakHashMap; + +import static android.support.v7.widget.SuggestionsAdapter.getColumnString; + +/** + * A widget that provides a user interface for the user to enter a search query and submit a request + * to a search provider. Shows a list of query suggestions or results, if available, and allows the + * user to pick a suggestion or result to launch into. + * + *

Note: This class is included in the support library for compatibility + * with API level 7 and higher. If you're developing your app for API level 11 and higher + * only, you should instead use the framework {@link android.widget.SearchView} class.

+ * + *

+ * When the SearchView is used in an {@link android.support.v7.app.ActionBar} + * as an action view, it's collapsed by default, so you must provide an icon for the action. + *

+ *

+ * If you want the search field to always be visible, then call + * {@link #setIconifiedByDefault(boolean) setIconifiedByDefault(false)}. + *

+ * + *
+ *

Developer Guides

+ *

For information about using {@code SearchView}, read the + * Search API guide. + * Additional information about action views is also available in the <Action Bar API guide

+ *
+ * + * @see android.support.v4.view.MenuItemCompat#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW + */ +public class SearchView extends LinearLayoutCompat implements CollapsibleActionView { + + private static final boolean DBG = false; + private static final String LOG_TAG = "SearchView"; + + private static final boolean IS_AT_LEAST_FROYO = Build.VERSION.SDK_INT >= 8; + + /** + * Private constant for removing the microphone in the keyboard. + */ + private static final String IME_OPTION_NO_MICROPHONE = "nm"; + + private final SearchAutoComplete mSearchSrcTextView; + private final View mSearchEditFrame; + private final View mSearchPlate; + private final View mSubmitArea; + private final ImageView mSearchButton; + private final ImageView mGoButton; + private final ImageView mCloseButton; + private final ImageView mVoiceButton; + private final View mDropDownAnchor; + + /** Icon optionally displayed when the SearchView is collapsed. */ + private final ImageView mCollapsedIcon; + + /** Drawable used as an EditText hint. */ + private final Drawable mSearchHintIcon; + + // Resources used by SuggestionsAdapter to display suggestions. + private final int mSuggestionRowLayout; + private final int mSuggestionCommitIconResId; + + // Intents used for voice searching. + private final Intent mVoiceWebSearchIntent; + private final Intent mVoiceAppSearchIntent; + + private OnQueryTextListener mOnQueryChangeListener; + private OnCloseListener mOnCloseListener; + private OnFocusChangeListener mOnQueryTextFocusChangeListener; + private OnSuggestionListener mOnSuggestionListener; + private OnClickListener mOnSearchClickListener; + + private boolean mIconifiedByDefault; + private boolean mIconified; + private CursorAdapter mSuggestionsAdapter; + private boolean mSubmitButtonEnabled; + private CharSequence mQueryHint; + private boolean mQueryRefinement; + private boolean mClearingFocus; + private int mMaxWidth; + private boolean mVoiceButtonEnabled; + private CharSequence mOldQueryText; + private CharSequence mUserQuery; + private boolean mExpandedInActionView; + private int mCollapsedImeOptions; + + private SearchableInfo mSearchable; + private Bundle mAppSearchData; + + private final TintManager mTintManager; + + static final AutoCompleteTextViewReflector HIDDEN_METHOD_INVOKER = new AutoCompleteTextViewReflector(); + + /* + * SearchView can be set expanded before the IME is ready to be shown during + * initial UI setup. The show operation is asynchronous to account for this. + */ + private Runnable mShowImeRunnable = new Runnable() { + public void run() { + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + if (imm != null) { + HIDDEN_METHOD_INVOKER.showSoftInputUnchecked(imm, SearchView.this, 0); + } + } + }; + + private final Runnable mUpdateDrawableStateRunnable = new Runnable() { + public void run() { + updateFocusedState(); + } + }; + + private Runnable mReleaseCursorRunnable = new Runnable() { + public void run() { + if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) { + mSuggestionsAdapter.changeCursor(null); + } + } + }; + + // A weak map of drawables we've gotten from other packages, so we don't load them + // more than once. + private final WeakHashMap mOutsideDrawablesCache = + new WeakHashMap(); + + /** + * Callbacks for changes to the query text. + */ + public interface OnQueryTextListener { + + /** + * Called when the user submits the query. This could be due to a key press on the + * keyboard or due to pressing a submit button. + * The listener can override the standard behavior by returning true + * to indicate that it has handled the submit request. Otherwise return false to + * let the SearchView handle the submission by launching any associated intent. + * + * @param query the query text that is to be submitted + * + * @return true if the query has been handled by the listener, false to let the + * SearchView perform the default action. + */ + boolean onQueryTextSubmit(String query); + + /** + * Called when the query text is changed by the user. + * + * @param newText the new content of the query text field. + * + * @return false if the SearchView should perform the default action of showing any + * suggestions if available, true if the action was handled by the listener. + */ + boolean onQueryTextChange(String newText); + } + + public interface OnCloseListener { + + /** + * The user is attempting to close the SearchView. + * + * @return true if the listener wants to override the default behavior of clearing the + * text field and dismissing it, false otherwise. + */ + boolean onClose(); + } + + /** + * Callback interface for selection events on suggestions. These callbacks + * are only relevant when a SearchableInfo has been specified by {@link #setSearchableInfo}. + */ + public interface OnSuggestionListener { + + /** + * Called when a suggestion was selected by navigating to it. + * @param position the absolute position in the list of suggestions. + * + * @return true if the listener handles the event and wants to override the default + * behavior of possibly rewriting the query based on the selected item, false otherwise. + */ + boolean onSuggestionSelect(int position); + + /** + * Called when a suggestion was clicked. + * @param position the absolute position of the clicked item in the list of suggestions. + * + * @return true if the listener handles the event and wants to override the default + * behavior of launching any intent or submitting a search query specified on that item. + * Return false otherwise. + */ + boolean onSuggestionClick(int position); + } + + public SearchView(Context context) { + this(context, null); + } + + public SearchView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.searchViewStyle); + } + + public SearchView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, + attrs, R.styleable.SearchView, defStyleAttr, 0); + // Keep the TintManager in case we need it later + mTintManager = a.getTintManager(); + + final LayoutInflater inflater = LayoutInflater.from(context); + final int layoutResId = a.getResourceId( + R.styleable.SearchView_layout, R.layout.abc_search_view); + inflater.inflate(layoutResId, this, true); + + mSearchSrcTextView = (SearchAutoComplete) findViewById(R.id.search_src_text); + mSearchSrcTextView.setSearchView(this); + + mSearchEditFrame = findViewById(R.id.search_edit_frame); + mSearchPlate = findViewById(R.id.search_plate); + mSubmitArea = findViewById(R.id.submit_area); + mSearchButton = (ImageView) findViewById(R.id.search_button); + mGoButton = (ImageView) findViewById(R.id.search_go_btn); + mCloseButton = (ImageView) findViewById(R.id.search_close_btn); + mVoiceButton = (ImageView) findViewById(R.id.search_voice_btn); + mCollapsedIcon = (ImageView) findViewById(R.id.search_mag_icon); + + // Set up icons and backgrounds. + mSearchPlate.setBackgroundDrawable(a.getDrawable(R.styleable.SearchView_queryBackground)); + mSubmitArea.setBackgroundDrawable(a.getDrawable(R.styleable.SearchView_submitBackground)); + mSearchButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon)); + mGoButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon)); + mCloseButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_closeIcon)); + mVoiceButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_voiceIcon)); + mCollapsedIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon)); + + mSearchHintIcon = a.getDrawable(R.styleable.SearchView_searchHintIcon); + + // Extract dropdown layout resource IDs for later use. + mSuggestionRowLayout = a.getResourceId(R.styleable.SearchView_suggestionRowLayout, + R.layout.abc_search_dropdown_item_icons_2line); + mSuggestionCommitIconResId = a.getResourceId(R.styleable.SearchView_commitIcon, 0); + + mSearchButton.setOnClickListener(mOnClickListener); + mCloseButton.setOnClickListener(mOnClickListener); + mGoButton.setOnClickListener(mOnClickListener); + mVoiceButton.setOnClickListener(mOnClickListener); + mSearchSrcTextView.setOnClickListener(mOnClickListener); + + mSearchSrcTextView.addTextChangedListener(mTextWatcher); + mSearchSrcTextView.setOnEditorActionListener(mOnEditorActionListener); + mSearchSrcTextView.setOnItemClickListener(mOnItemClickListener); + mSearchSrcTextView.setOnItemSelectedListener(mOnItemSelectedListener); + mSearchSrcTextView.setOnKeyListener(mTextKeyListener); + + // Inform any listener of focus changes + mSearchSrcTextView.setOnFocusChangeListener(new OnFocusChangeListener() { + + public void onFocusChange(View v, boolean hasFocus) { + if (mOnQueryTextFocusChangeListener != null) { + mOnQueryTextFocusChangeListener.onFocusChange(SearchView.this, hasFocus); + } + } + }); + setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true)); + + final int maxWidth = a.getDimensionPixelSize(R.styleable.SearchView_android_maxWidth, -1); + if (maxWidth != -1) { + setMaxWidth(maxWidth); + } + + final CharSequence queryHint = a.getText(R.styleable.SearchView_queryHint); + if (!TextUtils.isEmpty(queryHint)) { + setQueryHint(queryHint); + } + + final int imeOptions = a.getInt(R.styleable.SearchView_android_imeOptions, -1); + if (imeOptions != -1) { + setImeOptions(imeOptions); + } + + final int inputType = a.getInt(R.styleable.SearchView_android_inputType, -1); + if (inputType != -1) { + setInputType(inputType); + } + + boolean focusable = true; + focusable = a.getBoolean(R.styleable.SearchView_android_focusable, focusable); + setFocusable(focusable); + + a.recycle(); + + // Save voice intent for later queries/launching + mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); + + mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + mDropDownAnchor = findViewById(mSearchSrcTextView.getDropDownAnchor()); + if (mDropDownAnchor != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + addOnLayoutChangeListenerToDropDownAnchorSDK11(); + } else { + addOnLayoutChangeListenerToDropDownAnchorBase(); + } + } + + updateViewsVisibility(mIconifiedByDefault); + updateQueryHint(); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private void addOnLayoutChangeListenerToDropDownAnchorSDK11() { + mDropDownAnchor.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + adjustDropDownSizeAndPosition(); + } + }); + } + + private void addOnLayoutChangeListenerToDropDownAnchorBase() { + mDropDownAnchor.getViewTreeObserver() + .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + adjustDropDownSizeAndPosition(); + } + }); + } + + int getSuggestionRowLayout() { + return mSuggestionRowLayout; + } + + int getSuggestionCommitIconResId() { + return mSuggestionCommitIconResId; + } + + /** + * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used + * to display labels, hints, suggestions, create intents for launching search results screens + * and controlling other affordances such as a voice button. + * + * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific + * activity or a global search provider. + */ + public void setSearchableInfo(SearchableInfo searchable) { + mSearchable = searchable; + if (mSearchable != null) { + if (IS_AT_LEAST_FROYO) { + updateSearchAutoComplete(); + } + updateQueryHint(); + } + // Cache the voice search capability + mVoiceButtonEnabled = IS_AT_LEAST_FROYO && hasVoiceSearch(); + + if (mVoiceButtonEnabled) { + // Disable the microphone on the keyboard, as a mic is displayed near the text box + // TODO: use imeOptions to disable voice input when the new API will be available + mSearchSrcTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE); + } + updateViewsVisibility(isIconified()); + } + + /** + * Sets the APP_DATA for legacy SearchDialog use. + * @param appSearchData bundle provided by the app when launching the search dialog + * @hide + */ + public void setAppSearchData(Bundle appSearchData) { + mAppSearchData = appSearchData; + } + + /** + * Sets the IME options on the query text field. + * + * @see TextView#setImeOptions(int) + * @param imeOptions the options to set on the query text field + */ + public void setImeOptions(int imeOptions) { + mSearchSrcTextView.setImeOptions(imeOptions); + } + + /** + * Returns the IME options set on the query text field. + * @return the ime options + * @see TextView#setImeOptions(int) + */ + public int getImeOptions() { + return mSearchSrcTextView.getImeOptions(); + } + + /** + * Sets the input type on the query text field. + * + * @see TextView#setInputType(int) + * @param inputType the input type to set on the query text field + */ + public void setInputType(int inputType) { + mSearchSrcTextView.setInputType(inputType); + } + + /** + * Returns the input type set on the query text field. + * @return the input type + */ + public int getInputType() { + return mSearchSrcTextView.getInputType(); + } + + /** @hide */ + @Override + public boolean requestFocus(int direction, Rect previouslyFocusedRect) { + // Don't accept focus if in the middle of clearing focus + if (mClearingFocus) return false; + // Check if SearchView is focusable. + if (!isFocusable()) return false; + // If it is not iconified, then give the focus to the text field + if (!isIconified()) { + boolean result = mSearchSrcTextView.requestFocus(direction, previouslyFocusedRect); + if (result) { + updateViewsVisibility(false); + } + return result; + } else { + return super.requestFocus(direction, previouslyFocusedRect); + } + } + + /** @hide */ + @Override + public void clearFocus() { + mClearingFocus = true; + setImeVisibility(false); + super.clearFocus(); + mSearchSrcTextView.clearFocus(); + mClearingFocus = false; + } + + /** + * Sets a listener for user actions within the SearchView. + * + * @param listener the listener object that receives callbacks when the user performs + * actions in the SearchView such as clicking on buttons or typing a query. + */ + public void setOnQueryTextListener(OnQueryTextListener listener) { + mOnQueryChangeListener = listener; + } + + /** + * Sets a listener to inform when the user closes the SearchView. + * + * @param listener the listener to call when the user closes the SearchView. + */ + public void setOnCloseListener(OnCloseListener listener) { + mOnCloseListener = listener; + } + + /** + * Sets a listener to inform when the focus of the query text field changes. + * + * @param listener the listener to inform of focus changes. + */ + public void setOnQueryTextFocusChangeListener(OnFocusChangeListener listener) { + mOnQueryTextFocusChangeListener = listener; + } + + /** + * Sets a listener to inform when a suggestion is focused or clicked. + * + * @param listener the listener to inform of suggestion selection events. + */ + public void setOnSuggestionListener(OnSuggestionListener listener) { + mOnSuggestionListener = listener; + } + + /** + * Sets a listener to inform when the search button is pressed. This is only + * relevant when the text field is not visible by default. Calling {@link #setIconified + * setIconified(false)} can also cause this listener to be informed. + * + * @param listener the listener to inform when the search button is clicked or + * the text field is programmatically de-iconified. + */ + public void setOnSearchClickListener(OnClickListener listener) { + mOnSearchClickListener = listener; + } + + /** + * Returns the query string currently in the text field. + * + * @return the query string + */ + public CharSequence getQuery() { + return mSearchSrcTextView.getText(); + } + + /** + * Sets a query string in the text field and optionally submits the query as well. + * + * @param query the query string. This replaces any query text already present in the + * text field. + * @param submit whether to submit the query right now or only update the contents of + * text field. + */ + public void setQuery(CharSequence query, boolean submit) { + mSearchSrcTextView.setText(query); + if (query != null) { + mSearchSrcTextView.setSelection(mSearchSrcTextView.length()); + mUserQuery = query; + } + + // If the query is not empty and submit is requested, submit the query + if (submit && !TextUtils.isEmpty(query)) { + onSubmitQuery(); + } + } + + /** + * Sets the hint text to display in the query text field. This overrides any hint specified + * in the SearchableInfo. + * + * @param hint the hint text to display + */ + public void setQueryHint(CharSequence hint) { + mQueryHint = hint; + updateQueryHint(); + } + + /** + * Gets the hint text to display in the query text field. + * @return the query hint text, if specified, null otherwise. + */ + public CharSequence getQueryHint() { + if (mQueryHint != null) { + return mQueryHint; + } else if (IS_AT_LEAST_FROYO && mSearchable != null) { + CharSequence hint = null; + int hintId = mSearchable.getHintId(); + if (hintId != 0) { + hint = getContext().getString(hintId); + } + return hint; + } + return null; + } + + /** + * Sets the default or resting state of the search field. If true, a single search icon is + * shown by default and expands to show the text field and other buttons when pressed. Also, + * if the default state is iconified, then it collapses to that state when the close button + * is pressed. Changes to this property will take effect immediately. + * + *

The default value is true.

+ * + * @param iconified whether the search field should be iconified by default + */ + public void setIconifiedByDefault(boolean iconified) { + if (mIconifiedByDefault == iconified) return; + mIconifiedByDefault = iconified; + updateViewsVisibility(iconified); + updateQueryHint(); + } + + /** + * Returns the default iconified state of the search field. + * @return + */ + public boolean isIconfiedByDefault() { + return mIconifiedByDefault; + } + + /** + * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is + * a temporary state and does not override the default iconified state set by + * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then + * a false here will only be valid until the user closes the field. And if the default + * state is expanded, then a true here will only clear the text field and not close it. + * + * @param iconify a true value will collapse the SearchView to an icon, while a false will + * expand it. + */ + public void setIconified(boolean iconify) { + if (iconify) { + onCloseClicked(); + } else { + onSearchClicked(); + } + } + + /** + * Returns the current iconified state of the SearchView. + * + * @return true if the SearchView is currently iconified, false if the search field is + * fully visible. + */ + public boolean isIconified() { + return mIconified; + } + + /** + * Enables showing a submit button when the query is non-empty. In cases where the SearchView + * is being used to filter the contents of the current activity and doesn't launch a separate + * results activity, then the submit button should be disabled. + * + * @param enabled true to show a submit button for submitting queries, false if a submit + * button is not required. + */ + public void setSubmitButtonEnabled(boolean enabled) { + mSubmitButtonEnabled = enabled; + updateViewsVisibility(isIconified()); + } + + /** + * Returns whether the submit button is enabled when necessary or never displayed. + * + * @return whether the submit button is enabled automatically when necessary + */ + public boolean isSubmitButtonEnabled() { + return mSubmitButtonEnabled; + } + + /** + * Specifies if a query refinement button should be displayed alongside each suggestion + * or if it should depend on the flags set in the individual items retrieved from the + * suggestions provider. Clicking on the query refinement button will replace the text + * in the query text field with the text from the suggestion. This flag only takes effect + * if a SearchableInfo has been specified with {@link #setSearchableInfo(SearchableInfo)} + * and not when using a custom adapter. + * + * @param enable true if all items should have a query refinement button, false if only + * those items that have a query refinement flag set should have the button. + * + * @see SearchManager#SUGGEST_COLUMN_FLAGS + * @see SearchManager#FLAG_QUERY_REFINEMENT + */ + public void setQueryRefinementEnabled(boolean enable) { + mQueryRefinement = enable; + if (mSuggestionsAdapter instanceof SuggestionsAdapter) { + ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement( + enable ? SuggestionsAdapter.REFINE_ALL : SuggestionsAdapter.REFINE_BY_ENTRY); + } + } + + /** + * Returns whether query refinement is enabled for all items or only specific ones. + * @return true if enabled for all items, false otherwise. + */ + public boolean isQueryRefinementEnabled() { + return mQueryRefinement; + } + + /** + * You can set a custom adapter if you wish. Otherwise the default adapter is used to + * display the suggestions from the suggestions provider associated with the SearchableInfo. + * + * @see #setSearchableInfo(SearchableInfo) + */ + public void setSuggestionsAdapter(CursorAdapter adapter) { + mSuggestionsAdapter = adapter; + + mSearchSrcTextView.setAdapter(mSuggestionsAdapter); + } + + /** + * Returns the adapter used for suggestions, if any. + * @return the suggestions adapter + */ + public CursorAdapter getSuggestionsAdapter() { + return mSuggestionsAdapter; + } + + /** + * Makes the view at most this many pixels wide + */ + public void setMaxWidth(int maxpixels) { + mMaxWidth = maxpixels; + + requestLayout(); + } + + /** + * Gets the specified maximum width in pixels, if set. Returns zero if + * no maximum width was specified. + * @return the maximum width of the view + */ + public int getMaxWidth() { + return mMaxWidth; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Let the standard measurements take effect in iconified state. + if (isIconified()) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + + switch (widthMode) { + case MeasureSpec.AT_MOST: + // If there is an upper limit, don't exceed maximum width (explicit or implicit) + if (mMaxWidth > 0) { + width = Math.min(mMaxWidth, width); + } else { + width = Math.min(getPreferredWidth(), width); + } + break; + case MeasureSpec.EXACTLY: + // If an exact width is specified, still don't exceed any specified maximum width + if (mMaxWidth > 0) { + width = Math.min(mMaxWidth, width); + } + break; + case MeasureSpec.UNSPECIFIED: + // Use maximum width, if specified, else preferred width + width = mMaxWidth > 0 ? mMaxWidth : getPreferredWidth(); + break; + } + widthMode = MeasureSpec.EXACTLY; + super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), heightMeasureSpec); + } + + private int getPreferredWidth() { + return getContext().getResources() + .getDimensionPixelSize(R.dimen.abc_search_view_preferred_width); + } + + private void updateViewsVisibility(final boolean collapsed) { + mIconified = collapsed; + // Visibility of views that are visible when collapsed + final int visCollapsed = collapsed ? VISIBLE : GONE; + // Is there text in the query + final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText()); + + mSearchButton.setVisibility(visCollapsed); + updateSubmitButton(hasText); + mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE); + mCollapsedIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE); + updateCloseButton(); + updateVoiceButton(!hasText); + updateSubmitArea(); + } + + @TargetApi(Build.VERSION_CODES.FROYO) + private boolean hasVoiceSearch() { + if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) { + Intent testIntent = null; + if (mSearchable.getVoiceSearchLaunchWebSearch()) { + testIntent = mVoiceWebSearchIntent; + } else if (mSearchable.getVoiceSearchLaunchRecognizer()) { + testIntent = mVoiceAppSearchIntent; + } + if (testIntent != null) { + ResolveInfo ri = getContext().getPackageManager().resolveActivity(testIntent, + PackageManager.MATCH_DEFAULT_ONLY); + return ri != null; + } + } + return false; + } + + private boolean isSubmitAreaEnabled() { + return (mSubmitButtonEnabled || mVoiceButtonEnabled) && !isIconified(); + } + + private void updateSubmitButton(boolean hasText) { + int visibility = GONE; + if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus() + && (hasText || !mVoiceButtonEnabled)) { + visibility = VISIBLE; + } + mGoButton.setVisibility(visibility); + } + + private void updateSubmitArea() { + int visibility = GONE; + if (isSubmitAreaEnabled() + && (mGoButton.getVisibility() == VISIBLE + || mVoiceButton.getVisibility() == VISIBLE)) { + visibility = VISIBLE; + } + mSubmitArea.setVisibility(visibility); + } + + private void updateCloseButton() { + final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText()); + // Should we show the close button? It is not shown if there's no focus, + // field is not iconified by default and there is no text in it. + final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView); + mCloseButton.setVisibility(showClose ? VISIBLE : GONE); + final Drawable closeButtonImg = mCloseButton.getDrawable(); + if (closeButtonImg != null){ + closeButtonImg.setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET); + } + } + + private void postUpdateFocusedState() { + post(mUpdateDrawableStateRunnable); + } + + private void updateFocusedState() { + final boolean focused = mSearchSrcTextView.hasFocus(); + final int[] stateSet = focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET; + final Drawable searchPlateBg = mSearchPlate.getBackground(); + if (searchPlateBg != null) { + searchPlateBg.setState(stateSet); + } + final Drawable submitAreaBg = mSubmitArea.getBackground(); + if (submitAreaBg != null) { + submitAreaBg.setState(stateSet); + } + invalidate(); + } + + @Override + protected void onDetachedFromWindow() { + removeCallbacks(mUpdateDrawableStateRunnable); + post(mReleaseCursorRunnable); + super.onDetachedFromWindow(); + } + + private void setImeVisibility(final boolean visible) { + if (visible) { + post(mShowImeRunnable); + } else { + removeCallbacks(mShowImeRunnable); + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + if (imm != null) { + imm.hideSoftInputFromWindow(getWindowToken(), 0); + } + } + } + + /** + * Called by the SuggestionsAdapter + * @hide + */ + /* package */void onQueryRefine(CharSequence queryText) { + setQuery(queryText); + } + + private final OnClickListener mOnClickListener = new OnClickListener() { + + public void onClick(View v) { + if (v == mSearchButton) { + onSearchClicked(); + } else if (v == mCloseButton) { + onCloseClicked(); + } else if (v == mGoButton) { + onSubmitQuery(); + } else if (v == mVoiceButton) { + onVoiceClicked(); + } else if (v == mSearchSrcTextView) { + forceSuggestionQuery(); + } + } + }; + + /** + * React to the user typing "enter" or other hardwired keys while typing in + * the search box. This handles these special keys while the edit box has + * focus. + */ + View.OnKeyListener mTextKeyListener = new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + // guard against possible race conditions + if (mSearchable == null) { + return false; + } + + if (DBG) { + Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: " + + mSearchSrcTextView.getListSelection()); + } + + // If a suggestion is selected, handle enter, search key, and action keys + // as presses on the selected suggestion + if (mSearchSrcTextView.isPopupShowing() + && mSearchSrcTextView.getListSelection() != ListView.INVALID_POSITION) { + return onSuggestionsKey(v, keyCode, event); + } + + // If there is text in the query box, handle enter, and action keys + // The search key is handled by the dialog's onKeyDown(). + if (!mSearchSrcTextView.isEmpty() && KeyEventCompat.hasNoModifiers(event)) { + if (event.getAction() == KeyEvent.ACTION_UP) { + if (keyCode == KeyEvent.KEYCODE_ENTER) { + v.cancelLongPress(); + + // Launch as a regular search. + launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mSearchSrcTextView.getText() + .toString()); + return true; + } + } + } + return false; + } + }; + + /** + * React to the user typing while in the suggestions list. First, check for + * action keys. If not handled, try refocusing regular characters into the + * EditText. + */ + private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) { + // guard against possible race conditions (late arrival after dismiss) + if (mSearchable == null) { + return false; + } + if (mSuggestionsAdapter == null) { + return false; + } + if (event.getAction() == KeyEvent.ACTION_DOWN && KeyEventCompat.hasNoModifiers(event)) { + // First, check for enter or search (both of which we'll treat as a + // "click") + if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH + || keyCode == KeyEvent.KEYCODE_TAB) { + int position = mSearchSrcTextView.getListSelection(); + return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null); + } + + // Next, check for left/right moves, which we use to "return" the + // user to the edit view + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + // give "focus" to text editor, with cursor at the beginning if + // left key, at end if right key + // TODO: Reverse left/right for right-to-left languages, e.g. + // Arabic + int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mSearchSrcTextView + .length(); + mSearchSrcTextView.setSelection(selPoint); + mSearchSrcTextView.setListSelection(0); + mSearchSrcTextView.clearListSelection(); + HIDDEN_METHOD_INVOKER.ensureImeVisible(mSearchSrcTextView, true); + + return true; + } + + // Next, check for an "up and out" move + if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchSrcTextView.getListSelection()) { + // TODO: restoreUserQuery(); + // let ACTV complete the move + return false; + } + } + return false; + } + + private CharSequence getDecoratedHint(CharSequence hintText) { + // If the field is always expanded or we don't have a search hint icon, + // then don't add the search icon to the hint. + if (!mIconifiedByDefault || mSearchHintIcon == null) { + return hintText; + } + + final int textSize = (int) (mSearchSrcTextView.getTextSize() * 1.25); + mSearchHintIcon.setBounds(0, 0, textSize, textSize); + + final SpannableStringBuilder ssb = new SpannableStringBuilder(" "); + ssb.setSpan(new ImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(hintText); + return ssb; + } + + private void updateQueryHint() { + if (mQueryHint != null) { + mSearchSrcTextView.setHint(getDecoratedHint(mQueryHint)); + } else if (IS_AT_LEAST_FROYO && mSearchable != null) { + CharSequence hint = null; + int hintId = mSearchable.getHintId(); + if (hintId != 0) { + hint = getContext().getString(hintId); + } + if (hint != null) { + mSearchSrcTextView.setHint(getDecoratedHint(hint)); + } + } else { + mSearchSrcTextView.setHint(getDecoratedHint("")); + } + } + + /** + * Updates the auto-complete text view. + */ + @TargetApi(Build.VERSION_CODES.FROYO) + private void updateSearchAutoComplete() { + mSearchSrcTextView.setThreshold(mSearchable.getSuggestThreshold()); + mSearchSrcTextView.setImeOptions(mSearchable.getImeOptions()); + int inputType = mSearchable.getInputType(); + // We only touch this if the input type is set up for text (which it almost certainly + // should be, in the case of search!) + if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { + // The existence of a suggestions authority is the proxy for "suggestions + // are available here" + inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + if (mSearchable.getSuggestAuthority() != null) { + inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + // TYPE_TEXT_FLAG_AUTO_COMPLETE means that the text editor is performing + // auto-completion based on its own semantics, which it will present to the user + // as they type. This generally means that the input method should not show its + // own candidates, and the spell checker should not be in action. The text editor + // supplies its candidates by calling InputMethodManager.displayCompletions(), + // which in turn will call InputMethodSession.displayCompletions(). + inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; + } + } + mSearchSrcTextView.setInputType(inputType); + if (mSuggestionsAdapter != null) { + mSuggestionsAdapter.changeCursor(null); + } + // attach the suggestions adapter, if suggestions are available + // The existence of a suggestions authority is the proxy for "suggestions available here" + if (mSearchable.getSuggestAuthority() != null) { + mSuggestionsAdapter = new SuggestionsAdapter(getContext(), + this, mSearchable, mOutsideDrawablesCache); + mSearchSrcTextView.setAdapter(mSuggestionsAdapter); + ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement( + mQueryRefinement ? SuggestionsAdapter.REFINE_ALL + : SuggestionsAdapter.REFINE_BY_ENTRY); + } + } + + /** + * Update the visibility of the voice button. There are actually two voice search modes, + * either of which will activate the button. + * @param empty whether the search query text field is empty. If it is, then the other + * criteria apply to make the voice button visible. + */ + private void updateVoiceButton(boolean empty) { + int visibility = GONE; + if (mVoiceButtonEnabled && !isIconified() && empty) { + visibility = VISIBLE; + mGoButton.setVisibility(GONE); + } + mVoiceButton.setVisibility(visibility); + } + + private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() { + + /** + * Called when the input method default action key is pressed. + */ + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + onSubmitQuery(); + return true; + } + }; + + private void onTextChanged(CharSequence newText) { + CharSequence text = mSearchSrcTextView.getText(); + mUserQuery = text; + boolean hasText = !TextUtils.isEmpty(text); + updateSubmitButton(hasText); + updateVoiceButton(!hasText); + updateCloseButton(); + updateSubmitArea(); + if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) { + mOnQueryChangeListener.onQueryTextChange(newText.toString()); + } + mOldQueryText = newText.toString(); + } + + private void onSubmitQuery() { + CharSequence query = mSearchSrcTextView.getText(); + if (query != null && TextUtils.getTrimmedLength(query) > 0) { + if (mOnQueryChangeListener == null + || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) { + if (mSearchable != null) { + launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString()); + } + setImeVisibility(false); + dismissSuggestions(); + } + } + } + + private void dismissSuggestions() { + mSearchSrcTextView.dismissDropDown(); + } + + private void onCloseClicked() { + CharSequence text = mSearchSrcTextView.getText(); + if (TextUtils.isEmpty(text)) { + if (mIconifiedByDefault) { + // If the app doesn't override the close behavior + if (mOnCloseListener == null || !mOnCloseListener.onClose()) { + // hide the keyboard and remove focus + clearFocus(); + // collapse the search field + updateViewsVisibility(true); + } + } + } else { + mSearchSrcTextView.setText(""); + mSearchSrcTextView.requestFocus(); + setImeVisibility(true); + } + + } + + private void onSearchClicked() { + updateViewsVisibility(false); + mSearchSrcTextView.requestFocus(); + setImeVisibility(true); + if (mOnSearchClickListener != null) { + mOnSearchClickListener.onClick(this); + } + } + + @TargetApi(Build.VERSION_CODES.FROYO) + private void onVoiceClicked() { + // guard against possible race conditions + if (mSearchable == null) { + return; + } + SearchableInfo searchable = mSearchable; + try { + if (searchable.getVoiceSearchLaunchWebSearch()) { + Intent webSearchIntent = createVoiceWebSearchIntent(mVoiceWebSearchIntent, + searchable); + getContext().startActivity(webSearchIntent); + } else if (searchable.getVoiceSearchLaunchRecognizer()) { + Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent, + searchable); + getContext().startActivity(appSearchIntent); + } + } catch (ActivityNotFoundException e) { + // Should not happen, since we check the availability of + // voice search before showing the button. But just in case... + Log.w(LOG_TAG, "Could not find voice search activity"); + } + } + + void onTextFocusChanged() { + updateViewsVisibility(isIconified()); + // Delayed update to make sure that the focus has settled down and window focus changes + // don't affect it. A synchronous update was not working. + postUpdateFocusedState(); + if (mSearchSrcTextView.hasFocus()) { + forceSuggestionQuery(); + } + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + + postUpdateFocusedState(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onActionViewCollapsed() { + setQuery("", false); + clearFocus(); + updateViewsVisibility(true); + mSearchSrcTextView.setImeOptions(mCollapsedImeOptions); + mExpandedInActionView = false; + } + + /** + * {@inheritDoc} + */ + @Override + public void onActionViewExpanded() { + if (mExpandedInActionView) return; + + mExpandedInActionView = true; + mCollapsedImeOptions = mSearchSrcTextView.getImeOptions(); + mSearchSrcTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN); + mSearchSrcTextView.setText(""); + setIconified(false); + } + + + private void adjustDropDownSizeAndPosition() { + if (mDropDownAnchor.getWidth() > 1) { + Resources res = getContext().getResources(); + int anchorPadding = mSearchPlate.getPaddingLeft(); + Rect dropDownPadding = new Rect(); + final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this); + int iconOffset = mIconifiedByDefault + ? res.getDimensionPixelSize(R.dimen.abc_dropdownitem_icon_width) + + res.getDimensionPixelSize(R.dimen.abc_dropdownitem_text_padding_left) + : 0; + mSearchSrcTextView.getDropDownBackground().getPadding(dropDownPadding); + int offset; + if (isLayoutRtl) { + offset = - dropDownPadding.left; + } else { + offset = anchorPadding - (dropDownPadding.left + iconOffset); + } + mSearchSrcTextView.setDropDownHorizontalOffset(offset); + final int width = mDropDownAnchor.getWidth() + dropDownPadding.left + + dropDownPadding.right + iconOffset - anchorPadding; + mSearchSrcTextView.setDropDownWidth(width); + } + } + + private boolean onItemClicked(int position, int actionKey, String actionMsg) { + if (mOnSuggestionListener == null + || !mOnSuggestionListener.onSuggestionClick(position)) { + launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null); + setImeVisibility(false); + dismissSuggestions(); + return true; + } + return false; + } + + private boolean onItemSelected(int position) { + if (mOnSuggestionListener == null + || !mOnSuggestionListener.onSuggestionSelect(position)) { + rewriteQueryFromSuggestion(position); + return true; + } + return false; + } + + private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() { + + /** + * Implements OnItemClickListener + */ + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position); + onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null); + } + }; + + private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() { + + /** + * Implements OnItemSelectedListener + */ + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position); + SearchView.this.onItemSelected(position); + } + + /** + * Implements OnItemSelectedListener + */ + public void onNothingSelected(AdapterView parent) { + if (DBG) + Log.d(LOG_TAG, "onNothingSelected()"); + } + }; + + /** + * Query rewriting. + */ + private void rewriteQueryFromSuggestion(int position) { + CharSequence oldQuery = mSearchSrcTextView.getText(); + Cursor c = mSuggestionsAdapter.getCursor(); + if (c == null) { + return; + } + if (c.moveToPosition(position)) { + // Get the new query from the suggestion. + CharSequence newQuery = mSuggestionsAdapter.convertToString(c); + if (newQuery != null) { + // The suggestion rewrites the query. + // Update the text field, without getting new suggestions. + setQuery(newQuery); + } else { + // The suggestion does not rewrite the query, restore the user's query. + setQuery(oldQuery); + } + } else { + // We got a bad position, restore the user's query. + setQuery(oldQuery); + } + } + + /** + * Launches an intent based on a suggestion. + * + * @param position The index of the suggestion to create the intent from. + * @param actionKey The key code of the action key that was pressed, + * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. + * @param actionMsg The message for the action key that was pressed, + * or null if none. + * @return true if a successful launch, false if could not (e.g. bad position). + */ + private boolean launchSuggestion(int position, int actionKey, String actionMsg) { + Cursor c = mSuggestionsAdapter.getCursor(); + if ((c != null) && c.moveToPosition(position)) { + + Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg); + + // launch the intent + launchIntent(intent); + + return true; + } + return false; + } + + /** + * Launches an intent, including any special intent handling. + */ + private void launchIntent(Intent intent) { + if (intent == null) { + return; + } + try { + // If the intent was created from a suggestion, it will always have an explicit + // component here. + getContext().startActivity(intent); + } catch (RuntimeException ex) { + Log.e(LOG_TAG, "Failed launch activity: " + intent, ex); + } + } + + /** + * Sets the text in the query box, without updating the suggestions. + */ + private void setQuery(CharSequence query) { + mSearchSrcTextView.setText(query); + // Move the cursor to the end + mSearchSrcTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length()); + } + + private void launchQuerySearch(int actionKey, String actionMsg, String query) { + String action = Intent.ACTION_SEARCH; + Intent intent = createIntent(action, null, null, query, actionKey, actionMsg); + getContext().startActivity(intent); + } + + /** + * Constructs an intent from the given information and the search dialog state. + * + * @param action Intent action. + * @param data Intent data, or null. + * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or null. + * @param query Intent query, or null. + * @param actionKey The key code of the action key that was pressed, + * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. + * @param actionMsg The message for the action key that was pressed, + * or null if none. + * @return The intent. + */ + private Intent createIntent(String action, Uri data, String extraData, String query, + int actionKey, String actionMsg) { + // Now build the Intent + Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // We need CLEAR_TOP to avoid reusing an old task that has other activities + // on top of the one we want. We don't want to do this in in-app search though, + // as it can be destructive to the activity stack. + if (data != null) { + intent.setData(data); + } + intent.putExtra(SearchManager.USER_QUERY, mUserQuery); + if (query != null) { + intent.putExtra(SearchManager.QUERY, query); + } + if (extraData != null) { + intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData); + } + if (mAppSearchData != null) { + intent.putExtra(SearchManager.APP_DATA, mAppSearchData); + } + if (actionKey != KeyEvent.KEYCODE_UNKNOWN) { + intent.putExtra(SearchManager.ACTION_KEY, actionKey); + intent.putExtra(SearchManager.ACTION_MSG, actionMsg); + } + if (IS_AT_LEAST_FROYO) { + intent.setComponent(mSearchable.getSearchActivity()); + } + return intent; + } + + /** + * Create and return an Intent that can launch the voice search activity for web search. + */ + @TargetApi(Build.VERSION_CODES.FROYO) + private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) { + Intent voiceIntent = new Intent(baseIntent); + ComponentName searchActivity = searchable.getSearchActivity(); + voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null + : searchActivity.flattenToShortString()); + return voiceIntent; + } + + /** + * Create and return an Intent that can launch the voice search activity, perform a specific + * voice transcription, and forward the results to the searchable activity. + * + * @param baseIntent The voice app search intent to start from + * @return A completely-configured intent ready to send to the voice search activity + */ + @TargetApi(Build.VERSION_CODES.FROYO) + private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) { + ComponentName searchActivity = searchable.getSearchActivity(); + + // create the necessary intent to set up a search-and-forward operation + // in the voice search system. We have to keep the bundle separate, + // because it becomes immutable once it enters the PendingIntent + Intent queryIntent = new Intent(Intent.ACTION_SEARCH); + queryIntent.setComponent(searchActivity); + PendingIntent pending = PendingIntent.getActivity(getContext(), 0, queryIntent, + PendingIntent.FLAG_ONE_SHOT); + + // Now set up the bundle that will be inserted into the pending intent + // when it's time to do the search. We always build it here (even if empty) + // because the voice search activity will always need to insert "QUERY" into + // it anyway. + Bundle queryExtras = new Bundle(); + if (mAppSearchData != null) { + queryExtras.putParcelable(SearchManager.APP_DATA, mAppSearchData); + } + + // Now build the intent to launch the voice search. Add all necessary + // extras to launch the voice recognizer, and then all the necessary extras + // to forward the results to the searchable activity + Intent voiceIntent = new Intent(baseIntent); + + // Add all of the configuration options supplied by the searchable's metadata + String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM; + String prompt = null; + String language = null; + int maxResults = 1; + + if (Build.VERSION.SDK_INT >= 8) { + Resources resources = getResources(); + if (searchable.getVoiceLanguageModeId() != 0) { + languageModel = resources.getString(searchable.getVoiceLanguageModeId()); + } + if (searchable.getVoicePromptTextId() != 0) { + prompt = resources.getString(searchable.getVoicePromptTextId()); + } + if (searchable.getVoiceLanguageId() != 0) { + language = resources.getString(searchable.getVoiceLanguageId()); + } + if (searchable.getVoiceMaxResults() != 0) { + maxResults = searchable.getVoiceMaxResults(); + } + } + voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel); + voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt); + voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language); + voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults); + voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null + : searchActivity.flattenToShortString()); + + // Add the values that configure forwarding the results + voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending); + voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras); + + return voiceIntent; + } + + /** + * When a particular suggestion has been selected, perform the various lookups required + * to use the suggestion. This includes checking the cursor for suggestion-specific data, + * and/or falling back to the XML for defaults; It also creates REST style Uri data when + * the suggestion includes a data id. + * + * @param c The suggestions cursor, moved to the row of the user's selection + * @param actionKey The key code of the action key that was pressed, + * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. + * @param actionMsg The message for the action key that was pressed, + * or null if none. + * @return An intent for the suggestion at the cursor's position. + */ + private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) { + try { + // use specific action if supplied, or default action if supplied, or fixed default + String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION); + + if (action == null && Build.VERSION.SDK_INT >= 8) { + action = mSearchable.getSuggestIntentAction(); + } + if (action == null) { + action = Intent.ACTION_SEARCH; + } + + // use specific data if supplied, or default data if supplied + String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA); + if (IS_AT_LEAST_FROYO && data == null) { + data = mSearchable.getSuggestIntentData(); + } + // then, if an ID was provided, append it. + if (data != null) { + String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); + if (id != null) { + data = data + "/" + Uri.encode(id); + } + } + Uri dataUri = (data == null) ? null : Uri.parse(data); + + String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY); + String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); + + return createIntent(action, dataUri, extraData, query, actionKey, actionMsg); + } catch (RuntimeException e ) { + int rowNum; + try { // be really paranoid now + rowNum = c.getPosition(); + } catch (RuntimeException e2 ) { + rowNum = -1; + } + Log.w(LOG_TAG, "Search suggestions cursor at row " + rowNum + + " returned exception.", e); + return null; + } + } + + private void forceSuggestionQuery() { + HIDDEN_METHOD_INVOKER.doBeforeTextChanged(mSearchSrcTextView); + HIDDEN_METHOD_INVOKER.doAfterTextChanged(mSearchSrcTextView); + } + + static boolean isLandscapeMode(Context context) { + return context.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + } + + /** + * Callback to watch the text field for empty/non-empty + */ + private TextWatcher mTextWatcher = new TextWatcher() { + + public void beforeTextChanged(CharSequence s, int start, int before, int after) { } + + public void onTextChanged(CharSequence s, int start, + int before, int after) { + SearchView.this.onTextChanged(s); + } + + public void afterTextChanged(Editable s) { + } + }; + + /** + * Local subclass for AutoCompleteTextView. + * @hide + */ + public static class SearchAutoComplete extends TintAutoCompleteTextView { + + private int mThreshold; + private SearchView mSearchView; + + public SearchAutoComplete(Context context) { + this(context, null); + } + + public SearchAutoComplete(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.autoCompleteTextViewStyle); + } + + public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mThreshold = getThreshold(); + } + + void setSearchView(SearchView searchView) { + mSearchView = searchView; + } + + @Override + public void setThreshold(int threshold) { + super.setThreshold(threshold); + mThreshold = threshold; + } + + /** + * Returns true if the text field is empty, or contains only whitespace. + */ + private boolean isEmpty() { + return TextUtils.getTrimmedLength(getText()) == 0; + } + + /** + * We override this method to avoid replacing the query box text when a + * suggestion is clicked. + */ + @Override + protected void replaceText(CharSequence text) { + } + + /** + * We override this method to avoid an extra onItemClick being called on + * the drop-down's OnItemClickListener by + * {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)} when an item is + * clicked with the trackball. + */ + @Override + public void performCompletion() { + } + + /** + * We override this method to be sure and show the soft keyboard if + * appropriate when the TextView has focus. + */ + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + + if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) { + InputMethodManager inputManager = (InputMethodManager) getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.showSoftInput(this, 0); + // If in landscape mode, then make sure that + // the ime is in front of the dropdown. + if (isLandscapeMode(getContext())) { + HIDDEN_METHOD_INVOKER.ensureImeVisible(this, true); + } + } + } + + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + super.onFocusChanged(focused, direction, previouslyFocusedRect); + mSearchView.onTextFocusChanged(); + } + + /** + * We override this method so that we can allow a threshold of zero, + * which ACTV does not. + */ + @Override + public boolean enoughToFilter() { + return mThreshold <= 0 || super.enoughToFilter(); + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + // special case for the back key, we do not even try to send it + // to the drop down list but instead, consume it immediately + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + state.startTracking(event, this); + } + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + state.handleUpEvent(event); + } + if (event.isTracking() && !event.isCanceled()) { + mSearchView.clearFocus(); + mSearchView.setImeVisibility(false); + return true; + } + } + } + return super.onKeyPreIme(keyCode, event); + } + } + + private static class AutoCompleteTextViewReflector { + private Method doBeforeTextChanged, doAfterTextChanged; + private Method ensureImeVisible; + private Method showSoftInputUnchecked; + + AutoCompleteTextViewReflector() { + try { + doBeforeTextChanged = AutoCompleteTextView.class + .getDeclaredMethod("doBeforeTextChanged"); + doBeforeTextChanged.setAccessible(true); + } catch (NoSuchMethodException e) { + // Ah well. + } + try { + doAfterTextChanged = AutoCompleteTextView.class + .getDeclaredMethod("doAfterTextChanged"); + doAfterTextChanged.setAccessible(true); + } catch (NoSuchMethodException e) { + // Ah well. + } + try { + ensureImeVisible = AutoCompleteTextView.class + .getMethod("ensureImeVisible", boolean.class); + ensureImeVisible.setAccessible(true); + } catch (NoSuchMethodException e) { + // Ah well. + } + try { + showSoftInputUnchecked = InputMethodManager.class.getMethod( + "showSoftInputUnchecked", int.class, ResultReceiver.class); + showSoftInputUnchecked.setAccessible(true); + } catch (NoSuchMethodException e) { + // Ah well. + } + } + + void doBeforeTextChanged(AutoCompleteTextView view) { + if (doBeforeTextChanged != null) { + try { + doBeforeTextChanged.invoke(view); + } catch (Exception e) { + } + } + } + + void doAfterTextChanged(AutoCompleteTextView view) { + if (doAfterTextChanged != null) { + try { + doAfterTextChanged.invoke(view); + } catch (Exception e) { + } + } + } + + void ensureImeVisible(AutoCompleteTextView view, boolean visible) { + if (ensureImeVisible != null) { + try { + ensureImeVisible.invoke(view, visible); + } catch (Exception e) { + } + } + } + + void showSoftInputUnchecked(InputMethodManager imm, View view, int flags) { + if (showSoftInputUnchecked != null) { + try { + showSoftInputUnchecked.invoke(imm, flags, null); + return; + } catch (Exception e) { + } + } + + // Hidden method failed, call public version instead + imm.showSoftInput(view, flags); + } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/ShareActionProvider.java b/eclipse-compile/appcompat/src/android/support/v7/widget/ShareActionProvider.java new file mode 100644 index 0000000000..173e5fc6b5 --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/ShareActionProvider.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2013 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 android.support.v7.widget; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.view.ActionProvider; +import android.support.v7.appcompat.R; +import android.support.v7.internal.widget.ActivityChooserModel; +import android.support.v7.internal.widget.ActivityChooserView; +import android.support.v7.internal.widget.TintManager; +import android.util.TypedValue; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.SubMenu; +import android.view.View; +import android.support.v7.internal.widget.ActivityChooserModel.OnChooseActivityListener; + +/** + * This is a provider for a share action. It is responsible for creating views + * that enable data sharing and also to show a sub menu with sharing activities + * if the hosting item is placed on the overflow menu. + * + *

Note: This class is included in the support library for compatibility + * with API level 7 and higher. If you're developing your app for API level 14 and higher + * only, you should instead use the framework {@link android.widget.ShareActionProvider} + * class.

+ * + *

+ * Here is how to use the action provider with custom backing file in a {@link MenuItem}: + *

+ *

+ *  // In {@link android.app.Activity#onCreateOptionsMenu Activity.onCreateOptionsMenu()}
+ *  public boolean onCreateOptionsMenu(Menu menu) {
+ *      // Get the menu item.
+ *      MenuItem menuItem = menu.findItem(R.id.my_menu_item);
+ *      // Get the provider and hold onto it to set/change the share intent.
+ *      mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem);
+ *      // Set history different from the default before getting the action
+ *      // view since a call to {@link android.support.v4.view.MenuItemCompat#getActionView(android.view.MenuItem) MenuItemCompat.getActionView()} calls
+ *      // {@link ActionProvider#onCreateActionView()} which uses the backing file name. Omit this
+ *      // line if using the default share history file is desired.
+ *      mShareActionProvider.setShareHistoryFileName("custom_share_history.xml");
+ *      . . .
+ *  }
+ *
+ *  // Somewhere in the application.
+ *  public void doShare(Intent shareIntent) {
+ *      // When you want to share set the share intent.
+ *      mShareActionProvider.setShareIntent(shareIntent);
+ *  }
+ * 
+ *

+ * Note: While the sample snippet demonstrates how to use this provider + * in the context of a menu item, the use of the provider is not limited to menu items. + *

+ * + *
+ *

Developer Guides

+ * + *

For information about how to use {@link ShareActionProvider}, see the + * Action Bar API guide.

+ *
+ * + * @see ActionProvider + */ +public class ShareActionProvider extends ActionProvider { + + /** + * Listener for the event of selecting a share target. + */ + public interface OnShareTargetSelectedListener { + + /** + * Called when a share target has been selected. The client can + * decide whether to perform some action before the sharing is + * actually performed. + *

+ * Note: Modifying the intent is not permitted and + * any changes to the latter will be ignored. + *

+ *

+ * Note: You should not handle the + * intent here. This callback aims to notify the client that a + * sharing is being performed, so the client can update the UI + * if necessary. + *

+ * + * @param source The source of the notification. + * @param intent The intent for launching the chosen share target. + * @return The return result is ignored. Always return false for consistency. + */ + public boolean onShareTargetSelected(ShareActionProvider source, Intent intent); + } + + /** + * The default for the maximal number of activities shown in the sub-menu. + */ + private static final int DEFAULT_INITIAL_ACTIVITY_COUNT = 4; + + /** + * The the maximum number activities shown in the sub-menu. + */ + private int mMaxShownActivityCount = DEFAULT_INITIAL_ACTIVITY_COUNT; + + /** + * Listener for handling menu item clicks. + */ + private final ShareMenuItemOnMenuItemClickListener mOnMenuItemClickListener = + new ShareMenuItemOnMenuItemClickListener(); + + /** + * The default name for storing share history. + */ + public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml"; + + /** + * Context for accessing resources. + */ + private final Context mContext; + + /** + * The name of the file with share history data. + */ + private String mShareHistoryFileName = DEFAULT_SHARE_HISTORY_FILE_NAME; + + private OnShareTargetSelectedListener mOnShareTargetSelectedListener; + + private OnChooseActivityListener mOnChooseActivityListener; + + /** + * Creates a new instance. + * + * @param context Context for accessing resources. + */ + public ShareActionProvider(Context context) { + super(context); + mContext = context; + } + + /** + * Sets a listener to be notified when a share target has been selected. + * The listener can optionally decide to handle the selection and + * not rely on the default behavior which is to launch the activity. + *

+ * Note: If you choose the backing share history file + * you will still be notified in this callback. + *

+ * @param listener The listener. + */ + public void setOnShareTargetSelectedListener(OnShareTargetSelectedListener listener) { + mOnShareTargetSelectedListener = listener; + setActivityChooserPolicyIfNeeded(); + } + + /** + * {@inheritDoc} + */ + @Override + public View onCreateActionView() { + // Create the view and set its data model. + ActivityChooserView activityChooserView = new ActivityChooserView(mContext); + if (!activityChooserView.isInEditMode()) { + ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName); + activityChooserView.setActivityChooserModel(dataModel); + } + + // Lookup and set the expand action icon. + TypedValue outTypedValue = new TypedValue(); + mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true); + Drawable drawable = TintManager.getDrawable(mContext, outTypedValue.resourceId); + activityChooserView.setExpandActivityOverflowButtonDrawable(drawable); + activityChooserView.setProvider(this); + + // Set content description. + activityChooserView.setDefaultActionButtonContentDescription( + R.string.abc_shareactionprovider_share_with_application); + activityChooserView.setExpandActivityOverflowButtonContentDescription( + R.string.abc_shareactionprovider_share_with); + + return activityChooserView; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasSubMenu() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void onPrepareSubMenu(SubMenu subMenu) { + // Clear since the order of items may change. + subMenu.clear(); + + ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName); + PackageManager packageManager = mContext.getPackageManager(); + + final int expandedActivityCount = dataModel.getActivityCount(); + final int collapsedActivityCount = Math.min(expandedActivityCount, mMaxShownActivityCount); + + // Populate the sub-menu with a sub set of the activities. + for (int i = 0; i < collapsedActivityCount; i++) { + ResolveInfo activity = dataModel.getActivity(i); + subMenu.add(0, i, i, activity.loadLabel(packageManager)) + .setIcon(activity.loadIcon(packageManager)) + .setOnMenuItemClickListener(mOnMenuItemClickListener); + } + + if (collapsedActivityCount < expandedActivityCount) { + // Add a sub-menu for showing all activities as a list item. + SubMenu expandedSubMenu = subMenu.addSubMenu(Menu.NONE, collapsedActivityCount, + collapsedActivityCount, + mContext.getString(R.string.abc_activity_chooser_view_see_all)); + for (int i = 0; i < expandedActivityCount; i++) { + ResolveInfo activity = dataModel.getActivity(i); + expandedSubMenu.add(0, i, i, activity.loadLabel(packageManager)) + .setIcon(activity.loadIcon(packageManager)) + .setOnMenuItemClickListener(mOnMenuItemClickListener); + } + } + } + + /** + * Sets the file name of a file for persisting the share history which + * history will be used for ordering share targets. This file will be used + * for all view created by {@link #onCreateActionView()}. Defaults to + * {@link #DEFAULT_SHARE_HISTORY_FILE_NAME}. Set to null + * if share history should not be persisted between sessions. + *

+ * Note: The history file name can be set any time, however + * only the action views created by {@link #onCreateActionView()} after setting + * the file name will be backed by the provided file. Therefore, if you want to + * use different history files for sharing specific types of content, every time + * you change the history file {@link #setShareHistoryFileName(String)} you must + * call {@link android.app.Activity#invalidateOptionsMenu()} to recreate the + * action view. You should not call + * {@link android.app.Activity#invalidateOptionsMenu()} from + * {@link android.app.Activity#onCreateOptionsMenu(Menu)}." + *

+ * + * private void doShare(Intent intent) { + * if (IMAGE.equals(intent.getMimeType())) { + * mShareActionProvider.setHistoryFileName(SHARE_IMAGE_HISTORY_FILE_NAME); + * } else if (TEXT.equals(intent.getMimeType())) { + * mShareActionProvider.setHistoryFileName(SHARE_TEXT_HISTORY_FILE_NAME); + * } + * mShareActionProvider.setIntent(intent); + * invalidateOptionsMenu(); + * } + * + * + * @param shareHistoryFile The share history file name. + */ + public void setShareHistoryFileName(String shareHistoryFile) { + mShareHistoryFileName = shareHistoryFile; + setActivityChooserPolicyIfNeeded(); + } + + /** + * Sets an intent with information about the share action. Here is a + * sample for constructing a share intent: + *

+ *

+     * 
+     *  Intent shareIntent = new Intent(Intent.ACTION_SEND);
+     *  shareIntent.setType("image/*");
+     *  Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
+     *  shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString());
+     * 
+ * + *

+ * + * @param shareIntent The share intent. + * + * @see Intent#ACTION_SEND + * @see Intent#ACTION_SEND_MULTIPLE + */ + public void setShareIntent(Intent shareIntent) { + if (shareIntent != null) { + final String action = shareIntent.getAction(); + if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) { + updateIntent(shareIntent); + } + } + ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, + mShareHistoryFileName); + dataModel.setIntent(shareIntent); + } + + /** + * Reusable listener for handling share item clicks. + */ + private class ShareMenuItemOnMenuItemClickListener implements OnMenuItemClickListener { + @Override + public boolean onMenuItemClick(MenuItem item) { + ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, + mShareHistoryFileName); + final int itemId = item.getItemId(); + Intent launchIntent = dataModel.chooseActivity(itemId); + if (launchIntent != null) { + final String action = launchIntent.getAction(); + if (Intent.ACTION_SEND.equals(action) || + Intent.ACTION_SEND_MULTIPLE.equals(action)) { + updateIntent(launchIntent); + } + mContext.startActivity(launchIntent); + } + return true; + } + } + + /** + * Set the activity chooser policy of the model backed by the current + * share history file if needed which is if there is a registered callback. + */ + private void setActivityChooserPolicyIfNeeded() { + if (mOnShareTargetSelectedListener == null) { + return; + } + if (mOnChooseActivityListener == null) { + mOnChooseActivityListener = new ShareActivityChooserModelPolicy(); + } + ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName); + dataModel.setOnChooseActivityListener(mOnChooseActivityListener); + } + + /** + * Policy that delegates to the {@link OnShareTargetSelectedListener}, if such. + */ + private class ShareActivityChooserModelPolicy implements OnChooseActivityListener { + @Override + public boolean onChooseActivity(ActivityChooserModel host, Intent intent) { + if (mOnShareTargetSelectedListener != null) { + mOnShareTargetSelectedListener.onShareTargetSelected( + ShareActionProvider.this, intent); + } + return false; + } + } + + private void updateIntent(Intent intent) { + if (Build.VERSION.SDK_INT >= 21) { + // If we're on Lollipop, we can open the intent as a document + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | + Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + } else { + // Else, we will use the old CLEAR_WHEN_TASK_RESET flag + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + } + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java b/eclipse-compile/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java new file mode 100644 index 0000000000..6251e0e31a --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java @@ -0,0 +1,756 @@ +package android.support.v7.widget; + +/* + * Copyright (C) 2013 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. + */ + +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.ResourceCursorAdapter; +import android.support.v7.appcompat.R; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.TextAppearanceSpan; +import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.WeakHashMap; + +/** + * Provides the contents for the suggestion drop-down list.in {@link SearchView}. + * @hide + */ +class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListener { + + private static final boolean DBG = false; + private static final String LOG_TAG = "SuggestionsAdapter"; + private static final int QUERY_LIMIT = 50; + + static final int REFINE_NONE = 0; + static final int REFINE_BY_ENTRY = 1; + static final int REFINE_ALL = 2; + + private final SearchManager mSearchManager; + private final SearchView mSearchView; + private final SearchableInfo mSearchable; + private final Context mProviderContext; + private final WeakHashMap mOutsideDrawablesCache; + private final int mCommitIconResId; + private boolean mClosed = false; + private int mQueryRefinement = REFINE_BY_ENTRY; + + // URL color + private ColorStateList mUrlColor; + + static final int INVALID_INDEX = -1; + + // Cached column indexes, updated when the cursor changes. + private int mText1Col = INVALID_INDEX; + private int mText2Col = INVALID_INDEX; + private int mText2UrlCol = INVALID_INDEX; + private int mIconName1Col = INVALID_INDEX; + private int mIconName2Col = INVALID_INDEX; + private int mFlagsCol = INVALID_INDEX; + + // private final Runnable mStartSpinnerRunnable; + // private final Runnable mStopSpinnerRunnable; + + public SuggestionsAdapter(Context context, SearchView searchView, SearchableInfo searchable, + WeakHashMap outsideDrawablesCache) { + super(context, searchView.getSuggestionRowLayout(), null /* no initial cursor */, + true /* auto-requery */); + mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + mSearchView = searchView; + mSearchable = searchable; + mCommitIconResId = searchView.getSuggestionCommitIconResId(); + + // set up provider resources (gives us icons, etc.) + mProviderContext = context; + + mOutsideDrawablesCache = outsideDrawablesCache; + } + + /** + * Enables query refinement for all suggestions. This means that an additional icon + * will be shown for each entry. When clicked, the suggested text on that line will be + * copied to the query text field. + *

+ * + * @param refine which queries to refine. Possible values are {@link #REFINE_NONE}, + * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}. + */ + public void setQueryRefinement(int refineWhat) { + mQueryRefinement = refineWhat; + } + + /** + * Returns the current query refinement preference. + * @return value of query refinement preference + */ + public int getQueryRefinement() { + return mQueryRefinement; + } + + /** + * Overridden to always return false, since we cannot be sure that + * suggestion sources return stable IDs. + */ + @Override + public boolean hasStableIds() { + return false; + } + + /** + * Use the search suggestions provider to obtain a live cursor. This will be called + * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions). + * The results will be processed in the UI thread and changeCursor() will be called. + */ + @Override + public Cursor runQueryOnBackgroundThread(CharSequence constraint) { + if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")"); + String query = (constraint == null) ? "" : constraint.toString(); + /** + * for in app search we show the progress spinner until the cursor is returned with + * the results. + */ + Cursor cursor = null; + if (mSearchView.getVisibility() != View.VISIBLE + || mSearchView.getWindowVisibility() != View.VISIBLE) { + return null; + } + try { + cursor = getSearchManagerSuggestions(mSearchable, query, QUERY_LIMIT); + // trigger fill window so the spinner stays up until the results are copied over and + // closer to being ready + if (cursor != null) { + cursor.getCount(); + return cursor; + } + } catch (RuntimeException e) { + Log.w(LOG_TAG, "Search suggestions query threw an exception.", e); + } + // If cursor is null or an exception was thrown, stop the spinner and return null. + // changeCursor doesn't get called if cursor is null + return null; + } + + public void close() { + if (DBG) Log.d(LOG_TAG, "close()"); + changeCursor(null); + mClosed = true; + } + + @Override + public void notifyDataSetChanged() { + if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged"); + super.notifyDataSetChanged(); + + updateSpinnerState(getCursor()); + } + + @Override + public void notifyDataSetInvalidated() { + if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated"); + super.notifyDataSetInvalidated(); + + updateSpinnerState(getCursor()); + } + + private void updateSpinnerState(Cursor cursor) { + Bundle extras = cursor != null ? cursor.getExtras() : null; + if (DBG) { + Log.d(LOG_TAG, "updateSpinnerState - extra = " + + (extras != null + ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS) + : null)); + } + // Check if the Cursor indicates that the query is not complete and show the spinner + if (extras != null + && extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) { + return; + } + // If cursor is null or is done, stop the spinner + } + + /** + * Cache columns. + */ + @Override + public void changeCursor(Cursor c) { + if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")"); + + if (mClosed) { + Log.w(LOG_TAG, "Tried to change cursor after adapter was closed."); + if (c != null) c.close(); + return; + } + + try { + super.changeCursor(c); + + if (c != null) { + mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); + mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); + mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL); + mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); + mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); + mFlagsCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FLAGS); + } + } catch (Exception e) { + Log.e(LOG_TAG, "error changing cursor and caching columns", e); + } + } + + /** + * Tags the view with cached child view look-ups. + */ + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + final View v = super.newView(context, cursor, parent); + v.setTag(new ChildViewCache(v)); + + // Set up icon. + final ImageView iconRefine = (ImageView) v.findViewById(R.id.edit_query); + iconRefine.setImageResource(mCommitIconResId); + return v; + } + + /** + * Cache of the child views of drop-drown list items, to avoid looking up the children + * each time the contents of a list item are changed. + */ + private final static class ChildViewCache { + public final TextView mText1; + public final TextView mText2; + public final ImageView mIcon1; + public final ImageView mIcon2; + public final ImageView mIconRefine; + + public ChildViewCache(View v) { + mText1 = (TextView) v.findViewById(android.R.id.text1); + mText2 = (TextView) v.findViewById(android.R.id.text2); + mIcon1 = (ImageView) v.findViewById(android.R.id.icon1); + mIcon2 = (ImageView) v.findViewById(android.R.id.icon2); + mIconRefine = (ImageView) v.findViewById(R.id.edit_query); + } + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ChildViewCache views = (ChildViewCache) view.getTag(); + + int flags = 0; + if (mFlagsCol != INVALID_INDEX) { + flags = cursor.getInt(mFlagsCol); + } + if (views.mText1 != null) { + String text1 = getStringOrNull(cursor, mText1Col); + setViewText(views.mText1, text1); + } + if (views.mText2 != null) { + // First check TEXT_2_URL + CharSequence text2 = getStringOrNull(cursor, mText2UrlCol); + if (text2 != null) { + text2 = formatUrl(text2); + } else { + text2 = getStringOrNull(cursor, mText2Col); + } + + // If no second line of text is indicated, allow the first line of text + // to be up to two lines if it wants to be. + if (TextUtils.isEmpty(text2)) { + if (views.mText1 != null) { + views.mText1.setSingleLine(false); + views.mText1.setMaxLines(2); + } + } else { + if (views.mText1 != null) { + views.mText1.setSingleLine(true); + views.mText1.setMaxLines(1); + } + } + setViewText(views.mText2, text2); + } + + if (views.mIcon1 != null) { + setViewDrawable(views.mIcon1, getIcon1(cursor), View.INVISIBLE); + } + if (views.mIcon2 != null) { + setViewDrawable(views.mIcon2, getIcon2(cursor), View.GONE); + } + if (mQueryRefinement == REFINE_ALL + || (mQueryRefinement == REFINE_BY_ENTRY + && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) { + views.mIconRefine.setVisibility(View.VISIBLE); + views.mIconRefine.setTag(views.mText1.getText()); + views.mIconRefine.setOnClickListener(this); + } else { + views.mIconRefine.setVisibility(View.GONE); + } + } + + public void onClick(View v) { + Object tag = v.getTag(); + if (tag instanceof CharSequence) { + mSearchView.onQueryRefine((CharSequence) tag); + } + } + + private CharSequence formatUrl(CharSequence url) { + if (mUrlColor == null) { + // Lazily get the URL color from the current theme. + TypedValue colorValue = new TypedValue(); + mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true); + mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId); + } + + SpannableString text = new SpannableString(url); + text.setSpan(new TextAppearanceSpan(null, 0, 0, mUrlColor, null), + 0, url.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return text; + } + + private void setViewText(TextView v, CharSequence text) { + // Set the text even if it's null, since we need to clear any previous text. + v.setText(text); + + if (TextUtils.isEmpty(text)) { + v.setVisibility(View.GONE); + } else { + v.setVisibility(View.VISIBLE); + } + } + + private Drawable getIcon1(Cursor cursor) { + if (mIconName1Col == INVALID_INDEX) { + return null; + } + String value = cursor.getString(mIconName1Col); + Drawable drawable = getDrawableFromResourceValue(value); + if (drawable != null) { + return drawable; + } + return getDefaultIcon1(cursor); + } + + private Drawable getIcon2(Cursor cursor) { + if (mIconName2Col == INVALID_INDEX) { + return null; + } + String value = cursor.getString(mIconName2Col); + return getDrawableFromResourceValue(value); + } + + /** + * Sets the drawable in an image view, makes sure the view is only visible if there + * is a drawable. + */ + private void setViewDrawable(ImageView v, Drawable drawable, int nullVisibility) { + // Set the icon even if the drawable is null, since we need to clear any + // previous icon. + v.setImageDrawable(drawable); + + if (drawable == null) { + v.setVisibility(nullVisibility); + } else { + v.setVisibility(View.VISIBLE); + + // This is a hack to get any animated drawables (like a 'working' spinner) + // to animate. You have to setVisible true on an AnimationDrawable to get + // it to start animating, but it must first have been false or else the + // call to setVisible will be ineffective. We need to clear up the story + // about animated drawables in the future, see http://b/1878430. + drawable.setVisible(false, false); + drawable.setVisible(true, false); + } + } + + /** + * Gets the text to show in the query field when a suggestion is selected. + * + * @param cursor The Cursor to read the suggestion data from. The Cursor should already + * be moved to the suggestion that is to be read from. + * @return The text to show, or null if the query should not be + * changed when selecting this suggestion. + */ + @Override + public CharSequence convertToString(Cursor cursor) { + if (cursor == null) { + return null; + } + + String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY); + if (query != null) { + return query; + } + + if (mSearchable.shouldRewriteQueryFromData()) { + String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA); + if (data != null) { + return data; + } + } + + if (mSearchable.shouldRewriteQueryFromText()) { + String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1); + if (text1 != null) { + return text1; + } + } + + return null; + } + + /** + * This method is overridden purely to provide a bit of protection against + * flaky content providers. + * + * @see android.widget.ListAdapter#getView(int, View, ViewGroup) + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + try { + return super.getView(position, convertView, parent); + } catch (RuntimeException e) { + Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e); + // Put exception string in item title + View v = newView(mContext, mCursor, parent); + if (v != null) { + ChildViewCache views = (ChildViewCache) v.getTag(); + TextView tv = views.mText1; + tv.setText(e.toString()); + } + return v; + } + } + + /** + * Gets a drawable given a value provided by a suggestion provider. + * + * This value could be just the string value of a resource id + * (e.g., "2130837524"), in which case we will try to retrieve a drawable from + * the provider's resources. If the value is not an integer, it is + * treated as a Uri and opened with + * {@link ContentResolver#openOutputStream(android.net.Uri, String)}. + * + * All resources and URIs are read using the suggestion provider's context. + * + * If the string is not formatted as expected, or no drawable can be found for + * the provided value, this method returns null. + * + * @param drawableId a string like "2130837524", + * "android.resource://com.android.alarmclock/2130837524", + * or "content://contacts/photos/253". + * @return a Drawable, or null if none found + */ + private Drawable getDrawableFromResourceValue(String drawableId) { + if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) { + return null; + } + try { + // First, see if it's just an integer + int resourceId = Integer.parseInt(drawableId); + // It's an int, look for it in the cache + String drawableUri = ContentResolver.SCHEME_ANDROID_RESOURCE + + "://" + mProviderContext.getPackageName() + "/" + resourceId; + // Must use URI as cache key, since ints are app-specific + Drawable drawable = checkIconCache(drawableUri); + if (drawable != null) { + return drawable; + } + // Not cached, find it by resource ID + drawable = ContextCompat.getDrawable(mProviderContext, resourceId); + // Stick it in the cache, using the URI as key + storeInIconCache(drawableUri, drawable); + return drawable; + } catch (NumberFormatException nfe) { + // It's not an integer, use it as a URI + Drawable drawable = checkIconCache(drawableId); + if (drawable != null) { + return drawable; + } + Uri uri = Uri.parse(drawableId); + drawable = getDrawable(uri); + storeInIconCache(drawableId, drawable); + return drawable; + } catch (Resources.NotFoundException nfe) { + // It was an integer, but it couldn't be found, bail out + Log.w(LOG_TAG, "Icon resource not found: " + drawableId); + return null; + } + } + + /** + * Gets a drawable by URI, without using the cache. + * + * @return A drawable, or {@code null} if the drawable could not be loaded. + */ + private Drawable getDrawable(Uri uri) { + try { + String scheme = uri.getScheme(); + if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { + // Load drawables through Resources, to get the source density information + try { + return getDrawableFromResourceUri(uri); + } catch (Resources.NotFoundException ex) { + throw new FileNotFoundException("Resource does not exist: " + uri); + } + } else { + // Let the ContentResolver handle content and file URIs. + InputStream stream = mProviderContext.getContentResolver().openInputStream(uri); + if (stream == null) { + throw new FileNotFoundException("Failed to open " + uri); + } + try { + return Drawable.createFromStream(stream, null); + } finally { + try { + stream.close(); + } catch (IOException ex) { + Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex); + } + } + } + } catch (FileNotFoundException fnfe) { + Log.w(LOG_TAG, "Icon not found: " + uri + ", " + fnfe.getMessage()); + return null; + } + } + + + + private Drawable checkIconCache(String resourceUri) { + Drawable.ConstantState cached = mOutsideDrawablesCache.get(resourceUri); + if (cached == null) { + return null; + } + if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + resourceUri); + return cached.newDrawable(); + } + + private void storeInIconCache(String resourceUri, Drawable drawable) { + if (drawable != null) { + mOutsideDrawablesCache.put(resourceUri, drawable.getConstantState()); + } + } + + /** + * Gets the left-hand side icon that will be used for the current suggestion + * if the suggestion contains an icon column but no icon or a broken icon. + * + * @param cursor A cursor positioned at the current suggestion. + * @return A non-null drawable. + */ + private Drawable getDefaultIcon1(Cursor cursor) { + // Check the component that gave us the suggestion + Drawable drawable = getActivityIconWithCache(mSearchable.getSearchActivity()); + if (drawable != null) { + return drawable; + } + + // Fall back to a default icon + return mContext.getPackageManager().getDefaultActivityIcon(); + } + + /** + * Gets the activity or application icon for an activity. + * Uses the local icon cache for fast repeated lookups. + * + * @param component Name of an activity. + * @return A drawable, or {@code null} if neither the activity nor the application + * has an icon set. + */ + private Drawable getActivityIconWithCache(ComponentName component) { + // First check the icon cache + String componentIconKey = component.flattenToShortString(); + // Using containsKey() since we also store null values. + if (mOutsideDrawablesCache.containsKey(componentIconKey)) { + Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey); + return cached == null ? null : cached.newDrawable(mProviderContext.getResources()); + } + // Then try the activity or application icon + Drawable drawable = getActivityIcon(component); + // Stick it in the cache so we don't do this lookup again. + Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState(); + mOutsideDrawablesCache.put(componentIconKey, toCache); + return drawable; + } + + /** + * Gets the activity or application icon for an activity. + * + * @param component Name of an activity. + * @return A drawable, or {@code null} if neither the acitivy or the application + * have an icon set. + */ + private Drawable getActivityIcon(ComponentName component) { + PackageManager pm = mContext.getPackageManager(); + final ActivityInfo activityInfo; + try { + activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA); + } catch (NameNotFoundException ex) { + Log.w(LOG_TAG, ex.toString()); + return null; + } + int iconId = activityInfo.getIconResource(); + if (iconId == 0) return null; + String pkg = component.getPackageName(); + Drawable drawable = pm.getDrawable(pkg, iconId, activityInfo.applicationInfo); + if (drawable == null) { + Log.w(LOG_TAG, "Invalid icon resource " + iconId + " for " + + component.flattenToShortString()); + return null; + } + return drawable; + } + + /** + * Gets the value of a string column by name. + * + * @param cursor Cursor to read the value from. + * @param columnName The name of the column to read. + * @return The value of the given column, or null + * if the cursor does not contain the given column. + */ + public static String getColumnString(Cursor cursor, String columnName) { + int col = cursor.getColumnIndex(columnName); + return getStringOrNull(cursor, col); + } + + private static String getStringOrNull(Cursor cursor, int col) { + if (col == INVALID_INDEX) { + return null; + } + try { + return cursor.getString(col); + } catch (Exception e) { + Log.e(LOG_TAG, + "unexpected error retrieving valid column from cursor, " + + "did the remote process die?", e); + return null; + } + } + + /** + * Import of hidden method: ContentResolver.getResourceId(Uri). + * Modified to return a drawable, rather than a hidden type. + */ + Drawable getDrawableFromResourceUri(Uri uri) throws FileNotFoundException { + String authority = uri.getAuthority(); + Resources r; + if (TextUtils.isEmpty(authority)) { + throw new FileNotFoundException("No authority: " + uri); + } else { + try { + r = mContext.getPackageManager().getResourcesForApplication(authority); + } catch (NameNotFoundException ex) { + throw new FileNotFoundException("No package found for authority: " + uri); + } + } + List path = uri.getPathSegments(); + if (path == null) { + throw new FileNotFoundException("No path: " + uri); + } + int len = path.size(); + int id; + if (len == 1) { + try { + id = Integer.parseInt(path.get(0)); + } catch (NumberFormatException e) { + throw new FileNotFoundException("Single path segment is not a resource ID: " + uri); + } + } else if (len == 2) { + id = r.getIdentifier(path.get(1), path.get(0), authority); + } else { + throw new FileNotFoundException("More than two path segments: " + uri); + } + if (id == 0) { + throw new FileNotFoundException("No resource found for: " + uri); + } + return r.getDrawable(id); + } + + /** + * Import of hidden method: SearchManager.getSuggestions(SearchableInfo, String, int). + */ + Cursor getSearchManagerSuggestions(SearchableInfo searchable, String query, int limit) { + if (searchable == null) { + return null; + } + + String authority = searchable.getSuggestAuthority(); + if (authority == null) { + return null; + } + + Uri.Builder uriBuilder = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority) + .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel() + .fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel() + + // if content path provided, insert it now + final String contentPath = searchable.getSuggestPath(); + if (contentPath != null) { + uriBuilder.appendEncodedPath(contentPath); + } + + // append standard suggestion query path + uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY); + + // get the query selection, may be null + String selection = searchable.getSuggestSelection(); + // inject query, either as selection args or inline + String[] selArgs = null; + if (selection != null) { // use selection if provided + selArgs = new String[] { query }; + } else { // no selection, use REST pattern + uriBuilder.appendPath(query); + } + + if (limit > 0) { + uriBuilder.appendQueryParameter("limit", String.valueOf(limit)); + } + + Uri uri = uriBuilder.build(); + + // finally, make the query + return mContext.getContentResolver().query(uri, null, selection, selArgs, null); + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/SwitchCompat.java b/eclipse-compile/appcompat/src/android/support/v7/widget/SwitchCompat.java new file mode 100644 index 0000000000..9c53920e2c --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/SwitchCompat.java @@ -0,0 +1,1151 @@ +/* + * Copyright (C) 2014 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 android.support.v7.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewCompat; +import android.support.v7.appcompat.R; +import android.support.v7.internal.text.AllCapsTransformationMethod; +import android.support.v7.internal.widget.DrawableUtils; +import android.support.v7.internal.widget.TintManager; +import android.support.v7.internal.widget.TintTypedArray; +import android.support.v7.internal.widget.ViewUtils; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.method.TransformationMethod; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.Animation; +import android.view.animation.Transformation; +import android.widget.CompoundButton; + +/** + * SwitchCompat is a version of the Switch widget which on devices back to API v7. It does not + * make any attempt to use the platform provided widget on those devices which it is available + * normally. + *

+ * A Switch is a two-state toggle switch widget that can select between two + * options. The user may drag the "thumb" back and forth to choose the selected option, + * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text} + * property controls the text displayed in the label for the switch, whereas the + * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text + * controls the text on the thumb. Similarly, the + * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related + * setTypeface() methods control the typeface and style of label text, whereas the + * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and + * the related seSwitchTypeface() methods control that of the thumb. + */ +public class SwitchCompat extends CompoundButton { + private static final int THUMB_ANIMATION_DURATION = 250; + + private static final int TOUCH_MODE_IDLE = 0; + private static final int TOUCH_MODE_DOWN = 1; + private static final int TOUCH_MODE_DRAGGING = 2; + + // We force the accessibility events to have a class name of Switch, since screen readers + // already know how to handle their events + private static final String ACCESSIBILITY_EVENT_CLASS_NAME = "android.widget.Switch"; + + // Enum for the "typeface" XML parameter. + private static final int SANS = 1; + private static final int SERIF = 2; + private static final int MONOSPACE = 3; + + private Drawable mThumbDrawable; + private Drawable mTrackDrawable; + private int mThumbTextPadding; + private int mSwitchMinWidth; + private int mSwitchPadding; + private boolean mSplitTrack; + private CharSequence mTextOn; + private CharSequence mTextOff; + private boolean mShowText; + + private int mTouchMode; + private int mTouchSlop; + private float mTouchX; + private float mTouchY; + private VelocityTracker mVelocityTracker = VelocityTracker.obtain(); + private int mMinFlingVelocity; + + private float mThumbPosition; + + /** + * Width required to draw the switch track and thumb. Includes padding and + * optical bounds for both the track and thumb. + */ + private int mSwitchWidth; + + /** + * Height required to draw the switch track and thumb. Includes padding and + * optical bounds for both the track and thumb. + */ + private int mSwitchHeight; + + /** + * Width of the thumb's content region. Does not include padding or + * optical bounds. + */ + private int mThumbWidth; + + /** Left bound for drawing the switch track and thumb. */ + private int mSwitchLeft; + + /** Top bound for drawing the switch track and thumb. */ + private int mSwitchTop; + + /** Right bound for drawing the switch track and thumb. */ + private int mSwitchRight; + + /** Bottom bound for drawing the switch track and thumb. */ + private int mSwitchBottom; + + private TextPaint mTextPaint; + private ColorStateList mTextColors; + private Layout mOnLayout; + private Layout mOffLayout; + private TransformationMethod mSwitchTransformationMethod; + private Animation mPositionAnimator; + + @SuppressWarnings("hiding") + private final Rect mTempRect = new Rect(); + + private final TintManager mTintManager; + + private static final int[] CHECKED_STATE_SET = { + android.R.attr.state_checked + }; + + /** + * Construct a new Switch with default styling. + * + * @param context The Context that will determine this widget's theming. + */ + public SwitchCompat(Context context) { + this(context, null); + } + + /** + * Construct a new Switch with default styling, overriding specific style + * attributes as requested. + * + * @param context The Context that will determine this widget's theming. + * @param attrs Specification of attributes that should deviate from default styling. + */ + public SwitchCompat(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.switchStyle); + } + + /** + * Construct a new Switch with a default style determined by the given theme attribute, + * overriding specific style attributes as requested. + * + * @param context The Context that will determine this widget's theming. + * @param attrs Specification of attributes that should deviate from the default styling. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + */ + public SwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + + final Resources res = getResources(); + mTextPaint.density = res.getDisplayMetrics().density; + + final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, + attrs, R.styleable.SwitchCompat, defStyleAttr, 0); + mThumbDrawable = a.getDrawable(R.styleable.SwitchCompat_android_thumb); + if (mThumbDrawable != null) { + mThumbDrawable.setCallback(this); + } + mTrackDrawable = a.getDrawable(R.styleable.SwitchCompat_track); + if (mTrackDrawable != null) { + mTrackDrawable.setCallback(this); + } + mTextOn = a.getText(R.styleable.SwitchCompat_android_textOn); + mTextOff = a.getText(R.styleable.SwitchCompat_android_textOff); + mShowText = a.getBoolean(R.styleable.SwitchCompat_showText, true); + mThumbTextPadding = a.getDimensionPixelSize( + R.styleable.SwitchCompat_thumbTextPadding, 0); + mSwitchMinWidth = a.getDimensionPixelSize( + R.styleable.SwitchCompat_switchMinWidth, 0); + mSwitchPadding = a.getDimensionPixelSize( + R.styleable.SwitchCompat_switchPadding, 0); + mSplitTrack = a.getBoolean(R.styleable.SwitchCompat_splitTrack, false); + + final int appearance = a.getResourceId( + R.styleable.SwitchCompat_switchTextAppearance, 0); + if (appearance != 0) { + setSwitchTextAppearance(context, appearance); + } + + mTintManager = a.getTintManager(); + + a.recycle(); + + final ViewConfiguration config = ViewConfiguration.get(context); + mTouchSlop = config.getScaledTouchSlop(); + mMinFlingVelocity = config.getScaledMinimumFlingVelocity(); + + // Refresh display with current params + refreshDrawableState(); + setChecked(isChecked()); + } + + /** + * Sets the switch text color, size, style, hint color, and highlight color + * from the specified TextAppearance resource. + */ + public void setSwitchTextAppearance(Context context, int resid) { + TypedArray appearance = context.obtainStyledAttributes(resid, R.styleable.TextAppearance); + + ColorStateList colors; + int ts; + + colors = appearance.getColorStateList(R.styleable.TextAppearance_android_textColor); + if (colors != null) { + mTextColors = colors; + } else { + // If no color set in TextAppearance, default to the view's textColor + mTextColors = getTextColors(); + } + + ts = appearance.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, 0); + if (ts != 0) { + if (ts != mTextPaint.getTextSize()) { + mTextPaint.setTextSize(ts); + requestLayout(); + } + } + + int typefaceIndex, styleIndex; + typefaceIndex = appearance.getInt(R.styleable.TextAppearance_android_typeface, -1); + styleIndex = appearance.getInt(R.styleable.TextAppearance_android_textStyle, -1); + + setSwitchTypefaceByIndex(typefaceIndex, styleIndex); + + boolean allCaps = appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false); + if (allCaps) { + mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext()); + } else { + mSwitchTransformationMethod = null; + } + + appearance.recycle(); + } + + private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) { + Typeface tf = null; + switch (typefaceIndex) { + case SANS: + tf = Typeface.SANS_SERIF; + break; + + case SERIF: + tf = Typeface.SERIF; + break; + + case MONOSPACE: + tf = Typeface.MONOSPACE; + break; + } + + setSwitchTypeface(tf, styleIndex); + } + + /** + * Sets the typeface and style in which the text should be displayed on the + * switch, and turns on the fake bold and italic bits in the Paint if the + * Typeface that you provided does not have all the bits in the + * style that you specified. + */ + public void setSwitchTypeface(Typeface tf, int style) { + if (style > 0) { + if (tf == null) { + tf = Typeface.defaultFromStyle(style); + } else { + tf = Typeface.create(tf, style); + } + + setSwitchTypeface(tf); + // now compute what (if any) algorithmic styling is needed + int typefaceStyle = tf != null ? tf.getStyle() : 0; + int need = style & ~typefaceStyle; + mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); + mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); + } else { + mTextPaint.setFakeBoldText(false); + mTextPaint.setTextSkewX(0); + setSwitchTypeface(tf); + } + } + + /** + * Sets the typeface in which the text should be displayed on the switch. + * Note that not all Typeface families actually have bold and italic + * variants, so you may need to use + * {@link #setSwitchTypeface(Typeface, int)} to get the appearance + * that you actually want. + */ + public void setSwitchTypeface(Typeface tf) { + if (mTextPaint.getTypeface() != tf) { + mTextPaint.setTypeface(tf); + + requestLayout(); + invalidate(); + } + } + + /** + * Set the amount of horizontal padding between the switch and the associated text. + * + * @param pixels Amount of padding in pixels + */ + public void setSwitchPadding(int pixels) { + mSwitchPadding = pixels; + requestLayout(); + } + + /** + * Get the amount of horizontal padding between the switch and the associated text. + * + * @return Amount of padding in pixels + */ + public int getSwitchPadding() { + return mSwitchPadding; + } + + /** + * Set the minimum width of the switch in pixels. The switch's width will be the maximum + * of this value and its measured width as determined by the switch drawables and text used. + * + * @param pixels Minimum width of the switch in pixels + */ + public void setSwitchMinWidth(int pixels) { + mSwitchMinWidth = pixels; + requestLayout(); + } + + /** + * Get the minimum width of the switch in pixels. The switch's width will be the maximum + * of this value and its measured width as determined by the switch drawables and text used. + * + * @return Minimum width of the switch in pixels + */ + public int getSwitchMinWidth() { + return mSwitchMinWidth; + } + + /** + * Set the horizontal padding around the text drawn on the switch itself. + * + * @param pixels Horizontal padding for switch thumb text in pixels + */ + public void setThumbTextPadding(int pixels) { + mThumbTextPadding = pixels; + requestLayout(); + } + + /** + * Get the horizontal padding around the text drawn on the switch itself. + * + * @return Horizontal padding for switch thumb text in pixels + */ + public int getThumbTextPadding() { + return mThumbTextPadding; + } + + /** + * Set the drawable used for the track that the switch slides within. + * + * @param track Track drawable + */ + public void setTrackDrawable(Drawable track) { + mTrackDrawable = track; + requestLayout(); + } + + /** + * Set the drawable used for the track that the switch slides within. + * + * @param resId Resource ID of a track drawable + */ + public void setTrackResource(int resId) { + setTrackDrawable(mTintManager.getDrawable(resId)); + } + + /** + * Get the drawable used for the track that the switch slides within. + * + * @return Track drawable + */ + public Drawable getTrackDrawable() { + return mTrackDrawable; + } + + /** + * Set the drawable used for the switch "thumb" - the piece that the user + * can physically touch and drag along the track. + * + * @param thumb Thumb drawable + */ + public void setThumbDrawable(Drawable thumb) { + mThumbDrawable = thumb; + requestLayout(); + } + + /** + * Set the drawable used for the switch "thumb" - the piece that the user + * can physically touch and drag along the track. + * + * @param resId Resource ID of a thumb drawable + */ + public void setThumbResource(int resId) { + setThumbDrawable(mTintManager.getDrawable(resId)); + } + + /** + * Get the drawable used for the switch "thumb" - the piece that the user + * can physically touch and drag along the track. + * + * @return Thumb drawable + */ + public Drawable getThumbDrawable() { + return mThumbDrawable; + } + + /** + * Specifies whether the track should be split by the thumb. When true, + * the thumb's optical bounds will be clipped out of the track drawable, + * then the thumb will be drawn into the resulting gap. + * + * @param splitTrack Whether the track should be split by the thumb + */ + public void setSplitTrack(boolean splitTrack) { + mSplitTrack = splitTrack; + invalidate(); + } + + /** + * Returns whether the track should be split by the thumb. + */ + public boolean getSplitTrack() { + return mSplitTrack; + } + + /** + * Returns the text displayed when the button is in the checked state. + */ + public CharSequence getTextOn() { + return mTextOn; + } + + /** + * Sets the text displayed when the button is in the checked state. + */ + public void setTextOn(CharSequence textOn) { + mTextOn = textOn; + requestLayout(); + } + + /** + * Returns the text displayed when the button is not in the checked state. + */ + public CharSequence getTextOff() { + return mTextOff; + } + + /** + * Sets the text displayed when the button is not in the checked state. + */ + public void setTextOff(CharSequence textOff) { + mTextOff = textOff; + requestLayout(); + } + + /** + * Sets whether the on/off text should be displayed. + * + * @param showText {@code true} to display on/off text + */ + public void setShowText(boolean showText) { + if (mShowText != showText) { + mShowText = showText; + requestLayout(); + } + } + + /** + * @return whether the on/off text should be displayed + */ + public boolean getShowText() { + return mShowText; + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mShowText) { + if (mOnLayout == null) { + mOnLayout = makeLayout(mTextOn); + } + + if (mOffLayout == null) { + mOffLayout = makeLayout(mTextOff); + } + } + + final Rect padding = mTempRect; + final int thumbWidth; + final int thumbHeight; + if (mThumbDrawable != null) { + // Cached thumb width does not include padding. + mThumbDrawable.getPadding(padding); + thumbWidth = mThumbDrawable.getIntrinsicWidth() - padding.left - padding.right; + thumbHeight = mThumbDrawable.getIntrinsicHeight(); + } else { + thumbWidth = 0; + thumbHeight = 0; + } + + final int maxTextWidth; + if (mShowText) { + maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()) + + mThumbTextPadding * 2; + } else { + maxTextWidth = 0; + } + + mThumbWidth = Math.max(maxTextWidth, thumbWidth); + + final int trackHeight; + if (mTrackDrawable != null) { + mTrackDrawable.getPadding(padding); + trackHeight = mTrackDrawable.getIntrinsicHeight(); + } else { + padding.setEmpty(); + trackHeight = 0; + } + + // Adjust left and right padding to ensure there's enough room for the + // thumb's padding (when present). + int paddingLeft = padding.left; + int paddingRight = padding.right; + if (mThumbDrawable != null) { + final Rect inset = DrawableUtils.getOpticalBounds(mThumbDrawable); + paddingLeft = Math.max(paddingLeft, inset.left); + paddingRight = Math.max(paddingRight, inset.right); + } + + final int switchWidth = Math.max(mSwitchMinWidth, + 2 * mThumbWidth + paddingLeft + paddingRight); + final int switchHeight = Math.max(trackHeight, thumbHeight); + mSwitchWidth = switchWidth; + mSwitchHeight = switchHeight; + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + final int measuredHeight = getMeasuredHeight(); + if (measuredHeight < switchHeight) { + setMeasuredDimension(ViewCompat.getMeasuredWidthAndState(this), switchHeight); + } + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + super.onPopulateAccessibilityEvent(event); + + final CharSequence text = isChecked() ? mTextOn : mTextOff; + if (text != null) { + event.getText().add(text); + } + } + + private Layout makeLayout(CharSequence text) { + final CharSequence transformed = (mSwitchTransformationMethod != null) + ? mSwitchTransformationMethod.getTransformation(text, this) + : text; + + return new StaticLayout(transformed, mTextPaint, + transformed != null ? + (int) Math.ceil(Layout.getDesiredWidth(transformed, mTextPaint)) : 0, + Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true); + } + + /** + * @return true if (x, y) is within the target area of the switch thumb + */ + private boolean hitThumb(float x, float y) { + if (mThumbDrawable == null) { + return false; + } + + // Relies on mTempRect, MUST be called first! + final int thumbOffset = getThumbOffset(); + + mThumbDrawable.getPadding(mTempRect); + final int thumbTop = mSwitchTop - mTouchSlop; + final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop; + final int thumbRight = thumbLeft + mThumbWidth + + mTempRect.left + mTempRect.right + mTouchSlop; + final int thumbBottom = mSwitchBottom + mTouchSlop; + return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + mVelocityTracker.addMovement(ev); + final int action = MotionEventCompat.getActionMasked(ev); + switch (action) { + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + if (isEnabled() && hitThumb(x, y)) { + mTouchMode = TOUCH_MODE_DOWN; + mTouchX = x; + mTouchY = y; + } + break; + } + + case MotionEvent.ACTION_MOVE: { + switch (mTouchMode) { + case TOUCH_MODE_IDLE: + // Didn't target the thumb, treat normally. + break; + + case TOUCH_MODE_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + if (Math.abs(x - mTouchX) > mTouchSlop || + Math.abs(y - mTouchY) > mTouchSlop) { + mTouchMode = TOUCH_MODE_DRAGGING; + getParent().requestDisallowInterceptTouchEvent(true); + mTouchX = x; + mTouchY = y; + return true; + } + break; + } + + case TOUCH_MODE_DRAGGING: { + final float x = ev.getX(); + final int thumbScrollRange = getThumbScrollRange(); + final float thumbScrollOffset = x - mTouchX; + float dPos; + if (thumbScrollRange != 0) { + dPos = thumbScrollOffset / thumbScrollRange; + } else { + // If the thumb scroll range is empty, just use the + // movement direction to snap on or off. + dPos = thumbScrollOffset > 0 ? 1 : -1; + } + if (ViewUtils.isLayoutRtl(this)) { + dPos = -dPos; + } + final float newPos = constrain(mThumbPosition + dPos, 0, 1); + if (newPos != mThumbPosition) { + mTouchX = x; + setThumbPosition(newPos); + } + return true; + } + } + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + if (mTouchMode == TOUCH_MODE_DRAGGING) { + stopDrag(ev); + // Allow super class to handle pressed state, etc. + super.onTouchEvent(ev); + return true; + } + mTouchMode = TOUCH_MODE_IDLE; + mVelocityTracker.clear(); + break; + } + } + + return super.onTouchEvent(ev); + } + + private void cancelSuperTouch(MotionEvent ev) { + MotionEvent cancel = MotionEvent.obtain(ev); + cancel.setAction(MotionEvent.ACTION_CANCEL); + super.onTouchEvent(cancel); + cancel.recycle(); + } + + /** + * Called from onTouchEvent to end a drag operation. + * + * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL + */ + private void stopDrag(MotionEvent ev) { + mTouchMode = TOUCH_MODE_IDLE; + + // Commit the change if the event is up and not canceled and the switch + // has not been disabled during the drag. + final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled(); + final boolean oldState = isChecked(); + final boolean newState; + if (commitChange) { + mVelocityTracker.computeCurrentVelocity(1000); + final float xvel = mVelocityTracker.getXVelocity(); + if (Math.abs(xvel) > mMinFlingVelocity) { + newState = ViewUtils.isLayoutRtl(this) ? (xvel < 0) : (xvel > 0); + } else { + newState = getTargetCheckedState(); + } + } else { + newState = oldState; + } + + if (newState != oldState) { + playSoundEffect(SoundEffectConstants.CLICK); + setChecked(newState); + } + cancelSuperTouch(ev); + } + + private void animateThumbToCheckedState(boolean newCheckedState) { + final float startPosition = mThumbPosition; + final float targetPosition = newCheckedState ? 1 : 0; + final float diff = targetPosition - startPosition; + + mPositionAnimator = new Animation() { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + setThumbPosition(startPosition + (diff * interpolatedTime)); + } + }; + mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION); + startAnimation(mPositionAnimator); + } + + private void cancelPositionAnimator() { + if (mPositionAnimator != null) { + clearAnimation(); + mPositionAnimator = null; + } + } + + private boolean getTargetCheckedState() { + return mThumbPosition > 0.5f; + } + + /** + * Sets the thumb position as a decimal value between 0 (off) and 1 (on). + * + * @param position new position between [0,1] + */ + private void setThumbPosition(float position) { + mThumbPosition = position; + invalidate(); + } + + @Override + public void toggle() { + setChecked(!isChecked()); + } + + @Override + public void setChecked(boolean checked) { + super.setChecked(checked); + + // Calling the super method may result in setChecked() getting called + // recursively with a different value, so load the REAL value... + checked = isChecked(); + + if (getWindowToken() != null && ViewCompat.isLaidOut(this)) { + animateThumbToCheckedState(checked); + } else { + // Immediately move the thumb to the new position. + cancelPositionAnimator(); + setThumbPosition(checked ? 1 : 0); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + int opticalInsetLeft = 0; + int opticalInsetRight = 0; + if (mThumbDrawable != null) { + final Rect trackPadding = mTempRect; + if (mTrackDrawable != null) { + mTrackDrawable.getPadding(trackPadding); + } else { + trackPadding.setEmpty(); + } + + final Rect insets = DrawableUtils.getOpticalBounds(mThumbDrawable); + opticalInsetLeft = Math.max(0, insets.left - trackPadding.left); + opticalInsetRight = Math.max(0, insets.right - trackPadding.right); + } + + final int switchRight; + final int switchLeft; + if (ViewUtils.isLayoutRtl(this)) { + switchLeft = getPaddingLeft() + opticalInsetLeft; + switchRight = switchLeft + mSwitchWidth - opticalInsetLeft - opticalInsetRight; + } else { + switchRight = getWidth() - getPaddingRight() - opticalInsetRight; + switchLeft = switchRight - mSwitchWidth + opticalInsetLeft + opticalInsetRight; + } + + final int switchTop; + final int switchBottom; + switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) { + default: + case Gravity.TOP: + switchTop = getPaddingTop(); + switchBottom = switchTop + mSwitchHeight; + break; + + case Gravity.CENTER_VERTICAL: + switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 - + mSwitchHeight / 2; + switchBottom = switchTop + mSwitchHeight; + break; + + case Gravity.BOTTOM: + switchBottom = getHeight() - getPaddingBottom(); + switchTop = switchBottom - mSwitchHeight; + break; + } + + mSwitchLeft = switchLeft; + mSwitchTop = switchTop; + mSwitchBottom = switchBottom; + mSwitchRight = switchRight; + } + + @Override + public void draw(Canvas c) { + final Rect padding = mTempRect; + final int switchLeft = mSwitchLeft; + final int switchTop = mSwitchTop; + final int switchRight = mSwitchRight; + final int switchBottom = mSwitchBottom; + + int thumbInitialLeft = switchLeft + getThumbOffset(); + + final Rect thumbInsets; + if (mThumbDrawable != null) { + thumbInsets = DrawableUtils.getOpticalBounds(mThumbDrawable); + } else { + thumbInsets = DrawableUtils.INSETS_NONE; + } + + // Layout the track. + if (mTrackDrawable != null) { + mTrackDrawable.getPadding(padding); + + // Adjust thumb position for track padding. + thumbInitialLeft += padding.left; + + // If necessary, offset by the optical insets of the thumb asset. + int trackLeft = switchLeft; + int trackTop = switchTop; + int trackRight = switchRight; + int trackBottom = switchBottom; + if (thumbInsets != null && !thumbInsets.isEmpty()) { + if (thumbInsets.left > padding.left) { + trackLeft += thumbInsets.left - padding.left; + } + if (thumbInsets.top > padding.top) { + trackTop += thumbInsets.top - padding.top; + } + if (thumbInsets.right > padding.right) { + trackRight -= thumbInsets.right - padding.right; + } + if (thumbInsets.bottom > padding.bottom) { + trackBottom -= thumbInsets.bottom - padding.bottom; + } + } + mTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom); + } + + // Layout the thumb. + if (mThumbDrawable != null) { + mThumbDrawable.getPadding(padding); + + final int thumbLeft = thumbInitialLeft - padding.left; + final int thumbRight = thumbInitialLeft + mThumbWidth + padding.right; + mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); + + final Drawable background = getBackground(); + if (background != null) { + DrawableCompat.setHotspotBounds(background, thumbLeft, switchTop, + thumbRight, switchBottom); + } + } + + // Draw the background. + super.draw(c); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + final Rect padding = mTempRect; + final Drawable trackDrawable = mTrackDrawable; + if (trackDrawable != null) { + trackDrawable.getPadding(padding); + } else { + padding.setEmpty(); + } + + final int switchTop = mSwitchTop; + final int switchBottom = mSwitchBottom; + final int switchInnerTop = switchTop + padding.top; + final int switchInnerBottom = switchBottom - padding.bottom; + + final Drawable thumbDrawable = mThumbDrawable; + if (trackDrawable != null) { + if (mSplitTrack && thumbDrawable != null) { + final Rect insets = DrawableUtils.getOpticalBounds(thumbDrawable); + thumbDrawable.copyBounds(padding); + padding.left += insets.left; + padding.right -= insets.right; + + final int saveCount = canvas.save(); + canvas.clipRect(padding, Region.Op.DIFFERENCE); + trackDrawable.draw(canvas); + canvas.restoreToCount(saveCount); + } else { + trackDrawable.draw(canvas); + } + } + + final int saveCount = canvas.save(); + + if (thumbDrawable != null) { + thumbDrawable.draw(canvas); + } + + final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout; + if (switchText != null) { + final int drawableState[] = getDrawableState(); + if (mTextColors != null) { + mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0)); + } + mTextPaint.drawableState = drawableState; + + final int cX; + if (thumbDrawable != null) { + final Rect bounds = thumbDrawable.getBounds(); + cX = bounds.left + bounds.right; + } else { + cX = getWidth(); + } + + final int left = cX / 2 - switchText.getWidth() / 2; + final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2; + canvas.translate(left, top); + switchText.draw(canvas); + } + + canvas.restoreToCount(saveCount); + } + + @Override + public int getCompoundPaddingLeft() { + if (!ViewUtils.isLayoutRtl(this)) { + return super.getCompoundPaddingLeft(); + } + int padding = super.getCompoundPaddingLeft() + mSwitchWidth; + if (!TextUtils.isEmpty(getText())) { + padding += mSwitchPadding; + } + return padding; + } + + @Override + public int getCompoundPaddingRight() { + if (ViewUtils.isLayoutRtl(this)) { + return super.getCompoundPaddingRight(); + } + int padding = super.getCompoundPaddingRight() + mSwitchWidth; + if (!TextUtils.isEmpty(getText())) { + padding += mSwitchPadding; + } + return padding; + } + + /** + * Translates thumb position to offset according to current RTL setting and + * thumb scroll range. Accounts for both track and thumb padding. + * + * @return thumb offset + */ + private int getThumbOffset() { + final float thumbPosition; + if (ViewUtils.isLayoutRtl(this)) { + thumbPosition = 1 - mThumbPosition; + } else { + thumbPosition = mThumbPosition; + } + return (int) (thumbPosition * getThumbScrollRange() + 0.5f); + } + + private int getThumbScrollRange() { + if (mTrackDrawable != null) { + final Rect padding = mTempRect; + mTrackDrawable.getPadding(padding); + + final Rect insets; + if (mThumbDrawable != null) { + insets = DrawableUtils.getOpticalBounds(mThumbDrawable); + } else { + insets = DrawableUtils.INSETS_NONE; + } + + return mSwitchWidth - mThumbWidth - padding.left - padding.right + - insets.left - insets.right; + } else { + return 0; + } + } + + @Override + protected int[] onCreateDrawableState(int extraSpace) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + if (isChecked()) { + mergeDrawableStates(drawableState, CHECKED_STATE_SET); + } + return drawableState; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + final int[] myDrawableState = getDrawableState(); + + if (mThumbDrawable != null) { + mThumbDrawable.setState(myDrawableState); + } + + if (mTrackDrawable != null) { + mTrackDrawable.setState(myDrawableState); + } + + invalidate(); + } + + @Override + public void drawableHotspotChanged(float x, float y) { + if (Build.VERSION.SDK_INT >= 21) { + super.drawableHotspotChanged(x, y); + } + + if (mThumbDrawable != null) { + DrawableCompat.setHotspot(mThumbDrawable, x, y); + } + + if (mTrackDrawable != null) { + DrawableCompat.setHotspot(mTrackDrawable, x, y); + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable; + } + + @Override + public void jumpDrawablesToCurrentState() { + if (Build.VERSION.SDK_INT >= 11) { + super.jumpDrawablesToCurrentState(); + + if (mThumbDrawable != null) { + mThumbDrawable.jumpToCurrentState(); + } + + if (mTrackDrawable != null) { + mTrackDrawable.jumpToCurrentState(); + } + + if (mPositionAnimator != null && !mPositionAnimator.hasEnded()) { + clearAnimation(); + mPositionAnimator = null; + } + } + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ACCESSIBILITY_EVENT_CLASS_NAME); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + if (Build.VERSION.SDK_INT >= 14) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ACCESSIBILITY_EVENT_CLASS_NAME); + CharSequence switchText = isChecked() ? mTextOn : mTextOff; + if (!TextUtils.isEmpty(switchText)) { + CharSequence oldText = info.getText(); + if (TextUtils.isEmpty(oldText)) { + info.setText(switchText); + } else { + StringBuilder newText = new StringBuilder(); + newText.append(oldText).append(' ').append(switchText); + info.setText(newText); + } + } + } + } + + /** + * Taken from android.util.MathUtils + */ + private static float constrain(float amount, float low, float high) { + return amount < low ? low : (amount > high ? high : amount); + } +} \ No newline at end of file diff --git a/eclipse-compile/appcompat/src/android/support/v7/widget/Toolbar.java b/eclipse-compile/appcompat/src/android/support/v7/widget/Toolbar.java new file mode 100644 index 0000000000..d5381c892b --- /dev/null +++ b/eclipse-compile/appcompat/src/android/support/v7/widget/Toolbar.java @@ -0,0 +1,2028 @@ +/* + * Copyright (C) 2014 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 android.support.v7.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.MarginLayoutParamsCompat; +import android.support.v4.view.MenuItemCompat; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.appcompat.R; +import android.support.v7.internal.view.SupportMenuInflater; +import android.support.v7.internal.view.renamemenu.MenuBuilder; +import android.support.v7.internal.view.renamemenu.MenuItemImpl; +import android.support.v7.internal.view.renamemenu.MenuPresenter; +import android.support.v7.internal.view.renamemenu.MenuView; +import android.support.v7.internal.view.renamemenu.SubMenuBuilder; +import android.support.v7.internal.widget.DecorToolbar; +import android.support.v7.internal.widget.RtlSpacingHelper; +import android.support.v7.internal.widget.TintManager; +import android.support.v7.internal.widget.TintTypedArray; +import android.support.v7.internal.widget.ToolbarWidgetWrapper; +import android.support.v7.internal.widget.ViewUtils; +import android.support.v7.view.CollapsibleActionView; +import android.text.Layout; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +/** + * A standard toolbar for use within application content. + * + *

A Toolbar is a generalization of {@link ActionBar action bars} for use + * within application layouts. While an action bar is traditionally part of an + * {@link android.app.Activity Activity's} opaque window decor controlled by the framework, + * a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy. + * An application may choose to designate a Toolbar as the action bar for an Activity + * using the {@link android.support.v7.app.AppCompatActivity#setSupportActionBar(Toolbar) + * setSupportActionBar()} method.

+ * + *

Toolbar supports a more focused feature set than ActionBar. From start to end, a toolbar + * may contain a combination of the following optional elements: + * + *

    + *
  • A navigation button. This may be an Up arrow, navigation menu toggle, close, + * collapse, done or another glyph of the app's choosing. This button should always be used + * to access other navigational destinations within the container of the Toolbar and + * its signified content or otherwise leave the current context signified by the Toolbar.
  • + *
  • A branded logo image. This may extend to the height of the bar and can be + * arbitrarily wide.
  • + *
  • A title and subtitle. The title should be a signpost for the Toolbar's current + * position in the navigation hierarchy and the content contained there. The subtitle, + * if present should indicate any extended information about the current content. + * If an app uses a logo image it should strongly consider omitting a title and subtitle.
  • + *
  • One or more custom views. The application may add arbitrary child views + * to the Toolbar. They will appear at this position within the layout. If a child view's + * {@link LayoutParams} indicates a {@link Gravity} value of + * {@link Gravity#CENTER_HORIZONTAL CENTER_HORIZONTAL} the view will attempt to center + * within the available space remaining in the Toolbar after all other elements have been + * measured.
  • + *
  • An {@link ActionMenuView action menu}. The menu of actions will pin to the + * end of the Toolbar offering a few + * + * frequent, important or typical actions along with an optional overflow menu for + * additional actions.
  • + *
+ *

+ * + *

In modern Android UIs developers should lean more on a visually distinct color scheme for + * toolbars than on their application icon. The use of application icon plus title as a standard + * layout is discouraged on API 21 devices and newer.

+ */ +public class Toolbar extends ViewGroup { + private static final String TAG = "Toolbar"; + + private ActionMenuView mMenuView; + private TextView mTitleTextView; + private TextView mSubtitleTextView; + private ImageButton mNavButtonView; + private ImageView mLogoView; + + private Drawable mCollapseIcon; + private CharSequence mCollapseDescription; + private ImageButton mCollapseButtonView; + View mExpandedActionView; + + /** Context against which to inflate popup menus. */ + private Context mPopupContext; + + /** Theme resource against which to inflate popup menus. */ + private int mPopupTheme; + + private int mTitleTextAppearance; + private int mSubtitleTextAppearance; + + private int mButtonGravity; + + private int mMaxButtonHeight; + + private int mTitleMarginStart; + private int mTitleMarginEnd; + private int mTitleMarginTop; + private int mTitleMarginBottom; + + private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper(); + + private int mGravity = GravityCompat.START | Gravity.CENTER_VERTICAL; + + private CharSequence mTitleText; + private CharSequence mSubtitleText; + + private int mTitleTextColor; + private int mSubtitleTextColor; + + private boolean mEatingTouch; + private boolean mEatingHover; + + // Clear me after use. + private final ArrayList mTempViews = new ArrayList(); + + private final int[] mTempMargins = new int[2]; + + private OnMenuItemClickListener mOnMenuItemClickListener; + + private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener = + new ActionMenuView.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if (mOnMenuItemClickListener != null) { + return mOnMenuItemClickListener.onMenuItemClick(item); + } + return false; + } + }; + + private ToolbarWidgetWrapper mWrapper; + private ActionMenuPresenter mOuterActionMenuPresenter; + private ExpandedActionViewMenuPresenter mExpandedMenuPresenter; + private MenuPresenter.Callback mActionMenuPresenterCallback; + private MenuBuilder.Callback mMenuBuilderCallback; + + private boolean mCollapsible; + private int mMinHeight; + + private final Runnable mShowOverflowMenuRunnable = new Runnable() { + @Override public void run() { + showOverflowMenu(); + } + }; + + private final TintManager mTintManager; + + public Toolbar(Context context) { + this(context, null); + } + + public Toolbar(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.toolbarStyle); + } + + public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) { + // We manually themify the context here so that we don't break apps which only + // use app:theme when running on >= Lollipop + super(ViewUtils.themifyContext(context, attrs, false, true), attrs, defStyleAttr); + + // Need to use getContext() here so that we use the themed context + final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, + R.styleable.Toolbar, defStyleAttr, 0); + + mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0); + mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0); + mGravity = a.getInteger(R.styleable.Toolbar_android_gravity, mGravity); + mButtonGravity = Gravity.TOP; + mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom = + a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0); + + final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1); + if (marginStart >= 0) { + mTitleMarginStart = marginStart; + } + + final int marginEnd = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginEnd, -1); + if (marginEnd >= 0) { + mTitleMarginEnd = marginEnd; + } + + final int marginTop = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginTop, -1); + if (marginTop >= 0) { + mTitleMarginTop = marginTop; + } + + final int marginBottom = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginBottom, + -1); + if (marginBottom >= 0) { + mTitleMarginBottom = marginBottom; + } + + mMaxButtonHeight = a.getDimensionPixelSize(R.styleable.Toolbar_maxButtonHeight, -1); + + final int contentInsetStart = + a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetStart, + RtlSpacingHelper.UNDEFINED); + final int contentInsetEnd = + a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetEnd, + RtlSpacingHelper.UNDEFINED); + final int contentInsetLeft = + a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetLeft, 0); + final int contentInsetRight = + a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetRight, 0); + + mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight); + + if (contentInsetStart != RtlSpacingHelper.UNDEFINED || + contentInsetEnd != RtlSpacingHelper.UNDEFINED) { + mContentInsets.setRelative(contentInsetStart, contentInsetEnd); + } + + mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon); + mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription); + + final CharSequence title = a.getText(R.styleable.Toolbar_title); + if (!TextUtils.isEmpty(title)) { + setTitle(title); + } + + final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle); + if (!TextUtils.isEmpty(subtitle)) { + setSubtitle(subtitle); + } + // Set the default context, since setPopupTheme() may be a no-op. + mPopupContext = getContext(); + setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0)); + + final Drawable navIcon = a.getDrawable(R.styleable.Toolbar_navigationIcon); + if (navIcon != null) { + setNavigationIcon(navIcon); + } + final CharSequence navDesc = a.getText(R.styleable.Toolbar_navigationContentDescription); + if (!TextUtils.isEmpty(navDesc)) { + setNavigationContentDescription(navDesc); + } + + // This is read for devices running pre-v16 + mMinHeight = a.getDimensionPixelSize(R.styleable.Toolbar_android_minHeight, 0); + + a.recycle(); + + // Keep the TintManager in case we need it later + mTintManager = a.getTintManager(); + } + + /** + * Specifies the theme to use when inflating popup menus. By default, uses + * the same theme as the toolbar itself. + * + * @param resId theme used to inflate popup menus + * @see #getPopupTheme() + */ + public void setPopupTheme(int resId) { + if (mPopupTheme != resId) { + mPopupTheme = resId; + if (resId == 0) { + mPopupContext = getContext(); + } else { + mPopupContext = new ContextThemeWrapper(getContext(), resId); + } + } + } + + /** + * @return resource identifier of the theme used to inflate popup menus, or + * 0 if menus are inflated against the toolbar theme + * @see #setPopupTheme(int) + */ + public int getPopupTheme() { + return mPopupTheme; + } + + public void onRtlPropertiesChanged(int layoutDirection) { + if (Build.VERSION.SDK_INT >= 17) { + super.onRtlPropertiesChanged(layoutDirection); + } + mContentInsets.setDirection(layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL); + } + + /** + * Set a logo drawable from a resource id. + * + *

This drawable should generally take the place of title text. The logo cannot be + * clicked. Apps using a logo should also supply a description using + * {@link #setLogoDescription(int)}.

+ * + * @param resId ID of a drawable resource + */ + public void setLogo(int resId) { + setLogo(mTintManager.getDrawable(resId)); + } + + /** @hide */ + public boolean canShowOverflowMenu() { + return getVisibility() == VISIBLE && mMenuView != null && mMenuView.isOverflowReserved(); + } + + /** + * Check whether the overflow menu is currently showing. This may not reflect + * a pending show operation in progress. + * + * @return true if the overflow menu is currently showing + */ + public boolean isOverflowMenuShowing() { + return mMenuView != null && mMenuView.isOverflowMenuShowing(); + } + + /** @hide */ + public boolean isOverflowMenuShowPending() { + return mMenuView != null && mMenuView.isOverflowMenuShowPending(); + } + + /** + * Show the overflow items from the associated menu. + * + * @return true if the menu was able to be shown, false otherwise + */ + public boolean showOverflowMenu() { + return mMenuView != null && mMenuView.showOverflowMenu(); + } + + /** + * Hide the overflow items from the associated menu. + * + * @return true if the menu was able to be hidden, false otherwise + */ + public boolean hideOverflowMenu() { + return mMenuView != null && mMenuView.hideOverflowMenu(); + } + + /** @hide */ + public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) { + if (menu == null && mMenuView == null) { + return; + } + + ensureMenuView(); + final MenuBuilder oldMenu = mMenuView.peekMenu(); + if (oldMenu == menu) { + return; + } + + if (oldMenu != null) { + oldMenu.removeMenuPresenter(mOuterActionMenuPresenter); + oldMenu.removeMenuPresenter(mExpandedMenuPresenter); + } + + if (mExpandedMenuPresenter == null) { + mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); + } + + outerPresenter.setExpandedActionViewsExclusive(true); + if (menu != null) { + menu.addMenuPresenter(outerPresenter, mPopupContext); + menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext); + } else { + outerPresenter.initForMenu(mPopupContext, null); + mExpandedMenuPresenter.initForMenu(mPopupContext, null); + outerPresenter.updateMenuView(true); + mExpandedMenuPresenter.updateMenuView(true); + } + mMenuView.setPopupTheme(mPopupTheme); + mMenuView.setPresenter(outerPresenter); + mOuterActionMenuPresenter = outerPresenter; + } + + /** + * Dismiss all currently showing popup menus, including overflow or submenus. + */ + public void dismissPopupMenus() { + if (mMenuView != null) { + mMenuView.dismissPopupMenus(); + } + } + + /** @hide */ + public boolean isTitleTruncated() { + if (mTitleTextView == null) { + return false; + } + + final Layout titleLayout = mTitleTextView.getLayout(); + if (titleLayout == null) { + return false; + } + + final int lineCount = titleLayout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + if (titleLayout.getEllipsisCount(i) > 0) { + return true; + } + } + return false; + } + + /** + * Set a logo drawable. + * + *

This drawable should generally take the place of title text. The logo cannot be + * clicked. Apps using a logo should also supply a description using + * {@link #setLogoDescription(int)}.

+ * + * @param drawable Drawable to use as a logo + */ + public void setLogo(Drawable drawable) { + if (drawable != null) { + ensureLogoView(); + if (mLogoView.getParent() == null) { + addSystemView(mLogoView); + updateChildVisibilityForExpandedActionView(mLogoView); + } + } else if (mLogoView != null && mLogoView.getParent() != null) { + removeView(mLogoView); + } + if (mLogoView != null) { + mLogoView.setImageDrawable(drawable); + } + } + + /** + * Return the current logo drawable. + * + * @return The current logo drawable + * @see #setLogo(int) + * @see #setLogo(android.graphics.drawable.Drawable) + */ + public Drawable getLogo() { + return mLogoView != null ? mLogoView.getDrawable() : null; + } + + /** + * Set a description of the toolbar's logo. + * + *

This description will be used for accessibility or other similar descriptions + * of the UI.

+ * + * @param resId String resource id + */ + public void setLogoDescription(int resId) { + setLogoDescription(getContext().getText(resId)); + } + + /** + * Set a description of the toolbar's logo. + * + *

This description will be used for accessibility or other similar descriptions + * of the UI.

+ * + * @param description Description to set + */ + public void setLogoDescription(CharSequence description) { + if (!TextUtils.isEmpty(description)) { + ensureLogoView(); + } + if (mLogoView != null) { + mLogoView.setContentDescription(description); + } + } + + /** + * Return the description of the toolbar's logo. + * + * @return A description of the logo + */ + public CharSequence getLogoDescription() { + return mLogoView != null ? mLogoView.getContentDescription() : null; + } + + private void ensureLogoView() { + if (mLogoView == null) { + mLogoView = new ImageView(getContext()); + } + } + + /** + * Check whether this Toolbar is currently hosting an expanded action view. + * + *

An action view may be expanded either directly from the + * {@link android.view.MenuItem MenuItem} it belongs to or by user action. If the Toolbar + * has an expanded action view it can be collapsed using the {@link #collapseActionView()} + * method.

+ * + * @return true if the Toolbar has an expanded action view + */ + public boolean hasExpandedActionView() { + return mExpandedMenuPresenter != null && + mExpandedMenuPresenter.mCurrentExpandedItem != null; + } + + /** + * Collapse a currently expanded action view. If this Toolbar does not have an + * expanded action view this method has no effect. + * + *

An action view may be expanded either directly from the + * {@link android.view.MenuItem MenuItem} it belongs to or by user action.

+ * + * @see #hasExpandedActionView() + */ + public void collapseActionView() { + final MenuItemImpl item = mExpandedMenuPresenter == null ? null : + mExpandedMenuPresenter.mCurrentExpandedItem; + if (item != null) { + item.collapseActionView(); + } + } + + /** + * Returns the title of this toolbar. + * + * @return The current title. + */ + public CharSequence getTitle() { + return mTitleText; + } + + /** + * Set the title of this toolbar. + * + *

A title should be used as the anchor for a section of content. It should + * describe or name the content being viewed.

+ * + * @param resId Resource ID of a string to set as the title + */ + public void setTitle(int resId) { + setTitle(getContext().getText(resId)); + } + + /** + * Set the title of this toolbar. + * + *

A title should be used as the anchor for a section of content. It should + * describe or name the content being viewed.

+ * + * @param title Title to set + */ + public void setTitle(CharSequence title) { + if (!TextUtils.isEmpty(title)) { + if (mTitleTextView == null) { + final Context context = getContext(); + mTitleTextView = new TextView(context); + mTitleTextView.setSingleLine(); + mTitleTextView.setEllipsize(TextUtils.TruncateAt.END); + if (mTitleTextAppearance != 0) { + mTitleTextView.setTextAppearance(context, mTitleTextAppearance); + } + if (mTitleTextColor != 0) { + mTitleTextView.setTextColor(mTitleTextColor); + } + } + if (mTitleTextView.getParent() == null) { + addSystemView(mTitleTextView); + updateChildVisibilityForExpandedActionView(mTitleTextView); + } + } else if (mTitleTextView != null && mTitleTextView.getParent() != null) { + removeView(mTitleTextView); + } + if (mTitleTextView != null) { + mTitleTextView.setText(title); + } + mTitleText = title; + } + + /** + * Return the subtitle of this toolbar. + * + * @return The current subtitle + */ + public CharSequence getSubtitle() { + return mSubtitleText; + } + + /** + * Set the subtitle of this toolbar. + * + *

Subtitles should express extended information about the current content.

+ * + * @param resId String resource ID + */ + public void setSubtitle(int resId) { + setSubtitle(getContext().getText(resId)); + } + + /** + * Set the subtitle of this toolbar. + * + *

Subtitles should express extended information about the current content.

+ * + * @param subtitle Subtitle to set + */ + public void setSubtitle(CharSequence subtitle) { + if (!TextUtils.isEmpty(subtitle)) { + if (mSubtitleTextView == null) { + final Context context = getContext(); + mSubtitleTextView = new TextView(context); + mSubtitleTextView.setSingleLine(); + mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END); + if (mSubtitleTextAppearance != 0) { + mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance); + } + if (mSubtitleTextColor != 0) { + mSubtitleTextView.setTextColor(mSubtitleTextColor); + } + } + if (mSubtitleTextView.getParent() == null) { + addSystemView(mSubtitleTextView); + updateChildVisibilityForExpandedActionView(mSubtitleTextView); + } + } else if (mSubtitleTextView != null && mSubtitleTextView.getParent() != null) { + removeView(mSubtitleTextView); + } + if (mSubtitleTextView != null) { + mSubtitleTextView.setText(subtitle); + } + mSubtitleText = subtitle; + } + + /** + * Sets the text color, size, style, hint color, and highlight color + * from the specified TextAppearance resource. + */ + public void setTitleTextAppearance(Context context, int resId) { + mTitleTextAppearance = resId; + if (mTitleTextView != null) { + mTitleTextView.setTextAppearance(context, resId); + } + } + + /** + * Sets the text color, size, style, hint color, and highlight color + * from the specified TextAppearance resource. + */ + public void setSubtitleTextAppearance(Context context, int resId) { + mSubtitleTextAppearance = resId; + if (mSubtitleTextView != null) { + mSubtitleTextView.setTextAppearance(context, resId); + } + } + + /** + * Sets the text color of the title, if present. + * + * @param color The new text color in 0xAARRGGBB format + */ + public void setTitleTextColor(int color) { + mTitleTextColor = color; + if (mTitleTextView != null) { + mTitleTextView.setTextColor(color); + } + } + + /** + * Sets the text color of the subtitle, if present. + * + * @param color The new text color in 0xAARRGGBB format + */ + public void setSubtitleTextColor(int color) { + mSubtitleTextColor = color; + if (mSubtitleTextView != null) { + mSubtitleTextView.setTextColor(color); + } + } + + /** + * Retrieve the currently configured content description for the navigation button view. + * This will be used to describe the navigation action to users through mechanisms such + * as screen readers or tooltips. + * + * @return The navigation button's content description + */ + @Nullable + public CharSequence getNavigationContentDescription() { + return mNavButtonView != null ? mNavButtonView.getContentDescription() : null; + } + + /** + * Set a content description for the navigation button if one is present. The content + * description will be read via screen readers or other accessibility systems to explain + * the action of the navigation button. + * + * @param resId Resource ID of a content description string to set, or 0 to + * clear the description + */ + public void setNavigationContentDescription(int resId) { + setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null); + } + + /** + * Set a content description for the navigation button if one is present. The content + * description will be read via screen readers or other accessibility systems to explain + * the action of the navigation button. + * + * @param description Content description to set, or null to + * clear the content description + */ + public void setNavigationContentDescription(@Nullable CharSequence description) { + if (!TextUtils.isEmpty(description)) { + ensureNavButtonView(); + } + if (mNavButtonView != null) { + mNavButtonView.setContentDescription(description); + } + } + + /** + * Set the icon to use for the toolbar's navigation button. + * + *

The navigation button appears at the start of the toolbar if present. Setting an icon + * will make the navigation button visible.

+ * + *

If you use a navigation icon you should also set a description for its action using + * {@link #setNavigationContentDescription(int)}. This is used for accessibility and + * tooltips.

+ * + * @param resId Resource ID of a drawable to set + */ + public void setNavigationIcon(int resId) { + setNavigationIcon(mTintManager.getDrawable(resId)); + } + + /** + * Set the icon to use for the toolbar's navigation button. + * + *

The navigation button appears at the start of the toolbar if present. Setting an icon + * will make the navigation button visible.

+ * + *

If you use a navigation icon you should also set a description for its action using + * {@link #setNavigationContentDescription(int)}. This is used for accessibility and + * tooltips.

+ * + * @param icon Drawable to set, may be null to clear the icon + */ + public void setNavigationIcon(@Nullable Drawable icon) { + if (icon != null) { + ensureNavButtonView(); + if (mNavButtonView.getParent() == null) { + addSystemView(mNavButtonView); + updateChildVisibilityForExpandedActionView(mNavButtonView); + } + } else if (mNavButtonView != null && mNavButtonView.getParent() != null) { + removeView(mNavButtonView); + } + if (mNavButtonView != null) { + mNavButtonView.setImageDrawable(icon); + } + } + + /** + * Return the current drawable used as the navigation icon. + * + * @return The navigation icon drawable + */ + @Nullable + public Drawable getNavigationIcon() { + return mNavButtonView != null ? mNavButtonView.getDrawable() : null; + } + + /** + * Set a listener to respond to navigation events. + * + *

This listener will be called whenever the user clicks the navigation button + * at the start of the toolbar. An icon must be set for the navigation button to appear.

+ * + * @param listener Listener to set + * @see #setNavigationIcon(android.graphics.drawable.Drawable) + */ + public void setNavigationOnClickListener(OnClickListener listener) { + ensureNavButtonView(); + mNavButtonView.setOnClickListener(listener); + } + + /** + * Return the Menu shown in the toolbar. + * + *

Applications that wish to populate the toolbar's menu can do so from here. To use + * an XML menu resource, use {@link #inflateMenu(int)}.

+ * + * @return The toolbar's Menu + */ + public Menu getMenu() { + ensureMenu(); + return mMenuView.getMenu(); + } + + private void ensureMenu() { + ensureMenuView(); + if (mMenuView.peekMenu() == null) { + // Initialize a new menu for the first time. + final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu(); + if (mExpandedMenuPresenter == null) { + mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); + } + mMenuView.setExpandedActionViewsExclusive(true); + menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext); + } + } + + private void ensureMenuView() { + if (mMenuView == null) { + mMenuView = new ActionMenuView(getContext()); + mMenuView.setPopupTheme(mPopupTheme); + mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener); + mMenuView.setMenuCallbacks(mActionMenuPresenterCallback, mMenuBuilderCallback); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = GravityCompat.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + mMenuView.setLayoutParams(lp); + addSystemView(mMenuView); + } + } + + private MenuInflater getMenuInflater() { + return new SupportMenuInflater(getContext()); + } + + /** + * Inflate a menu resource into this toolbar. + * + *

Inflate an XML menu resource into this toolbar. Existing items in the menu will not + * be modified or removed.

+ * + * @param resId ID of a menu resource to inflate + */ + public void inflateMenu(int resId) { + getMenuInflater().inflate(resId, getMenu()); + } + + /** + * Set a listener to respond to menu item click events. + * + *

This listener will be invoked whenever a user selects a menu item from + * the action buttons presented at the end of the toolbar or the associated overflow.

+ * + * @param listener Listener to set + */ + public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { + mOnMenuItemClickListener = listener; + } + + /** + * Set the content insets for this toolbar relative to layout direction. + * + *

The content inset affects the valid area for Toolbar content other than + * the navigation button and menu. Insets define the minimum margin for these components + * and can be used to effectively align Toolbar content along well-known gridlines.

+ * + * @param contentInsetStart Content inset for the toolbar starting edge + * @param contentInsetEnd Content inset for the toolbar ending edge + * + * @see #setContentInsetsAbsolute(int, int) + * @see #getContentInsetStart() + * @see #getContentInsetEnd() + * @see #getContentInsetLeft() + * @see #getContentInsetRight() + */ + public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) { + mContentInsets.setRelative(contentInsetStart, contentInsetEnd); + } + + /** + * Get the starting content inset for this toolbar. + * + *

The content inset affects the valid area for Toolbar content other than + * the navigation button and menu. Insets define the minimum margin for these components + * and can be used to effectively align Toolbar content along well-known gridlines.

+ * + * @return The starting content inset for this toolbar + * + * @see #setContentInsetsRelative(int, int) + * @see #setContentInsetsAbsolute(int, int) + * @see #getContentInsetEnd() + * @see #getContentInsetLeft() + * @see #getContentInsetRight() + */ + public int getContentInsetStart() { + return mContentInsets.getStart(); + } + + /** + * Get the ending content inset for this toolbar. + * + *

The content inset affects the valid area for Toolbar content other than + * the navigation button and menu. Insets define the minimum margin for these components + * and can be used to effectively align Toolbar content along well-known gridlines.

+ * + * @return The ending content inset for this toolbar + * + * @see #setContentInsetsRelative(int, int) + * @see #setContentInsetsAbsolute(int, int) + * @see #getContentInsetStart() + * @see #getContentInsetLeft() + * @see #getContentInsetRight() + */ + public int getContentInsetEnd() { + return mContentInsets.getEnd(); + } + + /** + * Set the content insets for this toolbar. + * + *

The content inset affects the valid area for Toolbar content other than + * the navigation button and menu. Insets define the minimum margin for these components + * and can be used to effectively align Toolbar content along well-known gridlines.

+ * + * @param contentInsetLeft Content inset for the toolbar's left edge + * @param contentInsetRight Content inset for the toolbar's right edge + * + * @see #setContentInsetsAbsolute(int, int) + * @see #getContentInsetStart() + * @see #getContentInsetEnd() + * @see #getContentInsetLeft() + * @see #getContentInsetRight() + */ + public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) { + mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight); + } + + /** + * Get the left content inset for this toolbar. + * + *

The content inset affects the valid area for Toolbar content other than + * the navigation button and menu. Insets define the minimum margin for these components + * and can be used to effectively align Toolbar content along well-known gridlines.

+ * + * @return The left content inset for this toolbar + * + * @see #setContentInsetsRelative(int, int) + * @see #setContentInsetsAbsolute(int, int) + * @see #getContentInsetStart() + * @see #getContentInsetEnd() + * @see #getContentInsetRight() + */ + public int getContentInsetLeft() { + return mContentInsets.getLeft(); + } + + /** + * Get the right content inset for this toolbar. + * + *

The content inset affects the valid area for Toolbar content other than + * the navigation button and menu. Insets define the minimum margin for these components + * and can be used to effectively align Toolbar content along well-known gridlines.

+ * + * @return The right content inset for this toolbar + * + * @see #setContentInsetsRelative(int, int) + * @see #setContentInsetsAbsolute(int, int) + * @see #getContentInsetStart() + * @see #getContentInsetEnd() + * @see #getContentInsetLeft() + */ + public int getContentInsetRight() { + return mContentInsets.getRight(); + } + + private void ensureNavButtonView() { + if (mNavButtonView == null) { + mNavButtonView = new ImageButton(getContext(), null, + R.attr.toolbarNavigationButtonStyle); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + mNavButtonView.setLayoutParams(lp); + } + } + + private void ensureCollapseButtonView() { + if (mCollapseButtonView == null) { + mCollapseButtonView = new ImageButton(getContext(), null, + R.attr.toolbarNavigationButtonStyle); + mCollapseButtonView.setImageDrawable(mCollapseIcon); + mCollapseButtonView.setContentDescription(mCollapseDescription); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + lp.mViewType = LayoutParams.EXPANDED; + mCollapseButtonView.setLayoutParams(lp); + mCollapseButtonView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + collapseActionView(); + } + }); + } + } + + private void addSystemView(View v) { + final ViewGroup.LayoutParams vlp = v.getLayoutParams(); + final LayoutParams lp; + if (vlp == null) { + lp = generateDefaultLayoutParams(); + } else if (!checkLayoutParams(vlp)) { + lp = generateLayoutParams(vlp); + } else { + lp = (LayoutParams) vlp; + } + lp.mViewType = LayoutParams.SYSTEM; + addView(v, lp); + } + + @Override + protected Parcelable onSaveInstanceState() { + SavedState state = new SavedState(super.onSaveInstanceState()); + + if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) { + state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId(); + } + + state.isOverflowOpen = isOverflowMenuShowing(); + return state; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + final SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + + final Menu menu = mMenuView != null ? mMenuView.peekMenu() : null; + if (ss.expandedMenuItemId != 0 && mExpandedMenuPresenter != null && menu != null) { + final MenuItem item = menu.findItem(ss.expandedMenuItemId); + if (item != null) { + MenuItemCompat.expandActionView(item); + } + } + + if (ss.isOverflowOpen) { + postShowOverflowMenu(); + } + } + + private void postShowOverflowMenu() { + removeCallbacks(mShowOverflowMenuRunnable); + post(mShowOverflowMenuRunnable); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeCallbacks(mShowOverflowMenuRunnable); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Toolbars always eat touch events, but should still respect the touch event dispatch + // contract. If the normal View implementation doesn't want the events, we'll just silently + // eat the rest of the gesture without reporting the events to the default implementation + // since that's what it expects. + + final int action = MotionEventCompat.getActionMasked(ev); + if (action == MotionEvent.ACTION_DOWN) { + mEatingTouch = false; + } + + if (!mEatingTouch) { + final boolean handled = super.onTouchEvent(ev); + if (action == MotionEvent.ACTION_DOWN && !handled) { + mEatingTouch = true; + } + } + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + mEatingTouch = false; + } + + return true; + } + + @Override + public boolean onHoverEvent(MotionEvent ev) { + // Same deal as onTouchEvent() above. Eat all hover events, but still + // respect the touch event dispatch contract. + + final int action = MotionEventCompat.getActionMasked(ev); + if (action == MotionEvent.ACTION_HOVER_ENTER) { + mEatingHover = false; + } + + if (!mEatingHover) { + final boolean handled = super.onHoverEvent(ev); + if (action == MotionEvent.ACTION_HOVER_ENTER && !handled) { + mEatingHover = true; + } + } + + if (action == MotionEvent.ACTION_HOVER_EXIT || action == MotionEvent.ACTION_CANCEL) { + mEatingHover = false; + } + + return true; + } + + private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed, + int parentHeightSpec, int heightUsed, int heightConstraint) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + int childWidthSpec = getChildMeasureSpec(parentWidthSpec, + getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + + widthUsed, lp.width); + int childHeightSpec = getChildMeasureSpec(parentHeightSpec, + getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + final int childHeightMode = MeasureSpec.getMode(childHeightSpec); + if (childHeightMode != MeasureSpec.EXACTLY && heightConstraint >= 0) { + final int size = childHeightMode != MeasureSpec.UNSPECIFIED ? + Math.min(MeasureSpec.getSize(childHeightSpec), heightConstraint) : + heightConstraint; + childHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + } + child.measure(childWidthSpec, childHeightSpec); + } + + /** + * Returns the width + uncollapsed margins + */ + private int measureChildCollapseMargins(View child, + int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + final int leftDiff = lp.leftMargin - collapsingMargins[0]; + final int rightDiff = lp.rightMargin - collapsingMargins[1]; + final int leftMargin = Math.max(0, leftDiff); + final int rightMargin = Math.max(0, rightDiff); + final int hMargins = leftMargin + rightMargin; + collapsingMargins[0] = Math.max(0, -leftDiff); + collapsingMargins[1] = Math.max(0, -rightDiff); + + final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, + getPaddingLeft() + getPaddingRight() + hMargins + widthUsed, lp.width); + final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + return child.getMeasuredWidth() + hMargins; + } + + /** + * Returns true if the Toolbar is collapsible and has no child views with a measured size > 0. + */ + private boolean shouldCollapse() { + if (!mCollapsible) return false; + + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (shouldLayout(child) && child.getMeasuredWidth() > 0 && + child.getMeasuredHeight() > 0) { + return false; + } + } + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = 0; + int height = 0; + int childState = 0; + + final int[] collapsingMargins = mTempMargins; + final int marginStartIndex; + final int marginEndIndex; + if (ViewUtils.isLayoutRtl(this)) { + marginStartIndex = 1; + marginEndIndex = 0; + } else { + marginStartIndex = 0; + marginEndIndex = 1; + } + + // System views measure first. + + int navWidth = 0; + if (shouldLayout(mNavButtonView)) { + measureChildConstrained(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0, + mMaxButtonHeight); + navWidth = mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView); + height = Math.max(height, mNavButtonView.getMeasuredHeight() + + getVerticalMargins(mNavButtonView)); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mNavButtonView)); + } + + if (shouldLayout(mCollapseButtonView)) { + measureChildConstrained(mCollapseButtonView, widthMeasureSpec, width, + heightMeasureSpec, 0, mMaxButtonHeight); + navWidth = mCollapseButtonView.getMeasuredWidth() + + getHorizontalMargins(mCollapseButtonView); + height = Math.max(height, mCollapseButtonView.getMeasuredHeight() + + getVerticalMargins(mCollapseButtonView)); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mCollapseButtonView)); + } + + final int contentInsetStart = getContentInsetStart(); + width += Math.max(contentInsetStart, navWidth); + collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth); + + int menuWidth = 0; + if (shouldLayout(mMenuView)) { + measureChildConstrained(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0, + mMaxButtonHeight); + menuWidth = mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView); + height = Math.max(height, mMenuView.getMeasuredHeight() + + getVerticalMargins(mMenuView)); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mMenuView)); + } + + final int contentInsetEnd = getContentInsetEnd(); + width += Math.max(contentInsetEnd, menuWidth); + collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth); + + if (shouldLayout(mExpandedActionView)) { + width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); + height = Math.max(height, mExpandedActionView.getMeasuredHeight() + + getVerticalMargins(mExpandedActionView)); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mExpandedActionView)); + } + + if (shouldLayout(mLogoView)) { + width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); + height = Math.max(height, mLogoView.getMeasuredHeight() + + getVerticalMargins(mLogoView)); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mLogoView)); + } + + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.mViewType != LayoutParams.CUSTOM || !shouldLayout(child)) { + // We already got all system views above. Skip them and GONE views. + continue; + } + + width += measureChildCollapseMargins(child, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); + height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child)); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(child)); + } + + int titleWidth = 0; + int titleHeight = 0; + final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom; + final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd; + if (shouldLayout(mTitleTextView)) { + titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec, + width + titleHorizMargins, heightMeasureSpec, titleVertMargins, + collapsingMargins); + titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView); + titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mTitleTextView)); + } + if (shouldLayout(mSubtitleTextView)) { + titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView, + widthMeasureSpec, width + titleHorizMargins, + heightMeasureSpec, titleHeight + titleVertMargins, + collapsingMargins)); + titleHeight += mSubtitleTextView.getMeasuredHeight() + + getVerticalMargins(mSubtitleTextView); + childState = ViewUtils.combineMeasuredStates(childState, + ViewCompat.getMeasuredState(mSubtitleTextView)); + } + + width += titleWidth; + height = Math.max(height, titleHeight); + + // Measurement already took padding into account for available space for the children, + // add it in for the final size. + width += getPaddingLeft() + getPaddingRight(); + height += getPaddingTop() + getPaddingBottom(); + + final int measuredWidth = ViewCompat.resolveSizeAndState( + Math.max(width, getSuggestedMinimumWidth()), + widthMeasureSpec, childState & ViewCompat.MEASURED_STATE_MASK); + final int measuredHeight = ViewCompat.resolveSizeAndState( + Math.max(height, getSuggestedMinimumHeight()), + heightMeasureSpec, childState << ViewCompat.MEASURED_HEIGHT_STATE_SHIFT); + + setMeasuredDimension(measuredWidth, shouldCollapse() ? 0 : measuredHeight); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final boolean isRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL; + final int width = getWidth(); + final int height = getHeight(); + final int paddingLeft = getPaddingLeft(); + final int paddingRight = getPaddingRight(); + final int paddingTop = getPaddingTop(); + final int paddingBottom = getPaddingBottom(); + int left = paddingLeft; + int right = width - paddingRight; + + final int[] collapsingMargins = mTempMargins; + collapsingMargins[0] = collapsingMargins[1] = 0; + + // Align views within the minimum toolbar height, if set. + final int alignmentHeight = getMinimumHeightCompat(); + + if (shouldLayout(mNavButtonView)) { + if (isRtl) { + right = layoutChildRight(mNavButtonView, right, collapsingMargins, + alignmentHeight); + } else { + left = layoutChildLeft(mNavButtonView, left, collapsingMargins, + alignmentHeight); + } + } + + if (shouldLayout(mCollapseButtonView)) { + if (isRtl) { + right = layoutChildRight(mCollapseButtonView, right, collapsingMargins, + alignmentHeight); + } else { + left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins, + alignmentHeight); + } + } + + if (shouldLayout(mMenuView)) { + if (isRtl) { + left = layoutChildLeft(mMenuView, left, collapsingMargins, + alignmentHeight); + } else { + right = layoutChildRight(mMenuView, right, collapsingMargins, + alignmentHeight); + } + } + + collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left); + collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right)); + left = Math.max(left, getContentInsetLeft()); + right = Math.min(right, width - paddingRight - getContentInsetRight()); + + if (shouldLayout(mExpandedActionView)) { + if (isRtl) { + right = layoutChildRight(mExpandedActionView, right, collapsingMargins, + alignmentHeight); + } else { + left = layoutChildLeft(mExpandedActionView, left, collapsingMargins, + alignmentHeight); + } + } + + if (shouldLayout(mLogoView)) { + if (isRtl) { + right = layoutChildRight(mLogoView, right, collapsingMargins, + alignmentHeight); + } else { + left = layoutChildLeft(mLogoView, left, collapsingMargins, + alignmentHeight); + } + } + + final boolean layoutTitle = shouldLayout(mTitleTextView); + final boolean layoutSubtitle = shouldLayout(mSubtitleTextView); + int titleHeight = 0; + if (layoutTitle) { + final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); + titleHeight += lp.topMargin + mTitleTextView.getMeasuredHeight() + lp.bottomMargin; + } + if (layoutSubtitle) { + final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); + titleHeight += lp.topMargin + mSubtitleTextView.getMeasuredHeight() + lp.bottomMargin; + } + + if (layoutTitle || layoutSubtitle) { + int titleTop; + final View topChild = layoutTitle ? mTitleTextView : mSubtitleTextView; + final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView; + final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams(); + final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams(); + final boolean titleHasWidth = layoutTitle && mTitleTextView.getMeasuredWidth() > 0 + || layoutSubtitle && mSubtitleTextView.getMeasuredWidth() > 0; + + switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) { + case Gravity.TOP: + titleTop = getPaddingTop() + toplp.topMargin + mTitleMarginTop; + break; + default: + case Gravity.CENTER_VERTICAL: + final int space = height - paddingTop - paddingBottom; + int spaceAbove = (space - titleHeight) / 2; + if (spaceAbove < toplp.topMargin + mTitleMarginTop) { + spaceAbove = toplp.topMargin + mTitleMarginTop; + } else { + final int spaceBelow = height - paddingBottom - titleHeight - + spaceAbove - paddingTop; + if (spaceBelow < toplp.bottomMargin + mTitleMarginBottom) { + spaceAbove = Math.max(0, spaceAbove - + (bottomlp.bottomMargin + mTitleMarginBottom - spaceBelow)); + } + } + titleTop = paddingTop + spaceAbove; + break; + case Gravity.BOTTOM: + titleTop = height - paddingBottom - bottomlp.bottomMargin - mTitleMarginBottom - + titleHeight; + break; + } + if (isRtl) { + final int rd = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[1]; + right -= Math.max(0, rd); + collapsingMargins[1] = Math.max(0, -rd); + int titleRight = right; + int subtitleRight = right; + + if (layoutTitle) { + final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); + final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth(); + final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); + mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); + titleRight = titleLeft - mTitleMarginEnd; + titleTop = titleBottom + lp.bottomMargin; + } + if (layoutSubtitle) { + final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); + titleTop += lp.topMargin; + final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth(); + final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight(); + mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom); + subtitleRight = subtitleRight - mTitleMarginEnd; + titleTop = subtitleBottom + lp.bottomMargin; + } + if (titleHasWidth) { + right = Math.min(titleRight, subtitleRight); + } + } else { + final int ld = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[0]; + left += Math.max(0, ld); + collapsingMargins[0] = Math.max(0, -ld); + int titleLeft = left; + int subtitleLeft = left; + + if (layoutTitle) { + final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); + final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth(); + final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); + mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); + titleLeft = titleRight + mTitleMarginEnd; + titleTop = titleBottom + lp.bottomMargin; + } + if (layoutSubtitle) { + final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); + titleTop += lp.topMargin; + final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth(); + final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight(); + mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom); + subtitleLeft = subtitleRight + mTitleMarginEnd; + titleTop = subtitleBottom + lp.bottomMargin; + } + if (titleHasWidth) { + left = Math.max(titleLeft, subtitleLeft); + } + } + } + + // Get all remaining children sorted for layout. This is all prepared + // such that absolute layout direction can be used below. + + addCustomViewsWithGravity(mTempViews, Gravity.LEFT); + final int leftViewsCount = mTempViews.size(); + for (int i = 0; i < leftViewsCount; i++) { + left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins, + alignmentHeight); + } + + addCustomViewsWithGravity(mTempViews, Gravity.RIGHT); + final int rightViewsCount = mTempViews.size(); + for (int i = 0; i < rightViewsCount; i++) { + right = layoutChildRight(mTempViews.get(i), right, collapsingMargins, + alignmentHeight); + } + + // Centered views try to center with respect to the whole bar, but views pinned + // to the left or right can push the mass of centered views to one side or the other. + addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL); + final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins); + final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2; + final int halfCenterViewsWidth = centerViewsWidth / 2; + int centerLeft = parentCenter - halfCenterViewsWidth; + final int centerRight = centerLeft + centerViewsWidth; + if (centerLeft < left) { + centerLeft = left; + } else if (centerRight > right) { + centerLeft -= centerRight - right; + } + + final int centerViewsCount = mTempViews.size(); + for (int i = 0; i < centerViewsCount; i++) { + centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins, + alignmentHeight); + } + + mTempViews.clear(); + } + + private int getViewListMeasuredWidth(List views, int[] collapsingMargins) { + int collapseLeft = collapsingMargins[0]; + int collapseRight = collapsingMargins[1]; + int width = 0; + final int count = views.size(); + for (int i = 0; i < count; i++) { + final View v = views.get(i); + final LayoutParams lp = (LayoutParams) v.getLayoutParams(); + final int l = lp.leftMargin - collapseLeft; + final int r = lp.rightMargin - collapseRight; + final int leftMargin = Math.max(0, l); + final int rightMargin = Math.max(0, r); + collapseLeft = Math.max(0, -l); + collapseRight = Math.max(0, -r); + width += leftMargin + v.getMeasuredWidth() + rightMargin; + } + return width; + } + + private int layoutChildLeft(View child, int left, int[] collapsingMargins, + int alignmentHeight) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int l = lp.leftMargin - collapsingMargins[0]; + left += Math.max(0, l); + collapsingMargins[0] = Math.max(0, -l); + final int top = getChildTop(child, alignmentHeight); + final int childWidth = child.getMeasuredWidth(); + child.layout(left, top, left + childWidth, top + child.getMeasuredHeight()); + left += childWidth + lp.rightMargin; + return left; + } + + private int layoutChildRight(View child, int right, int[] collapsingMargins, + int alignmentHeight) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int r = lp.rightMargin - collapsingMargins[1]; + right -= Math.max(0, r); + collapsingMargins[1] = Math.max(0, -r); + final int top = getChildTop(child, alignmentHeight); + final int childWidth = child.getMeasuredWidth(); + child.layout(right - childWidth, top, right, top + child.getMeasuredHeight()); + right -= childWidth + lp.leftMargin; + return right; + } + + private int getChildTop(View child, int alignmentHeight) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int childHeight = child.getMeasuredHeight(); + final int alignmentOffset = alignmentHeight > 0 ? (childHeight - alignmentHeight) / 2 : 0; + switch (getChildVerticalGravity(lp.gravity)) { + case Gravity.TOP: + return getPaddingTop() - alignmentOffset; + + case Gravity.BOTTOM: + return getHeight() - getPaddingBottom() - childHeight + - lp.bottomMargin - alignmentOffset; + + default: + case Gravity.CENTER_VERTICAL: + final int paddingTop = getPaddingTop(); + final int paddingBottom = getPaddingBottom(); + final int height = getHeight(); + final int space = height - paddingTop - paddingBottom; + int spaceAbove = (space - childHeight) / 2; + if (spaceAbove < lp.topMargin) { + spaceAbove = lp.topMargin; + } else { + final int spaceBelow = height - paddingBottom - childHeight - + spaceAbove - paddingTop; + if (spaceBelow < lp.bottomMargin) { + spaceAbove = Math.max(0, spaceAbove - (lp.bottomMargin - spaceBelow)); + } + } + return paddingTop + spaceAbove; + } + } + + private int getChildVerticalGravity(int gravity) { + final int vgrav = gravity & Gravity.VERTICAL_GRAVITY_MASK; + switch (vgrav) { + case Gravity.TOP: + case Gravity.BOTTOM: + case Gravity.CENTER_VERTICAL: + return vgrav; + default: + return mGravity & Gravity.VERTICAL_GRAVITY_MASK; + } + } + + /** + * Prepare a list of non-SYSTEM child views. If the layout direction is RTL + * this will be in reverse child order. + * + * @param views List to populate. It will be cleared before use. + * @param gravity Horizontal gravity to match against + */ + private void addCustomViewsWithGravity(List views, int gravity) { + final boolean isRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL; + final int childCount = getChildCount(); + final int absGrav = GravityCompat.getAbsoluteGravity(gravity, + ViewCompat.getLayoutDirection(this)); + + views.clear(); + + if (isRtl) { + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) && + getChildHorizontalGravity(lp.gravity) == absGrav) { + views.add(child); + } + } + } else { + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) && + getChildHorizontalGravity(lp.gravity) == absGrav) { + views.add(child); + } + } + } + } + + private int getChildHorizontalGravity(int gravity) { + final int ld = ViewCompat.getLayoutDirection(this); + final int absGrav = GravityCompat.getAbsoluteGravity(gravity, ld); + final int hGrav = absGrav & Gravity.HORIZONTAL_GRAVITY_MASK; + switch (hGrav) { + case Gravity.LEFT: + case Gravity.RIGHT: + case Gravity.CENTER_HORIZONTAL: + return hGrav; + default: + return ld == ViewCompat.LAYOUT_DIRECTION_RTL ? Gravity.RIGHT : Gravity.LEFT; + } + } + + private boolean shouldLayout(View view) { + return view != null && view.getParent() == this && view.getVisibility() != GONE; + } + + private int getHorizontalMargins(View v) { + final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams(); + return MarginLayoutParamsCompat.getMarginStart(mlp) + + MarginLayoutParamsCompat.getMarginEnd(mlp); + } + + private int getVerticalMargins(View v) { + final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams(); + return mlp.topMargin + mlp.bottomMargin; + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + if (p instanceof LayoutParams) { + return new LayoutParams((LayoutParams) p); + } else if (p instanceof ActionBar.LayoutParams) { + return new LayoutParams((ActionBar.LayoutParams) p); + } else if (p instanceof MarginLayoutParams) { + return new LayoutParams((MarginLayoutParams) p); + } else { + return new LayoutParams(p); + } + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return super.checkLayoutParams(p) && p instanceof LayoutParams; + } + + private static boolean isCustomView(View child) { + return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM; + } + + /** @hide */ + public DecorToolbar getWrapper() { + if (mWrapper == null) { + mWrapper = new ToolbarWidgetWrapper(this, true); + } + return mWrapper; + } + + private void setChildVisibilityForExpandedActionView(boolean expand) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.mViewType != LayoutParams.EXPANDED && child != mMenuView) { + child.setVisibility(expand ? GONE : VISIBLE); + } + } + } + + private void updateChildVisibilityForExpandedActionView(View child) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.mViewType != LayoutParams.EXPANDED && child != mMenuView) { + child.setVisibility(mExpandedActionView != null ? GONE : VISIBLE); + } + } + + /** + * Force the toolbar to collapse to zero-height during measurement if + * it could be considered "empty" (no visible elements with nonzero measured size) + * @hide + */ + public void setCollapsible(boolean collapsible) { + mCollapsible = collapsible; + requestLayout(); + } + + /** + * Must be called before the menu is accessed + * @hide + */ + public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) { + mActionMenuPresenterCallback = pcb; + mMenuBuilderCallback = mcb; + } + + @Override + public void setMinimumHeight(int minHeight) { + // Update our locally kept value + mMinHeight = minHeight; + + super.setMinimumHeight(minHeight); + } + + private int getMinimumHeightCompat() { + if (Build.VERSION.SDK_INT >= 16) { + // If we're running on API 16 or newer, use the platform method + return ViewCompat.getMinimumHeight(this); + } else { + // Else we'll use our locally kept value + return mMinHeight; + } + } + + /** + * Interface responsible for receiving menu item click events if the items themselves + * do not have individual item click listeners. + */ + public interface OnMenuItemClickListener { + /** + * This method will be invoked when a menu item is clicked if the item itself did + * not already handle the event. + * + * @param item {@link MenuItem} that was clicked + * @return true if the event was handled, false otherwise. + */ + public boolean onMenuItemClick(MenuItem item); + } + + /** + * Layout information for child views of Toolbars. + * + *

Toolbar.LayoutParams extends ActionBar.LayoutParams for compatibility with existing + * ActionBar API. See + * {@link android.support.v7.app.AppCompatActivity#setSupportActionBar(Toolbar) + * ActionBarActivity.setActionBar} + * for more info on how to use a Toolbar as your Activity's ActionBar.

+ */ + public static class LayoutParams extends ActionBar.LayoutParams { + static final int CUSTOM = 0; + static final int SYSTEM = 1; + static final int EXPANDED = 2; + + int mViewType = CUSTOM; + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(int width, int height) { + super(width, height); + this.gravity = Gravity.CENTER_VERTICAL | GravityCompat.START; + } + + public LayoutParams(int width, int height, int gravity) { + super(width, height); + this.gravity = gravity; + } + + public LayoutParams(int gravity) { + this(WRAP_CONTENT, MATCH_PARENT, gravity); + } + + public LayoutParams(LayoutParams source) { + super(source); + + mViewType = source.mViewType; + } + + public LayoutParams(ActionBar.LayoutParams source) { + super(source); + } + + public LayoutParams(MarginLayoutParams source) { + super(source); + // ActionBar.LayoutParams doesn't have a MarginLayoutParams constructor. + // Fake it here and copy over the relevant data. + copyMarginsFromCompat(source); + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + + void copyMarginsFromCompat(MarginLayoutParams source) { + this.leftMargin = source.leftMargin; + this.topMargin = source.topMargin; + this.rightMargin = source.rightMargin; + this.bottomMargin = source.bottomMargin; + } + } + + static class SavedState extends BaseSavedState { + public int expandedMenuItemId; + public boolean isOverflowOpen; + + public SavedState(Parcel source) { + super(source); + expandedMenuItemId = source.readInt(); + isOverflowOpen = source.readInt() != 0; + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(expandedMenuItemId); + out.writeInt(isOverflowOpen ? 1 : 0); + } + + public static final Creator CREATOR = new Creator() { + + @Override + public SavedState createFromParcel(Parcel source) { + return new SavedState(source); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + 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) { + ensureCollapseButtonView(); + if (mCollapseButtonView.getParent() != Toolbar.this) { + addView(mCollapseButtonView); + } + mExpandedActionView = item.getActionView(); + mCurrentExpandedItem = item; + if (mExpandedActionView.getParent() != Toolbar.this) { + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + lp.mViewType = LayoutParams.EXPANDED; + mExpandedActionView.setLayoutParams(lp); + addView(mExpandedActionView); + } + + setChildVisibilityForExpandedActionView(true); + 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(mCollapseButtonView); + mExpandedActionView = null; + + setChildVisibilityForExpandedActionView(false); + 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/eclipse-compile/fab/.classpath b/eclipse-compile/fab/.classpath deleted file mode 100644 index c6e9dc9f1d..0000000000 --- a/eclipse-compile/fab/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/eclipse-compile/fab/.gitignore b/eclipse-compile/fab/.gitignore deleted file mode 100644 index e614fbbef9..0000000000 --- a/eclipse-compile/fab/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bin -gen diff --git a/eclipse-compile/fab/.project b/eclipse-compile/fab/.project deleted file mode 100644 index b53f0578e5..0000000000 --- a/eclipse-compile/fab/.project +++ /dev/null @@ -1,39 +0,0 @@ - - - ShellFab - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - org.eclipse.xtext.ui.shared.xtextBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - org.eclipse.xtext.ui.shared.xtextNature - - diff --git a/eclipse-compile/fab/AndroidManifest.xml b/eclipse-compile/fab/AndroidManifest.xml deleted file mode 100644 index 7e0d38def5..0000000000 --- a/eclipse-compile/fab/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/eclipse-compile/fab/java/com/software/shell/fab/ActionButton.java b/eclipse-compile/fab/java/com/software/shell/fab/ActionButton.java deleted file mode 100644 index 0da0da5d0c..0000000000 --- a/eclipse-compile/fab/java/com/software/shell/fab/ActionButton.java +++ /dev/null @@ -1,1432 +0,0 @@ -/* - * Copyright 2015 Shell Software Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * File created: 2015-01-17 10:39:13 - */ - -package com.software.shell.fab; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.*; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; - -/** - * This class represents a Action Button, which is used in - * Material Design - * - * @author Vladislav - * @version 1.0.3 - * @since 1.0.0 - */ -public class ActionButton extends View { - - /** - * Logging tag - */ - private static final String LOG_TAG = "FAB"; - - /** - * Action Button type - */ - private Type type = Type.DEFAULT; - - /** - * Action Button state - */ - private State state = State.NORMAL; - - /** - * Action Button color in {@link State#NORMAL} state - */ - private int buttonColor = Color.LTGRAY; - - /** - * Action Button color in {@link State#PRESSED} state - */ - private int buttonColorPressed = Color.DKGRAY; - - /** - * Shadow radius expressed in actual pixels - */ - private float shadowRadius = MetricsConverter.dpToPx(getContext(), 2.0f); - - /** - * Shadow X-axis offset expressed in actual pixels - */ - private float shadowXOffset = MetricsConverter.dpToPx(getContext(), 1.0f); - - /** - * Shadow Y-axis offset expressed in actual pixels - */ - private float shadowYOffset = MetricsConverter.dpToPx(getContext(), 1.5f); - - /** - * Shadow color - */ - private int shadowColor = Color.parseColor("#757575"); - - /** - * Stroke width - */ - private float strokeWidth = 0.0f; - - /** - * Stroke color - */ - private int strokeColor = Color.BLACK; - - /** - * Action Button image drawable centered inside the view - */ - private Drawable image; - - /** - * Size of the Action Button image inside the view - */ - private float imageSize = MetricsConverter.dpToPx(getContext(), 24.0f); - - /** - * Animation, which is used while showing Action Button - */ - private Animation showAnimation; - - /** - * Animation, which is used while hiding or dismissing Action Button - */ - private Animation hideAnimation; - - /** - * {@link android.graphics.Paint}, which is used for drawing the elements of - * Action Button - */ - protected final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - - /** - * Creates an instance of the Action Button - *

- * Used when instantiating Action Button programmatically - * - * @param context context the view is running in - */ - public ActionButton(Context context) { - super(context); - initActionButton(); - } - - /** - * Creates an instance of the Action Button - *

- * Used when inflating the declared Action Button - * within XML resource - * - * @param context context the view is running in - * @param attrs attributes of the XML tag that is inflating the view - */ - public ActionButton(Context context, AttributeSet attrs) { - super(context, attrs); - initActionButton(context, attrs, 0, 0); - } - - /** - * Creates an instance of the Action Button - *

- * Used when inflating the declared Action Button - * within XML resource - * - * @param context context the view is running in - * @param attrs attributes of the XML tag that is inflating the view - * @param defStyleAttr attribute in the current theme that contains a - * reference to a style resource that supplies default values for - * the view. Can be 0 to not look for defaults - */ - public ActionButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initActionButton(context, attrs, defStyleAttr, 0); - } - - /** - * Creates an instance of the Action Button - *

- * Used when inflating the declared Action Button - * within XML resource - *

- * Might be called if target API is LOLLIPOP (21) and higher - * - * @param context context the view is running in - * @param attrs attributes of the XML tag that is inflating the view - * @param defStyleAttr attribute in the current theme that contains a - * reference to a style resource that supplies default values for - * the view. Can be 0 to not look for defaults - * @param defStyleRes resource identifier of a style resource that - * supplies default values for the view, used only if - * defStyleAttr is 0 or can not be found in the theme. Can be 0 - * to not look for defaults - */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public ActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initActionButton(context, attrs, defStyleAttr, defStyleRes); - } - - /** - * Initializes the Action Button, which is created programmatically - */ - private void initActionButton() { - initLayerType(); - Log.v(LOG_TAG, "Action Button initialized"); - } - - /** - * Initializes the Action Button, which is declared within XML resource - *

- * Makes calls to different initialization methods for parameters initialization. - * For those parameters, which are not declared in the XML resource, - * the default value will be used - * - * @param context context the view is running in - * @param attrs attributes of the XML tag that is inflating the view - * @param defStyleAttr attribute in the current theme that contains a - * reference to a style resource that supplies default values for - * the view. Can be 0 to not look for defaults - * @param defStyleRes resource identifier of a style resource that - * supplies default values for the view, used only if - * defStyleAttr is 0 or can not be found in the theme. Can be 0 - * to not look for defaults - */ - private void initActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - initLayerType(); - TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ActionButton, - defStyleAttr, defStyleRes); - try { - initType(attributes); - initButtonColor(attributes); - initButtonColorPressed(attributes); - initShadowRadius(attributes); - initShadowXOffset(attributes); - initShadowYOffset(attributes); - initShadowColor(attributes); - initStrokeWidth(attributes); - initStrokeColor(attributes); - initImage(attributes); - initImageSize(attributes); - initShowAnimation(attributes); - initHideAnimation(attributes); - } catch (Exception e) { - Log.e(LOG_TAG, "Unable to read attr", e); - } finally { - attributes.recycle(); - } - Log.v(LOG_TAG, "Action Button initialized"); - } - - /** - * Initializes the layer type needed for shadows drawing - *

- * Might be called if target API is HONEYCOMB (11) and higher - */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - private void initLayerType() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - setLayerType(LAYER_TYPE_SOFTWARE, paint); - Log.v(LOG_TAG, "Layer type initialized"); - } - } - - /** - * Initializes the {@link Type} of Action Button - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initType(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_type)) { - final int id = attrs.getInteger(R.styleable.ActionButton_type, type.getId()); - type = Type.forId(id); - Log.v(LOG_TAG, "Initialized type: " + getType()); - } - } - - /** - * Initializes the Action Button color for - * {@link #state} set to {@link State#NORMAL} - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initButtonColor(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_button_color)) { - buttonColor = attrs.getColor(R.styleable.ActionButton_button_color, buttonColor); - Log.v(LOG_TAG, "Initialized button color: " + getButtonColor()); - } - } - - /** - * Initializes the Action Button color for - * {@link #state} set to {@link State#PRESSED} - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initButtonColorPressed(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_button_colorPressed)) { - buttonColorPressed = attrs.getColor(R.styleable.ActionButton_button_colorPressed, - buttonColorPressed); - Log.v(LOG_TAG, "Initialized button color pressed: " + getButtonColorPressed()); - } - } - - /** - * Initializes the shadow radius - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initShadowRadius(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_shadow_radius)) { - shadowRadius = attrs.getDimension(R.styleable.ActionButton_shadow_radius, shadowRadius); - Log.v(LOG_TAG, "Initialized shadow radius: " + getShadowRadius()); - } - } - - /** - * Initializes the shadow X-axis offset - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initShadowXOffset(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_shadow_xOffset)) { - shadowXOffset = attrs.getDimension(R.styleable.ActionButton_shadow_xOffset, shadowXOffset); - Log.v(LOG_TAG, "Initialized shadow X-axis offset: " + getShadowXOffset()); - } - } - - /** - * Initializes the shadow Y-axis offset - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initShadowYOffset(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_shadow_yOffset)) { - shadowYOffset = attrs.getDimension(R.styleable.ActionButton_shadow_yOffset, shadowYOffset); - Log.v(LOG_TAG, "Initialized shadow Y-axis offset: " + getShadowYOffset()); - } - } - - /** - * Initializes the shadow color - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initShadowColor(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_shadow_color)) { - shadowColor = attrs.getColor(R.styleable.ActionButton_shadow_color, shadowColor); - Log.v(LOG_TAG, "Initialized shadow color: " + getShadowColor()); - } - } - - /** - * Initializes the stroke width - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initStrokeWidth(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_stroke_width)) { - strokeWidth = attrs.getDimension(R.styleable.ActionButton_stroke_width, strokeWidth); - Log.v(LOG_TAG, "Initialized stroke width: " + getStrokeWidth()); - } - } - - /** - * Initializes the stroke color - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initStrokeColor(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_stroke_color)) { - strokeColor = attrs.getColor(R.styleable.ActionButton_stroke_color, strokeColor); - Log.v(LOG_TAG, "Initialized stroke color: " + getStrokeColor()); - } - } - - /** - * Initializes the animation, which is used while showing - * Action Button - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initShowAnimation(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_show_animation)) { - final int animResId = attrs.getResourceId(R.styleable.ActionButton_show_animation, - Animations.NONE.animResId); - showAnimation = Animations.load(getContext(), animResId); - Log.v(LOG_TAG, "Initialized animation on show"); - } - } - - /** - * Initializes the animation, which is used while hiding or dismissing - * Action Button - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initHideAnimation(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_hide_animation)) { - final int animResId = attrs.getResourceId(R.styleable.ActionButton_hide_animation, - Animations.NONE.animResId); - hideAnimation = Animations.load(getContext(), animResId); - Log.v(LOG_TAG, "Initialized animation on hide"); - } - } - - /** - * Initializes the image inside Action Button - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initImage(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_image)) { - image = attrs.getDrawable(R.styleable.ActionButton_image); - Log.v(LOG_TAG, "Initialized image"); - } - } - - /** - * Initializes the image size inside Action Button - *

- * Changing the default size of the image breaks the rules of - * Material Design - * - * @param attrs attributes of the XML tag that is inflating the view - */ - private void initImageSize(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_image_size)) { - imageSize = attrs.getDimension(R.styleable.ActionButton_image_size, imageSize); - Log.v(LOG_TAG, "Initialized image size: " + getImageSize()); - } - } - - /** - * Plays the {@link #showAnimation} if set - */ - public void playShowAnimation() { - startAnimation(getShowAnimation()); - } - - /** - * Plays the {@link #hideAnimation} if set - */ - public void playHideAnimation() { - startAnimation(getHideAnimation()); - } - - /** - * Makes the Action Button to appear and - * sets its visibility to {@link #VISIBLE} - *

- * {@link #showAnimation} is played if set - */ - public void show() { - if (isHidden()) { - playShowAnimation(); - setVisibility(VISIBLE); - Log.v(LOG_TAG, "Action Button shown"); - } - } - - /** - * Makes the Action Button to disappear and - * sets its visibility to {@link #INVISIBLE} - *

- * {@link #hideAnimation} is played if set - */ - public void hide() { - if (!isHidden() && !isDismissed()) { - playHideAnimation(); - setVisibility(INVISIBLE); - Log.v(LOG_TAG, "Action Button hidden"); - } - } - - /** - * Completely dismisses the Action Button, - * sets its visibility to {@link #GONE} and removes it from the parent view - *

- * After calling this method any calls to {@link #show()} won't result in showing - * the Action Button so far as it is removed from the parent View - *

- * {@link #hideAnimation} is played if set - */ - public void dismiss() { - if (!isDismissed()) { - if (!isHidden()) { - playHideAnimation(); - } - setVisibility(GONE); - ViewGroup parent = (ViewGroup) getParent(); - parent.removeView(this); - Log.v(LOG_TAG, "Action Button dismissed"); - } - } - - /** - * Checks whether Action Button is hidden - * - * @return true if Action Button is hidden, otherwise false - */ - public boolean isHidden() { - return getVisibility() == INVISIBLE; - } - - /** - * Checks whether Action Button is dismissed - * - * @return true if Action Button is dismissed, otherwise false - */ - public boolean isDismissed() { - ViewGroup parent = (ViewGroup) getParent(); - return parent == null; - } - - /** - * Returns the size of the Action Button in actual pixels (px). - * Size of the Action Button is the diameter of the main circle - * - * @return size of the Action Button in actual pixels (px) - */ - public int getButtonSize() { - final int buttonSize = (int) type.getSize(getContext()); - Log.v(LOG_TAG, "Button size is: " + buttonSize); - return buttonSize; - } - - /** - * Returns the type of the Action Button - * - * @return type of the Action Button - */ - public Type getType() { - return type; - } - - /** - * Sets the type of the Action Button and - * invalidates the layout of the view - * - * @param type type of the Action Button - */ - public void setType(Type type) { - this.type = type; - requestLayout(); - Log.v(LOG_TAG, "Type changed to: " + getType()); - } - - /** - * Returns the current state of the Action Button - * - * @return current state of the Action Button - */ - public State getState() { - return state; - } - - /** - * Sets the current state of the Action Button and - * invalidates the view - * - * @param state new state of the Action Button - */ - public void setState(State state) { - this.state = state; - invalidate(); - Log.v(LOG_TAG, "State changed to: " + getState()); - } - - /** - * Returns the Action Button color when in - * {@link State#NORMAL} state - * - * @return Action Button color when in - * {@link State#NORMAL} state - */ - public int getButtonColor() { - return buttonColor; - } - - /** - * Sets the Action Button color when in - * {@link State#NORMAL} state and invalidates the view - * - * @param buttonColor Action Button color - * when in {@link State#NORMAL} state - */ - public void setButtonColor(int buttonColor) { - this.buttonColor = buttonColor; - invalidate(); - Log.v(LOG_TAG, "Color changed to: " + getButtonColor()); - } - - /** - * Sets the Action Button color when in - * {@link State#PRESSED} state - * - * @return Action Button color when in - * {@link State#PRESSED} state - */ - public int getButtonColorPressed() { - return buttonColorPressed; - } - - /** - * Sets the Action Button color when in - * {@link State#PRESSED} state and invalidates the view - * - * @param buttonColorPressed Action Button color - * when in {@link State#PRESSED} state - */ - public void setButtonColorPressed(int buttonColorPressed) { - this.buttonColorPressed = buttonColorPressed; - invalidate(); - Log.v(LOG_TAG, "Pressed color changed to: " + getButtonColorPressed()); - } - - /** - * Checks whether Action Button has shadow by determining shadow radius - *

- * Shadow is disabled if elevation is set API level is {@code 21 Lollipop} and higher - * - * @return true if Action Button has radius, otherwise false - */ - public boolean hasShadow() { - return !hasElevation() && getShadowRadius() > 0.0f; - } - - /** - * Returns the Action Button shadow radius in actual - * pixels (px) - * - * @return Action Button shadow radius in actual pixels (px) - */ - public float getShadowRadius() { - return shadowRadius; - } - - /** - * Sets the Action Button shadow radius and - * invalidates the layout of the view - *

- * Must be specified in density-independent (dp) pixels, which are - * then converted into actual pixels (px). If shadow radius is set to 0, - * shadow is removed - * - * @param shadowRadius shadow radius specified in density-independent - * (dp) pixels - */ - public void setShadowRadius(float shadowRadius) { - this.shadowRadius = MetricsConverter.dpToPx(getContext(), shadowRadius); - requestLayout(); - Log.v(LOG_TAG, "Shadow radius changed to:" + getShadowRadius()); - } - - /** - * Removes the Action Button shadow by setting its radius to 0 - */ - public void removeShadow() { - if (hasShadow()) { - setShadowRadius(0.0f); - } - } - - /** - * Returns the Action Button shadow X-axis offset - * in actual pixels (px) - *

- * If X-axis offset is greater than 0 shadow is shifted right. - * If X-axis offset is lesser than 0 shadow is shifted left. - * 0 X-axis offset means that shadow is not X-axis shifted at all - * - * @return Action Button shadow X-axis offset - * in actual pixels (px) - */ - public float getShadowXOffset() { - return shadowXOffset; - } - - /** - * Sets the Action Button shadow X-axis offset and - * invalidates the layout of the view - *

- * If X-axis offset is greater than 0 shadow is shifted right. - * If X-axis offset is lesser than 0 shadow is shifted left. - * 0 X-axis offset means that shadow is not shifted at all - *

- * Must be specified in density-independent (dp) pixels, which are - * then converted into actual pixels (px) - * - * @param shadowXOffset shadow X-axis offset specified in density-independent - * (dp) pixels - */ - public void setShadowXOffset(float shadowXOffset) { - this.shadowXOffset = MetricsConverter.dpToPx(getContext(), shadowXOffset); - requestLayout(); - Log.v(LOG_TAG, "Shadow X offset changed to: " + getShadowXOffset()); - } - - /** - * Returns the Action Button shadow Y-axis offset - * in actual pixels (px) - *

- * If Y-axis offset is greater than 0 shadow is shifted down. - * If Y-axis offset is lesser than 0 shadow is shifted up. - * 0 Y-axis offset means that shadow is not Y-axis shifted at all - * - * @return Action Button shadow Y-axis offset - * in actual pixels (px) - */ - public float getShadowYOffset() { - return shadowYOffset; - } - - /** - * Sets the Action Button shadow Y-axis offset and - * invalidates the layout of the view - *

- * If Y-axis offset is greater than 0 shadow is shifted down. - * If Y-axis offset is lesser than 0 shadow is shifted up. - * 0 Y-axis offset means that shadow is not Y-axis shifted at all - *

- * Must be specified in density-independent (dp) pixels, which are - * then converted into actual pixels (px) - * - * @param shadowYOffset shadow Y-axis offset specified in density-independent - * (dp) pixels - */ - public void setShadowYOffset(float shadowYOffset) { - this.shadowYOffset = MetricsConverter.dpToPx(getContext(), shadowYOffset); - requestLayout(); - Log.v(LOG_TAG, "Shadow Y offset changed to:" + getShadowYOffset()); - } - - /** - * Returns Action Button shadow color - * - * @return Action Button shadow color - */ - public int getShadowColor() { - return shadowColor; - } - - /** - * Sets the Action Button shadow color and - * invalidates the view - * - * @param shadowColor Action Button color - */ - public void setShadowColor(int shadowColor) { - this.shadowColor = shadowColor; - invalidate(); - Log.v(LOG_TAG, "Shadow color changed to: " + getShadowColor()); - } - - /** - * Returns the Action Button stroke width in actual - * pixels (px) - * - * @return Action Button stroke width in actual - * pixels (px) - */ - public float getStrokeWidth() { - return strokeWidth; - } - - /** - * Checks whether Action Button has stroke by checking - * stroke width - * - * @return true if Action Button has stroke, otherwise false - */ - public boolean hasStroke() { - return getStrokeWidth() > 0.0f; - } - - /** - * Sets the Action Button stroke width and - * invalidates the layout of the view - *

- * Stroke width value must be greater than 0. If stroke width is - * set to 0 stroke is removed - *

- * Must be specified in density-independent (dp) pixels, which are - * then converted into actual pixels (px) - * - * @param strokeWidth stroke width specified in density-independent - * (dp) pixels - */ - public void setStrokeWidth(float strokeWidth) { - this.strokeWidth = MetricsConverter.dpToPx(getContext(), strokeWidth); - requestLayout(); - Log.v(LOG_TAG, "Stroke width changed to: " + getStrokeWidth()); - } - - /** - * Removes the Action Button stroke by setting its width to 0 - */ - public void removeStroke() { - if (hasStroke()) { - setStrokeWidth(0.0f); - } - } - - /** - * Returns the Action Button stroke color - * - * @return Action Button stroke color - */ - public int getStrokeColor() { - return strokeColor; - } - - /** - * Sets the Action Button stroke color and - * invalidates the view - * - * @param strokeColor Action Button stroke color - */ - public void setStrokeColor(int strokeColor) { - this.strokeColor = strokeColor; - invalidate(); - Log.v(LOG_TAG, "Stroke color changed to: " + getStrokeColor()); - } - - /** - * Returns the Action Button image drawable centered - * inside the view - * - * @return Action Button image drawable centered - * inside the view - */ - public Drawable getImage() { - return image; - } - - /** - * Checks whether Action Button has an image centered - * inside the view - * - * @return true if Action Button has an image centered - * inside the view, otherwise false - */ - public boolean hasImage() { - return getImage() != null; - } - - /** - * Places the image drawable centered inside the view and - * invalidates the view - *

- * Size of the image while drawing is fit to {@link #imageSize} - * - * @param image image drawable, which will be placed centered - * inside the view - */ - public void setImageDrawable(Drawable image) { - this.image = image; - invalidate(); - Log.v(LOG_TAG, "Image drawable set"); - } - - /** - * Resolves the drawable resource id and places the resolved image drawable - * centered inside the view - * - * @param resId drawable resource id, which is to be resolved to - * image drawable and used as parameter when calling - * {@link #setImageDrawable(android.graphics.drawable.Drawable)} - */ - public void setImageResource(int resId) { - setImageDrawable(getResources().getDrawable(resId)); - } - - /** - * Creates the {@link android.graphics.drawable.BitmapDrawable} from the given - * {@link android.graphics.Bitmap} and places it centered inside the view - * - * @param bitmap bitmap, from which {@link android.graphics.drawable.BitmapDrawable} - * is created and used as parameter when calling - * {@link #setImageDrawable(android.graphics.drawable.Drawable)} - */ - public void setImageBitmap(Bitmap bitmap) { - setImageDrawable(new BitmapDrawable(getResources(), bitmap)); - } - - /** - * Removes the Action Button image by setting its value to null - */ - public void removeImage() { - if (hasImage()) { - setImageDrawable(null); - } - } - - /** - * Returns the Action Button image size in actual pixels (px). - * If Action Button image is not set returns 0 - * - * @return Action Button image size in actual pixels (px), - * 0 if image is not set - */ - public float getImageSize() { - return getImage() != null ? imageSize : 0.0f; - } - - /** - * Sets the size of the Action Button image - *

- * Changing the default size of the image breaks the rules of - * Material Design - *

- * Must be specified in density-independent (dp) pixels, which are - * then converted into actual pixels (px) - * - * @param size size of the Action Button image - * specified in density-independent (dp) pixels - */ - public void setImageSize(float size) { - this.imageSize = MetricsConverter.dpToPx(getContext(), size); - Log.v(LOG_TAG, "Image size changed to: " + getImageSize()); - } - - /** - * Returns an animation, which is used while showing Action Button - * - * @return animation, which is used while showing Action Button - */ - public Animation getShowAnimation() { - return showAnimation; - } - - /** - * Sets the animation, which is used while showing Action Button - * - * @param animation animation, which is to be used while showing - * Action Button - */ - public void setShowAnimation(Animation animation) { - this.showAnimation = animation; - Log.v(LOG_TAG, "Show animation set"); - } - - /** - * Sets one of the {@link Animations} as animation, which is used while showing - * Action Button - * - * @param animation one of the {@link Animations}, which is to be used while - * showing Action Button - */ - public void setShowAnimation(Animations animation) { - setShowAnimation(Animations.load(getContext(), animation.animResId)); - } - - /** - * Removes the animation, which is used while showing Action Button - */ - public void removeShowAnimation() { - setShowAnimation(Animations.NONE); - Log.v(LOG_TAG, "Show animation removed"); - } - - /** - * Returns an animation, which is used while hiding Action Button - * - * @return animation, which is used while hiding Action Button - */ - public Animation getHideAnimation() { - return hideAnimation; - } - - /** - * Sets the animation, which is used while hiding Action Button - * - * @param animation animation, which is to be used while hiding - * Action Button - */ - public void setHideAnimation(Animation animation) { - this.hideAnimation = animation; - Log.v(LOG_TAG, "Hide animation set"); - } - - /** - * Sets one of the {@link Animations} as animation, which is used while hiding - * Action Button - * - * @param animation one of the {@link Animations}, which is to be used while - * hiding Action Button - */ - public void setHideAnimation(Animations animation) { - setHideAnimation(Animations.load(getContext(), animation.animResId)); - } - - public void removeHideAnimation() { - setHideAnimation(Animations.NONE); - Log.v(LOG_TAG, "Hide animation removed"); - } - - /** - * Adds additional actions on motion events: - * 1. Changes the Action Button {@link #state} to {@link State#PRESSED} - * on {@link android.view.MotionEvent#ACTION_DOWN} - * 2. Changes the Action Button {@link #state} to {@link State#NORMAL} - * on {@link android.view.MotionEvent#ACTION_UP} - * - * @param event motion event - * @return true if event was handled, otherwise false - */ - @SuppressWarnings("all") - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent event) { - super.onTouchEvent(event); - final int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_DOWN: - Log.v(LOG_TAG, "Motion event action down detected"); - setState(State.PRESSED); - return true; - case MotionEvent.ACTION_UP: - Log.v(LOG_TAG, "Motion event action up detected"); - setState(State.NORMAL); - return true; - default: - Log.v(LOG_TAG, "Unrecognized motion event detected"); - return false; - } - } - - /** - * Adds additional checking whether animation is null before starting to play it - * - * @param animation animation to play - */ - @SuppressWarnings("all") - @Override - public void startAnimation(Animation animation) { - if (animation != null) { - super.startAnimation(animation); - } - } - - /** - * Resets the paint to its default values and sets initial flags to it - *

- * Use this method before drawing the new element of the view - */ - protected final void resetPaint() { - paint.reset(); - paint.setFlags(Paint.ANTI_ALIAS_FLAG); - Log.v(LOG_TAG, "Paint reset"); - } - - /** - * Draws the elements of the Action Button - * - * @param canvas canvas, on which the drawing is to be performed - */ - @SuppressWarnings("all") - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - Log.v(LOG_TAG, "Action Button onDraw called"); - drawCircle(canvas); - if (hasElevation()) { - drawElevation(); - } - if (hasStroke()) { - drawStroke(canvas); - } - if (hasImage()) { - drawImage(canvas); - } - } - - /** - * Draws the main circle of the Action Button and calls - * {@link #drawShadow()} to draw the shadow if present - * - * @param canvas canvas, on which circle is to be drawn - */ - protected void drawCircle(Canvas canvas) { - resetPaint(); - if (hasShadow()) { - drawShadow(); - } - paint.setStyle(Paint.Style.FILL); - paint.setColor(getState() == State.PRESSED ? getButtonColorPressed() : getButtonColor()); - canvas.drawCircle(calculateCenterX(), calculateCenterY(), calculateCircleRadius(), paint); - Log.v(LOG_TAG, "Circle drawn"); - } - - /** - * Calculates the X-axis center coordinate of the entire view - * - * @return X-axis center coordinate of the entire view - */ - protected float calculateCenterX() { - final float centerX = getMeasuredWidth() / 2; - Log.v(LOG_TAG, "Calculated center X = " + centerX); - return centerX; - } - - /** - * Calculates the Y-axis center coordinate of the entire view - * - * @return Y-axis center coordinate of the entire view - */ - protected float calculateCenterY() { - final float centerY = getMeasuredHeight() / 2; - Log.v(LOG_TAG, "Calculated center Y = " + centerY); - return centerY; - } - - /** - * Calculates the radius of the main circle - * - * @return radius of the main circle - */ - protected final float calculateCircleRadius() { - final float circleRadius = getButtonSize() / 2; - Log.v(LOG_TAG, "Calculated circle circleRadius = " + circleRadius); - return circleRadius; - } - - /** - * Draws the shadow if view elevation is not enabled - */ - protected void drawShadow() { - paint.setShadowLayer(getShadowRadius(), getShadowXOffset(), getShadowYOffset(), getShadowColor()); - Log.v(LOG_TAG, "Shadow drawn"); - } - - /** - * Draws the elevation around the main circle - *

- * Uses the stroke corrective, which helps to avoid the elevation overlapping issue - */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - protected void drawElevation() { - final int strokeWeightCorrective = (int) (getStrokeWidth() / 1.5f); - final int width = getWidth() - strokeWeightCorrective; - final int height = getHeight() - strokeWeightCorrective; - final ViewOutlineProvider outlineProvider = new ActionButtonOutlineProvider(width, height); - setOutlineProvider(outlineProvider); - Log.v(LOG_TAG, "Elevation drawn"); - } - - /** - * Checks whether view elevation is enabled - * - * @return true if view elevation enabled, otherwise false - */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private boolean hasElevation() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getElevation() > 0.0f; - } - - /** - * Draws stroke around the main circle - * - * @param canvas canvas, on which circle is to be drawn - */ - protected void drawStroke(Canvas canvas) { - resetPaint(); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(getStrokeWidth()); - paint.setColor(getStrokeColor()); - canvas.drawCircle(calculateCenterX(), calculateCenterY(), calculateCircleRadius(), paint); - Log.v(LOG_TAG, "Stroke drawn"); - } - - /** - * Draws the image centered inside the view - * - * @param canvas canvas, on which circle is to be drawn - */ - protected void drawImage(Canvas canvas) { - final int startPointX = (int) (calculateCenterX() - getImageSize() / 2); - final int startPointY = (int) (calculateCenterY() - getImageSize() / 2); - final int endPointX = (int) (startPointX + getImageSize()); - final int endPointY = (int) (startPointY + getImageSize()); - getImage().setBounds(startPointX, startPointY, endPointX, endPointY); - getImage().draw(canvas); - Log.v(LOG_TAG, String.format("Image drawn on canvas with coordinates: startPointX = %s, startPointY = %s, " + - "endPointX = %s, endPointY = %s", startPointX, startPointY, endPointX, endPointY)); - } - - /** - * Sets the measured dimension for the entire view - * - * @param widthMeasureSpec horizontal space requirements as imposed by the parent. - * The requirements are encoded with - * {@link android.view.View.MeasureSpec} - * @param heightMeasureSpec vertical space requirements as imposed by the parent. - * The requirements are encoded with - * {@link android.view.View.MeasureSpec} - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - Log.v(LOG_TAG, "Action Button onMeasure called"); - setMeasuredDimension(calculateMeasuredWidth(), calculateMeasuredHeight()); - Log.v(LOG_TAG, String.format("View size measured with: height = %s, width = %s", getHeight(), getWidth())); - } - - /** - * Calculates the measured width in actual pixels for the entire view - * - * @return measured width in actual pixels for the entire view - */ - private int calculateMeasuredWidth() { - final int measuredWidth = getButtonSize() + calculateShadowWidth() + calculateStrokeWeight(); - Log.v(LOG_TAG, "Calculated measured width = " + measuredWidth); - return measuredWidth; - } - - /** - * Calculates the measured height in actual pixels for the entire view - * - * @return measured width in actual pixels for the entire view - */ - private int calculateMeasuredHeight() { - final int measuredHeight = getButtonSize() + calculateShadowHeight() + calculateStrokeWeight(); - Log.v(LOG_TAG, "Calculated measured height = " + measuredHeight); - return measuredHeight; - } - - /** - * Calculates shadow width in actual pixels - * - * @return shadow width in actual pixels - */ - private int calculateShadowWidth() { - final int shadowWidth = hasShadow() ? (int) ((getShadowRadius() + Math.abs(getShadowXOffset())) * 2) : 0; - Log.v(LOG_TAG, "Calculated shadow width = " + shadowWidth); - return shadowWidth; - } - - /** - * Calculates shadow height in actual pixels - * - * @return shadow height in actual pixels - */ - private int calculateShadowHeight() { - final int shadowHeight = hasShadow() ? (int) ((getShadowRadius() + Math.abs(getShadowYOffset())) * 2) : 0; - Log.v(LOG_TAG, "Calculated shadow height = " + shadowHeight); - return shadowHeight; - } - - /** - * Calculates the stroke weight in actual pixels - * * - * @return stroke weight in actual pixels - */ - private int calculateStrokeWeight() { - final int strokeWeight = (int) (getStrokeWidth() * 2.0f); - Log.v(LOG_TAG, "Calculated stroke weight is: " + strokeWeight); - return strokeWeight; - } - - /** - * Determines the Action Button types - */ - public enum Type { - - /** - * Action Button default (56dp) type - */ - DEFAULT { - @Override - int getId() { - return 0; - } - - @Override - float getSize(Context context) { - return MetricsConverter.dpToPx(context, 56.0f); - } - }, - - /** - * Action Button mini (40dp) type - */ - MINI { - @Override - int getId() { - return 1; - } - - @Override - float getSize(Context context) { - return MetricsConverter.dpToPx(context, 40.0f); - } - }; - - /** - * Returns an {@code id} for specific Action Button - * type, which is defined in attributes - * - * @return {@code id} for particular Action Button type, - * which is defined in attributes - */ - abstract int getId(); - - /** - * Returns the size of the specific type of the Action Button - * - * @param context context the view is running in - * @return size of the particular type of the Action Button - */ - abstract float getSize(Context context); - - /** - * Returns the Action Button type for a specific {@code id} - * - * @param id an {@code id}, for which Action Button type required - * @return Action Button type - */ - static Type forId(int id) { - for (Type type : values()) { - if (type.getId() == id) { - return type; - } - } - return DEFAULT; - } - - } - - /** - * Determines the Action Button states - */ - public enum State { - - /** - * Action Button normal state - */ - NORMAL, - - /** - * Action Button pressed state - */ - PRESSED - - } - - public enum Animations { - - /** - * None. Animation absent - */ - NONE (0), - - /** - * Fade in animation - */ - FADE_IN (R.anim.fab_fade_in), - - /** - * Fade out animation - */ - FADE_OUT (R.anim.fab_fade_out), - - /** - * Scale up animation - */ - SCALE_UP (R.anim.fab_scale_up), - - /** - * Scale down animation - */ - SCALE_DOWN (R.anim.fab_scale_down), - - /** - * Roll from down animation - */ - ROLL_FROM_DOWN (R.anim.fab_roll_from_down), - - /** - * Roll to down animation - */ - ROLL_TO_DOWN (R.anim.fab_roll_to_down), - - /** - * Roll from right animation - */ - ROLL_FROM_RIGHT (R.anim.fab_roll_from_right), - - /** - * Roll to right animation - */ - ROLL_TO_RIGHT (R.anim.fab_roll_to_right), - - /** - * Jump from down animation - */ - JUMP_FROM_DOWN (R.anim.fab_jump_from_down), - - /** - * Jump to down animation - */ - JUMP_TO_DOWN (R.anim.fab_jump_to_down), - - /** - * Jump from right animation - */ - JUMP_FROM_RIGHT (R.anim.fab_jump_from_right), - - /** - * Jump to right animation - */ - JUMP_TO_RIGHT (R.anim.fab_jump_to_right); - - /** - * Correspondent animation resource id - */ - final int animResId; - - private Animations(int animResId) { - this.animResId = animResId; - } - - /** - * Loads an animation from animation resource id - * - * @param context context the view is running in - * @param animResId resource id of the animation, which is to be loaded - * @return loaded animation - */ - protected static Animation load(Context context, int animResId) { - return animResId == NONE.animResId ? null : AnimationUtils.loadAnimation(context, animResId); - } - - } - -} diff --git a/eclipse-compile/fab/java/com/software/shell/fab/ActionButtonOutlineProvider.java b/eclipse-compile/fab/java/com/software/shell/fab/ActionButtonOutlineProvider.java deleted file mode 100644 index d154c3998f..0000000000 --- a/eclipse-compile/fab/java/com/software/shell/fab/ActionButtonOutlineProvider.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015 Shell Software Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * File created: 2015-02-25 19:54:28 - */ - -package com.software.shell.fab; - -import android.annotation.TargetApi; -import android.graphics.Outline; -import android.os.Build; -import android.view.View; -import android.view.ViewOutlineProvider; - -/** - * An implementation of the {@link android.view.ViewOutlineProvider} - * for Action Button - * - * Used for drawing the elevation shadow for {@code API 21 Lollipop} and higher - * - * @author Vladislav - * @version 1.0.0 - * @since 1.0.0 - */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -class ActionButtonOutlineProvider extends ViewOutlineProvider { - - /** - * Outline provider width - */ - private int width; - - /** - * Outline provider height - */ - private int height; - - /** - * Creates an instance of the {@link com.software.shell.fab.ActionButtonOutlineProvider} - * - * @param width initial outline provider width - * @param height initial outline provider height - */ - ActionButtonOutlineProvider(int width, int height) { - this.width = width; - this.height = height; - } - - /** - * Called to get the provider to populate the Outline. This method will be called by a View - * when its owned Drawables are invalidated, when the View's size changes, or if invalidateOutline() - * is called explicitly. The input outline is empty and has an alpha of 1.0f - * - * @param view a view, which builds the outline - * @param outline an empty outline, which is to be populated - */ - @Override - public void getOutline(View view, Outline outline) { - outline.setOval(0, 0, width, height); - } - -} diff --git a/eclipse-compile/fab/java/com/software/shell/fab/FloatingActionButton.java b/eclipse-compile/fab/java/com/software/shell/fab/FloatingActionButton.java deleted file mode 100644 index 196c8e7ad5..0000000000 --- a/eclipse-compile/fab/java/com/software/shell/fab/FloatingActionButton.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2015 Shell Software Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * File created: 2015-02-16 10:56:57 - */ - -package com.software.shell.fab; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.util.Log; -import android.view.animation.Animation; - -/** - * Deprecated since version 1.0.2. Use {@link com.software.shell.fab.ActionButton} - * class instead. - * The reason is the rename of base class name from FloatingActionButton to - * ActionButton and some of the methods, which are present in this class. - *

- * Will be removed in version 2.0.0. Please use {@link com.software.shell.fab.ActionButton} and - * methods declared there instead - * - * @author Vladislav - * @version 1.0.0 - * @since 1.0.0 - */ -@Deprecated -public class FloatingActionButton extends ActionButton{ - - private static final String LOG_TAG = "FAB"; - - public FloatingActionButton(Context context) { - super(context); - } - - public FloatingActionButton(Context context, AttributeSet attrs) { - super(context, attrs); - initActionButton(context, attrs, 0, 0); - } - - public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initActionButton(context, attrs, defStyleAttr, 0); - } - - public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initActionButton(context, attrs, defStyleAttr, defStyleRes); - } - - /** - * Returns an animation, which is used while showing Floating Action Button - * @deprecated since version 1.0.2. Please use {@link #getShowAnimation()} instead - * - * @return animation, which is used while showing Floating Action Button - */ - @Deprecated - public Animation getAnimationOnShow() { - return getShowAnimation(); - } - - /** - * Sets the animation, which is used while showing Floating Action Button - * @deprecated since version 1.0.2. Please use - * {@link #setShowAnimation(android.view.animation.Animation)} instead - * - * @param animation animation, which is to be used while showing - * Floating Action Button - */ - @Deprecated - public void setAnimationOnShow(Animation animation) { - setShowAnimation(animation); - } - - /** - * Sets one of the {@link Animations} as animation, which is used while showing - * Floating Action Button - * @deprecated since version 1.0.2. Please use - * {@link #setShowAnimation(com.software.shell.fab.ActionButton.Animations)} instead - * - * @param animation one of the {@link Animations}, which is to be used while - * showing Floating Action Button - */ - @Deprecated - public void setAnimationOnShow(Animations animation) { - setShowAnimation(animation); - } - - /** - * Returns an animation, which is used while hiding Floating Action Button - * @deprecated since version 1.0.2. Please use {@link #getHideAnimation()} - * instead - * - * @return animation, which is used while hiding Floating Action Button - */ - @Deprecated - public Animation getAnimationOnHide() { - return getHideAnimation(); - } - - /** - * Sets the animation, which is used while hiding Floating Action Button - * @deprecated since version 1.0.2. Please use - * {@link #setHideAnimation(android.view.animation.Animation)} instead - * - * @param animation animation, which is to be used while hiding - * Floating Action Button - */ - @Deprecated - public void setAnimationOnHide(Animation animation) { - setHideAnimation(animation); - } - - /** - * Sets one of the {@link Animations} as animation, which is used while hiding - * Floating Action Button - * @deprecated since version 1.0.2. Please use - * {@link #setHideAnimation(com.software.shell.fab.ActionButton.Animations)} )} instead - * - * @param animation one of the {@link Animations}, which is to be used while - * hiding Floating Action Button - */ - @Deprecated - public void setAnimationOnHide(Animations animation) { - setHideAnimation(animation); - } - - private void initActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ActionButton, - defStyleAttr, defStyleRes); - try { - initType(attributes); - initShowAnimation(attributes); - initHideAnimation(attributes); - } catch (Exception e) { - Log.e(LOG_TAG, "Unable to read attr", e); - } finally { - attributes.recycle(); - } - Log.v(LOG_TAG, "Floating Action Button initialized"); - } - - private void initType(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_type)) { - final int id = attrs.getInteger(R.styleable.ActionButton_type, 0); - setType(Type.forId(id)); - Log.v(LOG_TAG, "Initialized type: " + getType()); - } - } - - /** - * Initializes the animation, which is used while showing - * Action Button - * @deprecated since 1.0.2 and will be removed in version 2.0.0. - * Use show_animation and hide_animation in XML instead - * - * @param attrs attributes of the XML tag that is inflating the view - */ - @Deprecated - private void initShowAnimation(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_animation_onShow)) { - final int animResId = attrs.getResourceId(R.styleable.ActionButton_animation_onShow, - Animations.NONE.animResId); - setShowAnimation(Animations.load(getContext(), animResId)); - } - } - - /** - * Initializes the animation, which is used while hiding or dismissing - * Action Button - * @deprecated since 1.0.2 and will be removed in version 2.0.0 - * Use show_animation and hide_animation in XML instead - * - * @param attrs attributes of the XML tag that is inflating the view - */ - @Deprecated - private void initHideAnimation(TypedArray attrs) { - if (attrs.hasValue(R.styleable.ActionButton_animation_onHide)) { - final int animResId = attrs.getResourceId(R.styleable.ActionButton_animation_onHide, - Animations.NONE.animResId); - setHideAnimation(Animations.load(getContext(), animResId)); - } - } - -} diff --git a/eclipse-compile/fab/java/com/software/shell/fab/MetricsConverter.java b/eclipse-compile/fab/java/com/software/shell/fab/MetricsConverter.java deleted file mode 100644 index c4770449c4..0000000000 --- a/eclipse-compile/fab/java/com/software/shell/fab/MetricsConverter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015 Shell Software Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * File created: 2015-01-30 22:55:44 - */ - -package com.software.shell.fab; - -import android.content.Context; - -/** - * Contains utility methods for metrics conversion - * - * @author Vladislav - * @version 1.0.0 - * @since 1.0.0 - */ -public final class MetricsConverter { - - /** - * Prevents from creating {@link com.software.shell.fab.MetricsConverter} instances - */ - private MetricsConverter() { - } - - /** - * Converts the density-independent value into real pixel value based on display metrics - * - * @param context application context - * @param dp density-independent value - * @return converted real pixel value - */ - public static float dpToPx(Context context, float dp) { - final float scale = context.getResources().getDisplayMetrics().density; - return dp * scale; - } - -} diff --git a/eclipse-compile/fab/project.properties b/eclipse-compile/fab/project.properties deleted file mode 100644 index 17b8e6b92f..0000000000 --- a/eclipse-compile/fab/project.properties +++ /dev/null @@ -1,15 +0,0 @@ -# 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. - -# Indicates whether an apk should be generated for each density. -split.density=false -# Project target. -target=android-21 -dex.force.jumbo=true -android.library=true diff --git a/eclipse-compile/fab/res/anim-v11/fab_jump_from_down.xml b/eclipse-compile/fab/res/anim-v11/fab_jump_from_down.xml deleted file mode 100644 index 87095c9bc7..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_jump_from_down.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_jump_from_right.xml b/eclipse-compile/fab/res/anim-v11/fab_jump_from_right.xml deleted file mode 100644 index 9db4bf984f..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_jump_from_right.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_jump_to_down.xml b/eclipse-compile/fab/res/anim-v11/fab_jump_to_down.xml deleted file mode 100644 index ca59b72de1..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_jump_to_down.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_jump_to_right.xml b/eclipse-compile/fab/res/anim-v11/fab_jump_to_right.xml deleted file mode 100644 index d4287e6264..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_jump_to_right.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_roll_from_down.xml b/eclipse-compile/fab/res/anim-v11/fab_roll_from_down.xml deleted file mode 100644 index 438160f361..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_roll_from_down.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_roll_from_right.xml b/eclipse-compile/fab/res/anim-v11/fab_roll_from_right.xml deleted file mode 100644 index 0ef84e389b..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_roll_from_right.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_roll_to_down.xml b/eclipse-compile/fab/res/anim-v11/fab_roll_to_down.xml deleted file mode 100644 index 8e8140cf93..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_roll_to_down.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_roll_to_right.xml b/eclipse-compile/fab/res/anim-v11/fab_roll_to_right.xml deleted file mode 100644 index 22915225a9..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_roll_to_right.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_scale_down.xml b/eclipse-compile/fab/res/anim-v11/fab_scale_down.xml deleted file mode 100644 index ce63f0fd5f..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_scale_down.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - diff --git a/eclipse-compile/fab/res/anim-v11/fab_scale_up.xml b/eclipse-compile/fab/res/anim-v11/fab_scale_up.xml deleted file mode 100644 index 1d18f40544..0000000000 --- a/eclipse-compile/fab/res/anim-v11/fab_scale_up.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/eclipse-compile/fab/res/anim/fab_fade_in.xml b/eclipse-compile/fab/res/anim/fab_fade_in.xml deleted file mode 100644 index 2b0ebedb7f..0000000000 --- a/eclipse-compile/fab/res/anim/fab_fade_in.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/eclipse-compile/fab/res/anim/fab_fade_out.xml b/eclipse-compile/fab/res/anim/fab_fade_out.xml deleted file mode 100644 index 064ff9e45b..0000000000 --- a/eclipse-compile/fab/res/anim/fab_fade_out.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/eclipse-compile/fab/res/anim/fab_jump_from_down.xml b/eclipse-compile/fab/res/anim/fab_jump_from_down.xml deleted file mode 100644 index f41e38317f..0000000000 --- a/eclipse-compile/fab/res/anim/fab_jump_from_down.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_jump_from_right.xml b/eclipse-compile/fab/res/anim/fab_jump_from_right.xml deleted file mode 100644 index c9bdc0da7a..0000000000 --- a/eclipse-compile/fab/res/anim/fab_jump_from_right.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_jump_to_down.xml b/eclipse-compile/fab/res/anim/fab_jump_to_down.xml deleted file mode 100644 index 0649c71a04..0000000000 --- a/eclipse-compile/fab/res/anim/fab_jump_to_down.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_jump_to_right.xml b/eclipse-compile/fab/res/anim/fab_jump_to_right.xml deleted file mode 100644 index 8ea8b69a3a..0000000000 --- a/eclipse-compile/fab/res/anim/fab_jump_to_right.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_roll_from_down.xml b/eclipse-compile/fab/res/anim/fab_roll_from_down.xml deleted file mode 100644 index 66a284f25d..0000000000 --- a/eclipse-compile/fab/res/anim/fab_roll_from_down.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_roll_from_right.xml b/eclipse-compile/fab/res/anim/fab_roll_from_right.xml deleted file mode 100644 index 3c38343fcd..0000000000 --- a/eclipse-compile/fab/res/anim/fab_roll_from_right.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_roll_to_down.xml b/eclipse-compile/fab/res/anim/fab_roll_to_down.xml deleted file mode 100644 index 16a6946ea9..0000000000 --- a/eclipse-compile/fab/res/anim/fab_roll_to_down.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_roll_to_right.xml b/eclipse-compile/fab/res/anim/fab_roll_to_right.xml deleted file mode 100644 index 420734f2b8..0000000000 --- a/eclipse-compile/fab/res/anim/fab_roll_to_right.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_scale_down.xml b/eclipse-compile/fab/res/anim/fab_scale_down.xml deleted file mode 100644 index e0dd6bea64..0000000000 --- a/eclipse-compile/fab/res/anim/fab_scale_down.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - diff --git a/eclipse-compile/fab/res/anim/fab_scale_up.xml b/eclipse-compile/fab/res/anim/fab_scale_up.xml deleted file mode 100644 index 956f6ed79d..0000000000 --- a/eclipse-compile/fab/res/anim/fab_scale_up.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/eclipse-compile/fab/res/drawable-hdpi/fab_plus_icon.png b/eclipse-compile/fab/res/drawable-hdpi/fab_plus_icon.png deleted file mode 100644 index b4358ce201..0000000000 Binary files a/eclipse-compile/fab/res/drawable-hdpi/fab_plus_icon.png and /dev/null differ diff --git a/eclipse-compile/fab/res/drawable-mdpi/fab_plus_icon.png b/eclipse-compile/fab/res/drawable-mdpi/fab_plus_icon.png deleted file mode 100644 index 6a32089858..0000000000 Binary files a/eclipse-compile/fab/res/drawable-mdpi/fab_plus_icon.png and /dev/null differ diff --git a/eclipse-compile/fab/res/drawable-xhdpi/fab_plus_icon.png b/eclipse-compile/fab/res/drawable-xhdpi/fab_plus_icon.png deleted file mode 100644 index a92d0a8f34..0000000000 Binary files a/eclipse-compile/fab/res/drawable-xhdpi/fab_plus_icon.png and /dev/null differ diff --git a/eclipse-compile/fab/res/drawable-xxhdpi/fab_plus_icon.png b/eclipse-compile/fab/res/drawable-xxhdpi/fab_plus_icon.png deleted file mode 100644 index 569b089f47..0000000000 Binary files a/eclipse-compile/fab/res/drawable-xxhdpi/fab_plus_icon.png and /dev/null differ diff --git a/eclipse-compile/fab/res/drawable-xxxhdpi/fab_plus_icon.png b/eclipse-compile/fab/res/drawable-xxxhdpi/fab_plus_icon.png deleted file mode 100644 index 90a1d16dcf..0000000000 Binary files a/eclipse-compile/fab/res/drawable-xxxhdpi/fab_plus_icon.png and /dev/null differ diff --git a/eclipse-compile/fab/res/values/attrs.xml b/eclipse-compile/fab/res/values/attrs.xml deleted file mode 100644 index bdd08225a7..0000000000 --- a/eclipse-compile/fab/res/values/attrs.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eclipse-compile/fab/res/values/colors.xml b/eclipse-compile/fab/res/values/colors.xml deleted file mode 100644 index 60ca1a0bbb..0000000000 --- a/eclipse-compile/fab/res/values/colors.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - #f44336 - #b71c1c - - #e91e63 - #880e4f - - #9c27b0 - #4a148c - - #673ab7 - #311b92 - - #3f51b5 - #1a237e - - #2196f3 - #0d47a1 - - #03a9f4 - #01579b - - #00bcd4 - #006064 - - #009688 - #004d40 - - #4caf50 - #1b5e20 - - #8bc34a - #33691e - - #cddc39 - #827717 - - #ffeb3b - #f57f17 - - #ffc107 - #ff6f00 - - #ff9800 - #e65100 - - #ff5722 - #bf360c - - #795548 - #3e2723 - - #9e9e9e - #212121 - - #607d8b - #263238 - - #000000 - - #ffffff - diff --git a/eclipse-compile/fab/res/values/dimens.xml b/eclipse-compile/fab/res/values/dimens.xml deleted file mode 100644 index 06d5dccfcd..0000000000 --- a/eclipse-compile/fab/res/values/dimens.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - 16dp - - diff --git a/eclipse-compile/observable/.classpath b/eclipse-compile/observable/.classpath deleted file mode 100644 index c6e9dc9f1d..0000000000 --- a/eclipse-compile/observable/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/eclipse-compile/observable/.gitignore b/eclipse-compile/observable/.gitignore deleted file mode 100755 index 15f46acd99..0000000000 --- a/eclipse-compile/observable/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/build -/bin -/gen - diff --git a/eclipse-compile/observable/.project b/eclipse-compile/observable/.project deleted file mode 100644 index 8258814f66..0000000000 --- a/eclipse-compile/observable/.project +++ /dev/null @@ -1,39 +0,0 @@ - - - ListObservable - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - org.eclipse.xtext.ui.shared.xtextBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - org.eclipse.xtext.ui.shared.xtextNature - - diff --git a/eclipse-compile/observable/AndroidManifest.xml b/eclipse-compile/observable/AndroidManifest.xml deleted file mode 100755 index 2dc5b76674..0000000000 --- a/eclipse-compile/observable/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/eclipse-compile/observable/build.gradle b/eclipse-compile/observable/build.gradle deleted file mode 100755 index 20f59fb26a..0000000000 --- a/eclipse-compile/observable/build.gradle +++ /dev/null @@ -1,62 +0,0 @@ -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.1.0' - classpath 'com.android.tools.build:gradle:1.0.0' - } -} - -apply plugin: 'com.android.library' - -repositories { - mavenCentral() -} - -dependencies { - compile 'com.android.support:recyclerview-v7:21.0.0' - androidTestCompile ('com.android.support:appcompat-v7:21.0.2') { - exclude module: 'support-v4' - } - androidTestCompile ('com.nineoldandroids:library:2.4.0') { - exclude module: 'support-v4' - } -} - -android { - compileSdkVersion 21 - buildToolsVersion "21.1.1" - - defaultConfig { - minSdkVersion 9 - } - - jacoco { - version = '0.7.2.201409121644' - } - - buildTypes { - debug { - testCoverageEnabled = true - } - } - - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - res.srcDirs = ['res'] - } - } - - lintOptions { - abortOnError false - } -} - -apply plugin: 'com.github.kt3k.coveralls' - -coveralls.jacocoReportPath = 'build/outputs/reports/coverage/debug/report.xml' - -// This is from 'https://github.com/chrisbanes/gradle-mvn-push' -apply from: 'gradle-mvn-push.gradle' diff --git a/eclipse-compile/observable/gradle-mvn-push.gradle b/eclipse-compile/observable/gradle-mvn-push.gradle deleted file mode 100755 index f5f4129c96..0000000000 --- a/eclipse-compile/observable/gradle-mvn-push.gradle +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2013 Chris Banes - * - * 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. - */ - -apply plugin: 'maven' -apply plugin: 'signing' - -def isReleaseBuild() { - return VERSION_NAME.contains("SNAPSHOT") == false -} - -def getReleaseRepositoryUrl() { - return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL - : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" -} - -def getSnapshotRepositoryUrl() { - return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL - : "https://oss.sonatype.org/content/repositories/snapshots/" -} - -def getRepositoryUsername() { - return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" -} - -def getRepositoryPassword() { - return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" -} - -afterEvaluate { project -> - uploadArchives { - repositories { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - pom.groupId = GROUP - pom.artifactId = POM_ARTIFACT_ID - pom.version = VERSION_NAME - - repository(url: getReleaseRepositoryUrl()) { - authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) - } - snapshotRepository(url: getSnapshotRepositoryUrl()) { - authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) - } - - pom.project { - name POM_NAME - packaging POM_PACKAGING - description POM_DESCRIPTION - url POM_URL - - scm { - url POM_SCM_URL - connection POM_SCM_CONNECTION - developerConnection POM_SCM_DEV_CONNECTION - } - - licenses { - license { - name POM_LICENCE_NAME - url POM_LICENCE_URL - distribution POM_LICENCE_DIST - } - } - - developers { - developer { - id POM_DEVELOPER_ID - name POM_DEVELOPER_NAME - } - } - } - } - } - } - - signing { - required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } - sign configurations.archives - } - - task androidJavadocs(type: Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - } - - task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { - classifier = 'javadoc' - from androidJavadocs.destinationDir - } - - task androidSourcesJar(type: Jar) { - classifier = 'sources' - from android.sourceSets.main.java.sourceFiles - } - - artifacts { - archives androidSourcesJar - archives androidJavadocsJar - } -} diff --git a/eclipse-compile/observable/gradle.properties b/eclipse-compile/observable/gradle.properties deleted file mode 100755 index a5035de4fa..0000000000 --- a/eclipse-compile/observable/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -POM_NAME=Android-ObservableScrollView -POM_ARTIFACT_ID=android-observablescrollview -POM_PACKAGING=aar diff --git a/eclipse-compile/observable/java2/androidTest/AndroidManifest.xml b/eclipse-compile/observable/java2/androidTest/AndroidManifest.xml deleted file mode 100755 index 0bbcb89165..0000000000 --- a/eclipse-compile/observable/java2/androidTest/AndroidManifest.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/assets/lipsum.html b/eclipse-compile/observable/java2/androidTest/assets/lipsum.html deleted file mode 100755 index 7d71889e3b..0000000000 --- a/eclipse-compile/observable/java2/androidTest/assets/lipsum.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - -

-

Lorem ipsum dolor sit amet, ut duis lorem provident sed felis blandit, condimentum donec lectus ipsum et mauris, morbi porttitor interdum feugiat nulla donec sodales, vestibulum nisl primis a molestie vestibulum quam, sapien mauris metus risus suspendisse magnis. Augue viverra nulla faucibus egestas eu, a etiam id congue rutrum ante, arcu tincidunt donec quam felis at ornare, iaculis ligula sodales venenatis commodo volutpat neque, suspendisse elit praesent tellus felis mi amet. Inceptos amet tempor lectus lorem est non, ac donec ac libero neque mauris, tellus ante metus eget leo consequat. Scelerisque dolor curabitur pretium blandit ut feugiat, amet lacus pulvinar justo convallis ut, sed natoque ipsum urna posuere nibh eu. Sed at sed vulputate sit orci, facilisis a aliquam tellus quam aliquam, eu aliquam donec at molestie ante, pellentesque mauris lorem ultrices libero faucibus porta, imperdiet adipiscing sit hac diam ut nulla. Lacus enim elit pulvinar donec vehicula dapibus, accumsan purus officia cursus dolor sapien, eu amet dis mauris mi nulla ut. Non accusamus etiam pede non urna tempus, vestibulum aliquam tortor eget pharetra sodales, in vestibulum ut justo orci nulla, lobortis purus sem semper consectetuer magni purus. Dolor a leo vestibulum amet ut sit, arcu ut eaque urna fusce aliquet turpis, sed fermentum sed vestibulum nisl pede, tristique enim lorem posuere in laborum ut. Vestibulum id id justo leo nulla, magna lobortis ullamcorper et dignissim pellentesque, duis suspendisse quis id lorem ante. Vivamus a nullam ante adipiscing amet, mi vel consectetuer nunc aenean pede quisque, eget rhoncus dis porttitor habitant nunc vivamus, duis cubilia blandit non donec justo dictumst, praesent vitae nulla nam pulvinar urna. Adipiscing adipiscing justo urna pulvinar imperdiet nullam, vitae fusce rhoncus proin nonummy suscipit, ullamcorper amet et non potenti platea ultrices, mauris nullam sapien nunc justo vel, eu semper pellentesque arcu fusce augue. Malesuada mauris nibh sit a a scelerisque, velit sem lectus tellus convallis consectetuer, ultricies auctor a ante eros amet sed.

-

Risus lacus duis leo platea wisi, felis maecenas rutrum in id in donec, non id a potenti libero eget, posuere elit ea sed pellentesque quis. Sunt lacus urna lorem elit duis, nibh donec purus quisque consectetuer dolor, neque vestibulum proin ornare eros nonummy phasellus. Iaculis cras eu at egestas dolor montes, viverra quisque malesuada consectetuer semper maecenas, a sed vitae donec tempor aliqua metus, ornare mollis suscipit et erat fusce, sit orci aut auctor elementum fames aliquam. Platea dui integer magnis non metus, minus dignissimos ante massa nostra et, rutrum sapien egestas quis sapien donec donec. Erat sit a eros aenean natoque, quam libero id lorem enim proin, lorem ipsum fermentum mattis metus et. Aliquam aliquet suscipit purus conubia at neque, platea vivamus vestibulum nulla quibusdam senectus, et morbi lectus malesuada gravida donec, elementum sit convallis pellentesque velit amet. Et eveniet viverra vehicula consectetuer justo, provident sed commodo non lacinia velit, tempor phasellus vel leo nisl cras, vivamus et arcu interdum dui eu amet. Volutpat wisi rhoncus vel turpis diam quibusdam, dapibus elit est quisque cubilia mauris, nulla elit magna tempor accumsan bibendum, lorem varius sed interdum eget mattis, scelerisque egestas feugiat donec dui molestie. Leo facilisis nisl sit montes ligula sed, enim commodo consectetuer nunc est et, ut sed vehicula dolor luctus elit. Fermentum cras donec eget nibh est vel, sed justo risus et pharetra diam, eu vivamus egestas ligula risus diam, sed justo eget hac ut mauris. Vestibulum diam nec vitae mi eget suspendisse, aenean arcu purus facilisis purus class in, id aliquam sit id scelerisque sapien etiam. Ut nullam sit sed at mauris lobortis, consequat dolor autem ipsum euismod nulla, elit quis proin eget conubia varius, erat arcu massa mus in mauris, scelerisque ut eu sollicitudin libero leo urna.

-

Consectetuer luctus tempor elit ut dolor ligula, quis dui per dui hendrerit ante sagittis, in quisque pretium in eleifend enim. Condimentum iaculis vitae feugiat dis tellus vel, lectus dolor nec dui nulla nascetur, et pellentesque curabitur lorem leo velit eget. Id nascetur arcu lobortis suspendisse imperdiet urna, natoque nascetur ante in porta a, interdum hendrerit mi bibendum platea tellus, urna in enim ornare vestibulum faucibus enim. Leo fusce egestas ante nec volutpat, in tempor vel facilisis potenti ut, pede at non lorem a commodo, nulla dolor orci interdum vestibulum nulla. Dui nulla vestibulum quisque a pharetra porta, integer nec ipsum nec sed dui pharetra, magna et dignissim ipsum sed dictum, litora eros vivamus scelerisque libero ipsum. Sed ac ac lorem molestie adipiscing morbi, pellentesque imperdiet nunc quis morbi amet ante, libero dui ligula nec risus neque et, velit nonummy phasellus et facilisi amet, ligula in elementum non sapien pulvinar faucibus. Eu leo ut posuere sed aliquet, tincidunt vel urna volutpat tempus sem, sit felis aliquet vestibulum condimentum sit, amet nibh vel tellus purus ullamcorper libero, nulla vestibulum pede ut vestibulum pretium. Eu nulla vestibulum a neque in metus, quisquam nam sed cursus eget luctus, pede ultrices nec sed dignissim pellentesque, sit class cursus metus nulla placerat mauris, consequat mollis neque vivamus amet pede. Mauris dolor nulla diam eros bibendum, quam ante vestibulum morbi non ligula vel, molestie curabitur rhoncus nulla euismod interdum non. Nulla fringilla lorem mollis ad massa, sit molestie nibh lorem arcu volutpat, accumsan commodo lectus eu et donec, sit tempor tempus rutrum in curabitur amet. Nec urna euismod a tincidunt commodo, eu pede turpis libero vitae viverra, ante vestibulum nam non habitasse potenti, mauris imperdiet in in nunc convallis. Et nostra wisi in est accumsan vehicula, quisque vitae felis mauris sed vulputate nec, ante imperdiet sollicitudin massa iaculis massa sit.

-

Quam libero nulla netus eu porta curae, ut nulla bibendum facilisis et urna sed, quis congue vestibulum aliquam interdum etiam. Nulla vel lobortis ullamcorper vitae excepturi, neque urna feugiat lectus vel lacinia, massa pretium orci eu metus neque vulputate. Imperdiet ac velit rhoncus nulla malesuada nullam, nec pulvinar justo gravida lorem rutrum magna, habitasse repudiandae mi eros vestibulum ante, nec euismod dui iaculis in turpis pretium, ac id metus egestas proin lacus lectus. Laoreet lorem nec vitae risus erat arcu, vitae quam ut in ante tristique, porta dolor pede quam et odio nam, arcu lacus sem congue ante cursus massa. Et mattis sagittis erat accumsan fusce quam, vehicula ligula beatae natoque fusce sodales conubia, habitasse metus cum magnis viverra nam cursus, egestas urna wisi primis blandit eu magna, eget libero elit lacus lorem dis aliquam. Ut mauris ante natoque lacus massa, justo a lectus sodales enim adipiscing id, accumsan ut ipsum vestibulum sed enim auctor, vitae congue tincidunt id phasellus lacinia scelerisque, tincidunt sapien nulla euismod volutpat iaculis. Platea sociis nec aliquet nec molestie, in mi et augue sapien in vivamus, integer fames proin vitae in ullamcorper et. Fringilla etiam sapiente rhoncus suspendisse nec id, lobortis cras eget egestas dui ac nec, justo lacus ut lorem bibendum quia eros, eget a gravida id donec nunc suscipit, porta sed in sodales non rutrum. Lectus vel dui elementum pellentesque magna aliquam, vitae non sit pede et fusce nibh, id id deserunt ornare dui sit condimentum, in adipiscing imperdiet turpis nam aliquet, facilisis metus magna lacus wisi facilisis tortor. Vulputate elit accumsan quam amet ligula, suspendisse lacus mi nonummy integer urna, libero nulla nunc varius in odio, laoreet nulla amet placerat amet nec. Consectetuer vel massa hendrerit vitae iaculis id, sed ut ut laudantium odio in, elit vestibulum duis ante maecenas interdum in, neque vehicula ultrices varius in quam, pede tellus pellentesque sed nullam quis.

-
- - \ No newline at end of file diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/SavedStateTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/SavedStateTest.java deleted file mode 100755 index c98d0fa50b..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/SavedStateTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview; - -import android.os.Parcel; -import android.test.InstrumentationTestCase; -import android.util.SparseIntArray; -import android.view.AbsSavedState; - -public class SavedStateTest extends InstrumentationTestCase { - - public void testGridViewSavedState() throws Throwable { - Parcel parcel = Parcel.obtain(); - ObservableGridView.SavedState state1 = new ObservableGridView.SavedState(AbsSavedState.EMPTY_STATE); - state1.prevFirstVisiblePosition = 1; - state1.prevFirstVisibleChildHeight = 2; - state1.prevScrolledChildrenHeight = 3; - state1.prevScrollY = 4; - state1.scrollY = 5; - state1.childrenHeights = new SparseIntArray(); - state1.childrenHeights.put(0, 10); - state1.childrenHeights.put(1, 20); - state1.childrenHeights.put(2, 30); - state1.writeToParcel(parcel, 0); - - parcel.setDataPosition(0); - - ObservableGridView.SavedState state2 = ObservableGridView.SavedState.CREATOR.createFromParcel(parcel); - assertNotNull(state2); - assertEquals(state1.prevFirstVisiblePosition, state2.prevFirstVisiblePosition); - assertEquals(state1.prevFirstVisibleChildHeight, state2.prevFirstVisibleChildHeight); - assertEquals(state1.prevScrolledChildrenHeight, state2.prevScrolledChildrenHeight); - assertEquals(state1.prevScrollY, state2.prevScrollY); - assertEquals(state1.scrollY, state2.scrollY); - assertNotNull(state1.childrenHeights); - assertEquals(3, state1.childrenHeights.size()); - assertEquals(10, state1.childrenHeights.get(0)); - assertEquals(20, state1.childrenHeights.get(1)); - assertEquals(30, state1.childrenHeights.get(2)); - } - - public void testListViewSavedState() throws Throwable { - Parcel parcel = Parcel.obtain(); - ObservableListView.SavedState state1 = new ObservableListView.SavedState(AbsSavedState.EMPTY_STATE); - state1.prevFirstVisiblePosition = 1; - state1.prevFirstVisibleChildHeight = 2; - state1.prevScrolledChildrenHeight = 3; - state1.prevScrollY = 4; - state1.scrollY = 5; - state1.childrenHeights = new SparseIntArray(); - state1.childrenHeights.put(0, 10); - state1.childrenHeights.put(1, 20); - state1.childrenHeights.put(2, 30); - state1.writeToParcel(parcel, 0); - - parcel.setDataPosition(0); - - ObservableListView.SavedState state2 = ObservableListView.SavedState.CREATOR.createFromParcel(parcel); - assertNotNull(state2); - assertEquals(state1.prevFirstVisiblePosition, state2.prevFirstVisiblePosition); - assertEquals(state1.prevFirstVisibleChildHeight, state2.prevFirstVisibleChildHeight); - assertEquals(state1.prevScrolledChildrenHeight, state2.prevScrolledChildrenHeight); - assertEquals(state1.prevScrollY, state2.prevScrollY); - assertEquals(state1.scrollY, state2.scrollY); - assertNotNull(state1.childrenHeights); - assertEquals(3, state1.childrenHeights.size()); - assertEquals(10, state1.childrenHeights.get(0)); - assertEquals(20, state1.childrenHeights.get(1)); - assertEquals(30, state1.childrenHeights.get(2)); - } - - public void testRecyclerViewSavedState() throws Throwable { - Parcel parcel = Parcel.obtain(); - ObservableRecyclerView.SavedState state1 = new ObservableRecyclerView.SavedState(AbsSavedState.EMPTY_STATE); - state1.prevFirstVisiblePosition = 1; - state1.prevFirstVisibleChildHeight = 2; - state1.prevScrolledChildrenHeight = 3; - state1.prevScrollY = 4; - state1.scrollY = 5; - state1.childrenHeights = new SparseIntArray(); - state1.childrenHeights.put(0, 10); - state1.childrenHeights.put(1, 20); - state1.childrenHeights.put(2, 30); - state1.writeToParcel(parcel, 0); - - parcel.setDataPosition(0); - - ObservableRecyclerView.SavedState state2 = ObservableRecyclerView.SavedState.CREATOR.createFromParcel(parcel); - assertNotNull(state2); - assertEquals(state1.prevFirstVisiblePosition, state2.prevFirstVisiblePosition); - assertEquals(state1.prevFirstVisibleChildHeight, state2.prevFirstVisibleChildHeight); - assertEquals(state1.prevScrolledChildrenHeight, state2.prevScrolledChildrenHeight); - assertEquals(state1.prevScrollY, state2.prevScrollY); - assertEquals(state1.scrollY, state2.scrollY); - assertNotNull(state1.childrenHeights); - assertEquals(3, state1.childrenHeights.size()); - assertEquals(10, state1.childrenHeights.get(0)); - assertEquals(20, state1.childrenHeights.get(1)); - assertEquals(30, state1.childrenHeights.get(2)); - } - - public void testScrollViewSavedState() throws Throwable { - Parcel parcel = Parcel.obtain(); - ObservableScrollView.SavedState state1 = new ObservableScrollView.SavedState(AbsSavedState.EMPTY_STATE); - state1.prevScrollY = 1; - state1.scrollY = 2; - state1.writeToParcel(parcel, 0); - - parcel.setDataPosition(0); - - ObservableScrollView.SavedState state2 = ObservableScrollView.SavedState.CREATOR.createFromParcel(parcel); - assertNotNull(state2); - assertEquals(state1.prevScrollY, state2.prevScrollY); - assertEquals(state1.scrollY, state2.scrollY); - } - - public void testWebViewSavedState() throws Throwable { - Parcel parcel = Parcel.obtain(); - ObservableWebView.SavedState state1 = new ObservableWebView.SavedState(AbsSavedState.EMPTY_STATE); - state1.prevScrollY = 1; - state1.scrollY = 2; - state1.writeToParcel(parcel, 0); - - parcel.setDataPosition(0); - - ObservableWebView.SavedState state2 = ObservableWebView.SavedState.CREATOR.createFromParcel(parcel); - assertNotNull(state2); - assertEquals(state1.prevScrollY, state2.prevScrollY); - assertEquals(state1.scrollY, state2.scrollY); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/GridViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/GridViewActivity.java deleted file mode 100755 index 111b616fda..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/GridViewActivity.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.widget.AbsListView; - -import com.github.ksoichiro.android.observablescrollview.ObservableGridView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; - -public class GridViewActivity extends Activity implements ObservableScrollViewCallbacks { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_gridview); - ObservableGridView scrollable = (ObservableGridView) findViewById(R.id.scrollable); - scrollable.setScrollViewCallbacks(this); - UiTestUtils.setDummyData(this, scrollable); - scrollable.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - } - }); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/GridViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/GridViewActivityTest.java deleted file mode 100755 index 7a08f3502b..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/GridViewActivityTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import com.github.ksoichiro.android.observablescrollview.ObservableGridView; - -public class GridViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableGridView scrollable; - - public GridViewActivityTest() { - super(GridViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableGridView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } - - public void testScrollVerticallyTo() throws Throwable { - final DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); - runTestOnUiThread(new Runnable() { - @Override - public void run() { - scrollable.scrollVerticallyTo((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, metrics)); - } - }); - getInstrumentation().waitForIdleSync(); - - runTestOnUiThread(new Runnable() { - @Override - public void run() { - scrollable.scrollVerticallyTo(0); - } - }); - getInstrumentation().waitForIdleSync(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewActivity.java deleted file mode 100755 index 5d6dc79611..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewActivity.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.widget.AbsListView; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; - -public class ListViewActivity extends Activity implements ObservableScrollViewCallbacks { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_listview); - ObservableListView scrollable = (ObservableListView) findViewById(R.id.scrollable); - scrollable.setScrollViewCallbacks(this); - UiTestUtils.setDummyData(this, scrollable); - scrollable.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - } - }); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewActivityTest.java deleted file mode 100755 index 188fbc4fbc..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewActivityTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; - -public class ListViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableListView scrollable; - - public ListViewActivityTest() { - super(ListViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableListView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } - - public void testScrollVerticallyTo() throws Throwable { - final DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); - runTestOnUiThread(new Runnable() { - @Override - public void run() { - scrollable.scrollVerticallyTo((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, metrics)); - } - }); - getInstrumentation().waitForIdleSync(); - - runTestOnUiThread(new Runnable() { - @Override - public void run() { - scrollable.scrollVerticallyTo(0); - } - }); - getInstrumentation().waitForIdleSync(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewScrollFromBottomActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewScrollFromBottomActivity.java deleted file mode 100755 index 6a3509b2aa..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewScrollFromBottomActivity.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.os.Bundle; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; -import com.github.ksoichiro.android.observablescrollview.ScrollUtils; - -public class ListViewScrollFromBottomActivity extends ListViewActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final ObservableListView scrollable = (ObservableListView) findViewById(R.id.scrollable); - ScrollUtils.addOnGlobalLayoutListener(scrollable, new Runnable() { - @Override - public void run() { - int count = scrollable.getAdapter().getCount() - 1; - int position = count == 0 ? 1 : count > 0 ? count : 0; - scrollable.smoothScrollToPosition(position); - scrollable.setSelection(position); - } - }); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewScrollFromBottomActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewScrollFromBottomActivityTest.java deleted file mode 100755 index db8b465bba..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ListViewScrollFromBottomActivityTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; - -public class ListViewScrollFromBottomActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableListView scrollable; - - public ListViewScrollFromBottomActivityTest() { - super(ListViewScrollFromBottomActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableListView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewActivity.java deleted file mode 100755 index 0175397494..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewActivity.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; - -import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; - -public class RecyclerViewActivity extends Activity implements ObservableScrollViewCallbacks { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_recyclerview); - - ObservableRecyclerView recyclerView = (ObservableRecyclerView) findViewById(R.id.scrollable); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.setHasFixedSize(true); - recyclerView.setScrollViewCallbacks(this); - UiTestUtils.setDummyData(this, recyclerView); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewActivityTest.java deleted file mode 100755 index 33335ed057..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewActivityTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; - -public class RecyclerViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableRecyclerView scrollable; - - public RecyclerViewActivityTest() { - super(RecyclerViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableRecyclerView) activity.findViewById(R.id.scrollable); - getInstrumentation().waitForIdleSync(); - } - - public void testScroll() throws Throwable { - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } - - public void testScrollVerticallyTo() throws Throwable { - final DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); - runTestOnUiThread(new Runnable() { - @Override - public void run() { - scrollable.scrollVerticallyTo((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, metrics)); - } - }); - getInstrumentation().waitForIdleSync(); - - runTestOnUiThread(new Runnable() { - @Override - public void run() { - scrollable.scrollVerticallyTo(0); - } - }); - getInstrumentation().waitForIdleSync(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewScrollFromBottomActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewScrollFromBottomActivity.java deleted file mode 100755 index 5bf39218a1..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewScrollFromBottomActivity.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.os.Bundle; - -import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; -import com.github.ksoichiro.android.observablescrollview.ScrollUtils; - -public class RecyclerViewScrollFromBottomActivity extends RecyclerViewActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final ObservableRecyclerView scrollable = (ObservableRecyclerView) findViewById(R.id.scrollable); - ScrollUtils.addOnGlobalLayoutListener(scrollable, new Runnable() { - @Override - public void run() { - int count = scrollable.getAdapter().getItemCount() - 1; - int position = count == 0 ? 1 : count > 0 ? count : 0; - scrollable.scrollToPosition(position); - } - }); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewScrollFromBottomActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewScrollFromBottomActivityTest.java deleted file mode 100755 index 37eaf4d582..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/RecyclerViewScrollFromBottomActivityTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; -import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; - -public class RecyclerViewScrollFromBottomActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableRecyclerView scrollable; - - public RecyclerViewScrollFromBottomActivityTest() { - super(RecyclerViewScrollFromBottomActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableRecyclerView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollUtilsTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollUtilsTest.java deleted file mode 100755 index 6b2aecdf44..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollUtilsTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.graphics.Color; -import android.test.InstrumentationTestCase; - -import com.github.ksoichiro.android.observablescrollview.ScrollUtils; - -import junit.framework.Assert; - -public class ScrollUtilsTest extends InstrumentationTestCase { - - public void testGetFloat() { - Assert.assertEquals(1.0f, ScrollUtils.getFloat(1, 0, 2)); - assertEquals(0.0f, ScrollUtils.getFloat(-1, 0, 2)); - assertEquals(2.0f, ScrollUtils.getFloat(3, 0, 2)); - } - - public void testGetColorWithAlpha() { - assertEquals(Color.parseColor("#00123456"), ScrollUtils.getColorWithAlpha(0, Color.parseColor("#FF123456"))); - assertEquals(Color.parseColor("#FF123456"), ScrollUtils.getColorWithAlpha(1, Color.parseColor("#FF123456"))); - } - - public void testMixColors() { - assertEquals(Color.parseColor("#000000"), ScrollUtils.mixColors(Color.parseColor("#000000"), Color.parseColor("#FFFFFF"), 0)); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollViewActivity.java deleted file mode 100755 index d98a9727f0..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollViewActivity.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.Scrollable; - -public class ScrollViewActivity extends Activity implements ObservableScrollViewCallbacks { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_scrollview); - ((Scrollable) findViewById(R.id.scrollable)).setScrollViewCallbacks(this); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollViewActivityTest.java deleted file mode 100755 index c7bcf06a33..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ScrollViewActivityTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollView; - -public class ScrollViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableScrollView scrollable; - - public ScrollViewActivityTest() { - super(ScrollViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableScrollView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/SimpleHeaderRecyclerAdapter.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/SimpleHeaderRecyclerAdapter.java deleted file mode 100755 index 18a3887f17..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/SimpleHeaderRecyclerAdapter.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import java.util.ArrayList; - -public class SimpleHeaderRecyclerAdapter extends RecyclerView.Adapter { - private static final int VIEW_TYPE_HEADER = 0; - private static final int VIEW_TYPE_ITEM = 1; - - private LayoutInflater mInflater; - private ArrayList mItems; - private View mHeaderView; - - public SimpleHeaderRecyclerAdapter(Context context, ArrayList items, View headerView) { - mInflater = LayoutInflater.from(context); - mItems = items; - mHeaderView = headerView; - } - - @Override - public int getItemCount() { - if (mHeaderView == null) { - return mItems.size(); - } else { - return mItems.size() + 1; - } - } - - @Override - public int getItemViewType(int position) { - return (position == 0) ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM; - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType == VIEW_TYPE_HEADER) { - return new HeaderViewHolder(mHeaderView); - } else { - return new ItemViewHolder(mInflater.inflate(android.R.layout.simple_list_item_1, parent, false)); - } - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { - if (viewHolder instanceof ItemViewHolder) { - ((ItemViewHolder) viewHolder).textView.setText(mItems.get(position - 1)); - } - } - - static class HeaderViewHolder extends RecyclerView.ViewHolder { - public HeaderViewHolder(View view) { - super(view); - } - } - - static class ItemViewHolder extends RecyclerView.ViewHolder { - TextView textView; - - public ItemViewHolder(View view) { - super(view); - textView = (TextView) view.findViewById(android.R.id.text1); - } - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/SimpleRecyclerAdapter.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/SimpleRecyclerAdapter.java deleted file mode 100755 index daf2b5dfa4..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/SimpleRecyclerAdapter.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import java.util.ArrayList; - -public class SimpleRecyclerAdapter extends RecyclerView.Adapter { - private LayoutInflater mInflater; - private ArrayList mItems; - - public SimpleRecyclerAdapter(Context context, ArrayList items) { - mInflater = LayoutInflater.from(context); - mItems = items; - } - - @Override - public int getItemCount() { - return mItems.size(); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new ViewHolder(mInflater.inflate(android.R.layout.simple_list_item_1, parent, false)); - } - - @Override - public void onBindViewHolder(ViewHolder viewHolder, int position) { - viewHolder.textView.setText(mItems.get(position)); - } - - static class ViewHolder extends RecyclerView.ViewHolder { - TextView textView; - - public ViewHolder(View view) { - super(view); - textView = (TextView) view.findViewById(android.R.id.text1); - } - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionGridViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionGridViewActivity.java deleted file mode 100755 index 5a0891c828..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionGridViewActivity.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.view.MotionEvent; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.github.ksoichiro.android.observablescrollview.ObservableGridView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.Scrollable; -import com.github.ksoichiro.android.observablescrollview.TouchInterceptionFrameLayout; -import com.nineoldandroids.view.ViewHelper; - -public class TouchInterceptionGridViewActivity extends Activity implements ObservableScrollViewCallbacks { - - private TouchInterceptionFrameLayout mInterceptionLayout; - private Scrollable mScrollable; - - private int mIntersectionHeight; - private int mHeaderBarHeight; - - private float mScrollYOnDownMotion; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_touchinterception_gridview); - ((TextView) findViewById(R.id.title)).setText(getClass().getSimpleName()); - mScrollable = (Scrollable) findViewById(R.id.scrollable); - mScrollable.setScrollViewCallbacks(this); - UiTestUtils.setDummyData(this, (ObservableGridView) mScrollable); - - mIntersectionHeight = getResources().getDimensionPixelSize(R.dimen.intersection_height); - mHeaderBarHeight = getResources().getDimensionPixelSize(R.dimen.header_bar_height); - - mInterceptionLayout = (TouchInterceptionFrameLayout) findViewById(R.id.scroll_wrapper); - mInterceptionLayout.setScrollInterceptionListener(mInterceptionListener); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } - - private TouchInterceptionFrameLayout.TouchInterceptionListener mInterceptionListener = new TouchInterceptionFrameLayout.TouchInterceptionListener() { - @Override - public boolean shouldInterceptTouchEvent(MotionEvent ev, boolean moving, float diffX, float diffY) { - final int minInterceptionLayoutY = -mIntersectionHeight; - return minInterceptionLayoutY < (int) ViewHelper.getY(mInterceptionLayout) - || (moving && mScrollable.getCurrentScrollY() - diffY < 0); - } - - @Override - public void onDownMotionEvent(MotionEvent ev) { - mScrollYOnDownMotion = mScrollable.getCurrentScrollY(); - } - - @Override - public void onMoveMotionEvent(MotionEvent ev, float diffX, float diffY) { - float translationY = ViewHelper.getTranslationY(mInterceptionLayout) - mScrollYOnDownMotion + diffY; - if (translationY < -mIntersectionHeight) { - translationY = -mIntersectionHeight; - } else if (getScreenHeight() - mHeaderBarHeight < translationY) { - translationY = getScreenHeight() - mHeaderBarHeight; - } - - slideTo(translationY, true); - } - - @Override - public void onUpOrCancelMotionEvent(MotionEvent ev) { - } - }; - - private void slideTo(float translationY, final boolean animated) { - ViewHelper.setTranslationY(mInterceptionLayout, translationY); - - if (translationY < 0) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInterceptionLayout.getLayoutParams(); - lp.height = (int) -translationY + getScreenHeight(); - mInterceptionLayout.requestLayout(); - } - } - - private int getScreenHeight() { - return findViewById(android.R.id.content).getHeight(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionGridViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionGridViewActivityTest.java deleted file mode 100755 index 381f247921..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionGridViewActivityTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.test.TouchUtils; - -import com.github.ksoichiro.android.observablescrollview.ObservableGridView; - -public class TouchInterceptionGridViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableGridView scrollable; - - public TouchInterceptionGridViewActivityTest() { - super(TouchInterceptionGridViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableGridView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - TouchUtils.touchAndCancelView(this, scrollable); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionListViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionListViewActivity.java deleted file mode 100755 index 3b8007489b..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionListViewActivity.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.view.MotionEvent; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.Scrollable; -import com.github.ksoichiro.android.observablescrollview.TouchInterceptionFrameLayout; -import com.nineoldandroids.view.ViewHelper; - -public class TouchInterceptionListViewActivity extends Activity implements ObservableScrollViewCallbacks { - - private TouchInterceptionFrameLayout mInterceptionLayout; - private Scrollable mScrollable; - - private int mIntersectionHeight; - private int mHeaderBarHeight; - - private float mScrollYOnDownMotion; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_touchinterception_listview); - ((TextView) findViewById(R.id.title)).setText(getClass().getSimpleName()); - mScrollable = (Scrollable) findViewById(R.id.scrollable); - mScrollable.setScrollViewCallbacks(this); - UiTestUtils.setDummyData(this, (ObservableListView) mScrollable); - - mIntersectionHeight = getResources().getDimensionPixelSize(R.dimen.intersection_height); - mHeaderBarHeight = getResources().getDimensionPixelSize(R.dimen.header_bar_height); - - mInterceptionLayout = (TouchInterceptionFrameLayout) findViewById(R.id.scroll_wrapper); - mInterceptionLayout.setScrollInterceptionListener(mInterceptionListener); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } - - private TouchInterceptionFrameLayout.TouchInterceptionListener mInterceptionListener = new TouchInterceptionFrameLayout.TouchInterceptionListener() { - @Override - public boolean shouldInterceptTouchEvent(MotionEvent ev, boolean moving, float diffX, float diffY) { - final int minInterceptionLayoutY = -mIntersectionHeight; - return minInterceptionLayoutY < (int) ViewHelper.getY(mInterceptionLayout) - || (moving && mScrollable.getCurrentScrollY() - diffY < 0); - } - - @Override - public void onDownMotionEvent(MotionEvent ev) { - mScrollYOnDownMotion = mScrollable.getCurrentScrollY(); - } - - @Override - public void onMoveMotionEvent(MotionEvent ev, float diffX, float diffY) { - float translationY = ViewHelper.getTranslationY(mInterceptionLayout) - mScrollYOnDownMotion + diffY; - if (translationY < -mIntersectionHeight) { - translationY = -mIntersectionHeight; - } else if (getScreenHeight() - mHeaderBarHeight < translationY) { - translationY = getScreenHeight() - mHeaderBarHeight; - } - - slideTo(translationY, true); - } - - @Override - public void onUpOrCancelMotionEvent(MotionEvent ev) { - } - }; - - private void slideTo(float translationY, final boolean animated) { - ViewHelper.setTranslationY(mInterceptionLayout, translationY); - - if (translationY < 0) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInterceptionLayout.getLayoutParams(); - lp.height = (int) -translationY + getScreenHeight(); - mInterceptionLayout.requestLayout(); - } - } - - private int getScreenHeight() { - return findViewById(android.R.id.content).getHeight(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionListViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionListViewActivityTest.java deleted file mode 100755 index 430b775905..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionListViewActivityTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.test.TouchUtils; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; - -public class TouchInterceptionListViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableListView scrollable; - - public TouchInterceptionListViewActivityTest() { - super(TouchInterceptionListViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableListView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - TouchUtils.touchAndCancelView(this, scrollable); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionRecyclerViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionRecyclerViewActivity.java deleted file mode 100755 index a7b80620b9..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionRecyclerViewActivity.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; -import android.view.MotionEvent; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.Scrollable; -import com.github.ksoichiro.android.observablescrollview.TouchInterceptionFrameLayout; -import com.nineoldandroids.view.ViewHelper; - -public class TouchInterceptionRecyclerViewActivity extends Activity implements ObservableScrollViewCallbacks { - - private TouchInterceptionFrameLayout mInterceptionLayout; - private Scrollable mScrollable; - - private int mIntersectionHeight; - private int mHeaderBarHeight; - - private float mScrollYOnDownMotion; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_touchinterception_recyclerview); - ((TextView) findViewById(R.id.title)).setText(getClass().getSimpleName()); - mScrollable = (Scrollable) findViewById(R.id.scrollable); - mScrollable.setScrollViewCallbacks(this); - ObservableRecyclerView recyclerView = (ObservableRecyclerView) mScrollable; - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.setHasFixedSize(true); - recyclerView.setScrollViewCallbacks(this); - UiTestUtils.setDummyData(this, recyclerView); - - mIntersectionHeight = getResources().getDimensionPixelSize(R.dimen.intersection_height); - mHeaderBarHeight = getResources().getDimensionPixelSize(R.dimen.header_bar_height); - - mInterceptionLayout = (TouchInterceptionFrameLayout) findViewById(R.id.scroll_wrapper); - mInterceptionLayout.setScrollInterceptionListener(mInterceptionListener); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } - - private TouchInterceptionFrameLayout.TouchInterceptionListener mInterceptionListener = new TouchInterceptionFrameLayout.TouchInterceptionListener() { - @Override - public boolean shouldInterceptTouchEvent(MotionEvent ev, boolean moving, float diffX, float diffY) { - final int minInterceptionLayoutY = -mIntersectionHeight; - return minInterceptionLayoutY < (int) ViewHelper.getY(mInterceptionLayout) - || (moving && mScrollable.getCurrentScrollY() - diffY < 0); - } - - @Override - public void onDownMotionEvent(MotionEvent ev) { - mScrollYOnDownMotion = mScrollable.getCurrentScrollY(); - } - - @Override - public void onMoveMotionEvent(MotionEvent ev, float diffX, float diffY) { - float translationY = ViewHelper.getTranslationY(mInterceptionLayout) - mScrollYOnDownMotion + diffY; - if (translationY < -mIntersectionHeight) { - translationY = -mIntersectionHeight; - } else if (getScreenHeight() - mHeaderBarHeight < translationY) { - translationY = getScreenHeight() - mHeaderBarHeight; - } - - slideTo(translationY, true); - } - - @Override - public void onUpOrCancelMotionEvent(MotionEvent ev) { - } - }; - - private void slideTo(float translationY, final boolean animated) { - ViewHelper.setTranslationY(mInterceptionLayout, translationY); - - if (translationY < 0) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInterceptionLayout.getLayoutParams(); - lp.height = (int) -translationY + getScreenHeight(); - mInterceptionLayout.requestLayout(); - } - } - - private int getScreenHeight() { - return findViewById(android.R.id.content).getHeight(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionRecyclerViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionRecyclerViewActivityTest.java deleted file mode 100755 index caf54346f9..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionRecyclerViewActivityTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.test.TouchUtils; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; -import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; - -public class TouchInterceptionRecyclerViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableRecyclerView scrollable; - - public TouchInterceptionRecyclerViewActivityTest() { - super(TouchInterceptionRecyclerViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableRecyclerView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - TouchUtils.touchAndCancelView(this, scrollable); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionScrollViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionScrollViewActivity.java deleted file mode 100755 index f2b9c5cb46..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionScrollViewActivity.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.view.MotionEvent; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.Scrollable; -import com.github.ksoichiro.android.observablescrollview.TouchInterceptionFrameLayout; -import com.nineoldandroids.view.ViewHelper; - -public class TouchInterceptionScrollViewActivity extends Activity implements ObservableScrollViewCallbacks { - - private TouchInterceptionFrameLayout mInterceptionLayout; - private Scrollable mScrollable; - - private int mIntersectionHeight; - private int mHeaderBarHeight; - - private float mScrollYOnDownMotion; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_touchinterception_scrollview); - ((TextView) findViewById(R.id.title)).setText(getClass().getSimpleName()); - mScrollable = (Scrollable) findViewById(R.id.scrollable); - mScrollable.setScrollViewCallbacks(this); - - mIntersectionHeight = getResources().getDimensionPixelSize(R.dimen.intersection_height); - mHeaderBarHeight = getResources().getDimensionPixelSize(R.dimen.header_bar_height); - - mInterceptionLayout = (TouchInterceptionFrameLayout) findViewById(R.id.scroll_wrapper); - mInterceptionLayout.setScrollInterceptionListener(mInterceptionListener); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } - - private TouchInterceptionFrameLayout.TouchInterceptionListener mInterceptionListener = new TouchInterceptionFrameLayout.TouchInterceptionListener() { - @Override - public boolean shouldInterceptTouchEvent(MotionEvent ev, boolean moving, float diffX, float diffY) { - final int minInterceptionLayoutY = -mIntersectionHeight; - return minInterceptionLayoutY < (int) ViewHelper.getY(mInterceptionLayout) - || (moving && mScrollable.getCurrentScrollY() - diffY < 0); - } - - @Override - public void onDownMotionEvent(MotionEvent ev) { - mScrollYOnDownMotion = mScrollable.getCurrentScrollY(); - } - - @Override - public void onMoveMotionEvent(MotionEvent ev, float diffX, float diffY) { - float translationY = ViewHelper.getTranslationY(mInterceptionLayout) - mScrollYOnDownMotion + diffY; - if (translationY < -mIntersectionHeight) { - translationY = -mIntersectionHeight; - } else if (getScreenHeight() - mHeaderBarHeight < translationY) { - translationY = getScreenHeight() - mHeaderBarHeight; - } - - slideTo(translationY, true); - } - - @Override - public void onUpOrCancelMotionEvent(MotionEvent ev) { - } - }; - - private void slideTo(float translationY, final boolean animated) { - ViewHelper.setTranslationY(mInterceptionLayout, translationY); - - if (translationY < 0) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInterceptionLayout.getLayoutParams(); - lp.height = (int) -translationY + getScreenHeight(); - mInterceptionLayout.requestLayout(); - } - } - - private int getScreenHeight() { - return findViewById(android.R.id.content).getHeight(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionScrollViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionScrollViewActivityTest.java deleted file mode 100755 index 1c56fd405f..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionScrollViewActivityTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.test.TouchUtils; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollView; - -public class TouchInterceptionScrollViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableScrollView scrollable; - - public TouchInterceptionScrollViewActivityTest() { - super(TouchInterceptionScrollViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableScrollView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - TouchUtils.touchAndCancelView(this, scrollable); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionWebViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionWebViewActivity.java deleted file mode 100755 index 023905b1ea..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionWebViewActivity.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.view.MotionEvent; -import android.webkit.WebView; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.Scrollable; -import com.github.ksoichiro.android.observablescrollview.TouchInterceptionFrameLayout; -import com.nineoldandroids.view.ViewHelper; - -public class TouchInterceptionWebViewActivity extends Activity implements ObservableScrollViewCallbacks { - - private TouchInterceptionFrameLayout mInterceptionLayout; - private Scrollable mScrollable; - - private int mIntersectionHeight; - private int mHeaderBarHeight; - - private float mScrollYOnDownMotion; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_touchinterception_webview); - ((TextView) findViewById(R.id.title)).setText(getClass().getSimpleName()); - mScrollable = (Scrollable) findViewById(R.id.scrollable); - mScrollable.setScrollViewCallbacks(this); - ((WebView) mScrollable).loadUrl("file:///android_asset/lipsum.html"); - - mIntersectionHeight = getResources().getDimensionPixelSize(R.dimen.intersection_height); - mHeaderBarHeight = getResources().getDimensionPixelSize(R.dimen.header_bar_height); - - mInterceptionLayout = (TouchInterceptionFrameLayout) findViewById(R.id.scroll_wrapper); - mInterceptionLayout.setScrollInterceptionListener(mInterceptionListener); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } - - private TouchInterceptionFrameLayout.TouchInterceptionListener mInterceptionListener = new TouchInterceptionFrameLayout.TouchInterceptionListener() { - @Override - public boolean shouldInterceptTouchEvent(MotionEvent ev, boolean moving, float diffX, float diffY) { - final int minInterceptionLayoutY = -mIntersectionHeight; - return minInterceptionLayoutY < (int) ViewHelper.getY(mInterceptionLayout) - || (moving && mScrollable.getCurrentScrollY() - diffY < 0); - } - - @Override - public void onDownMotionEvent(MotionEvent ev) { - mScrollYOnDownMotion = mScrollable.getCurrentScrollY(); - } - - @Override - public void onMoveMotionEvent(MotionEvent ev, float diffX, float diffY) { - float translationY = ViewHelper.getTranslationY(mInterceptionLayout) - mScrollYOnDownMotion + diffY; - if (translationY < -mIntersectionHeight) { - translationY = -mIntersectionHeight; - } else if (getScreenHeight() - mHeaderBarHeight < translationY) { - translationY = getScreenHeight() - mHeaderBarHeight; - } - - slideTo(translationY, true); - } - - @Override - public void onUpOrCancelMotionEvent(MotionEvent ev) { - } - }; - - private void slideTo(float translationY, final boolean animated) { - ViewHelper.setTranslationY(mInterceptionLayout, translationY); - - if (translationY < 0) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInterceptionLayout.getLayoutParams(); - lp.height = (int) -translationY + getScreenHeight(); - mInterceptionLayout.requestLayout(); - } - } - - private int getScreenHeight() { - return findViewById(android.R.id.content).getHeight(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionWebViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionWebViewActivityTest.java deleted file mode 100755 index d1f24e4fa1..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/TouchInterceptionWebViewActivityTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.test.TouchUtils; - -import com.github.ksoichiro.android.observablescrollview.ObservableWebView; - -public class TouchInterceptionWebViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableWebView scrollable; - - public TouchInterceptionWebViewActivityTest() { - super(TouchInterceptionWebViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableWebView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - TouchUtils.touchAndCancelView(this, scrollable); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/UiTestUtils.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/UiTestUtils.java deleted file mode 100755 index 43ebbf8440..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/UiTestUtils.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.support.v7.widget.RecyclerView; -import android.test.InstrumentationTestCase; -import android.test.TouchUtils; -import android.util.TypedValue; -import android.view.View; -import android.widget.ArrayAdapter; -import android.widget.GridView; -import android.widget.ListView; - -import java.util.ArrayList; - -public class UiTestUtils { - - private static final int NUM_OF_ITEMS = 100; - private static final int NUM_OF_ITEMS_FEW = 3; - private static final int DRAG_STEP_COUNT = 50; - - public enum Direction { - LEFT, RIGHT, UP, DOWN - } - - private UiTestUtils() { - } - - public static void saveAndRestoreInstanceState(final InstrumentationTestCase test, final Activity activity) throws Throwable { - test.runTestOnUiThread(new Runnable() { - @Override - public void run() { - Bundle outState = new Bundle(); - test.getInstrumentation().callActivityOnSaveInstanceState(activity, outState); - test.getInstrumentation().callActivityOnPause(activity); - test.getInstrumentation().callActivityOnResume(activity); - test.getInstrumentation().callActivityOnRestoreInstanceState(activity, outState); - } - }); - test.getInstrumentation().waitForIdleSync(); - } - - public static void swipeHorizontally(InstrumentationTestCase test, View v, Direction direction) { - int[] xy = new int[2]; - v.getLocationOnScreen(xy); - - final int viewWidth = v.getWidth(); - final int viewHeight = v.getHeight(); - - float distanceFromEdge = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, - v.getResources().getDisplayMetrics()); - float xStart = xy[0] + ((direction == Direction.LEFT) ? (viewWidth - distanceFromEdge) : distanceFromEdge); - float xEnd = xy[0] + ((direction == Direction.LEFT) ? distanceFromEdge : (viewWidth - distanceFromEdge)); - float y = xy[1] + (viewHeight / 2.0f); - - TouchUtils.drag(test, xStart, xEnd, y, y, DRAG_STEP_COUNT); - } - - public static void swipeVertically(InstrumentationTestCase test, View v, Direction direction) { - int[] xy = new int[2]; - v.getLocationOnScreen(xy); - - final int viewWidth = v.getWidth(); - final int viewHeight = v.getHeight(); - - float distanceFromEdge = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, - v.getResources().getDisplayMetrics()); - float x = xy[0] + (viewWidth / 2.0f); - float yStart = xy[1] + ((direction == Direction.UP) ? (viewHeight - distanceFromEdge) : distanceFromEdge); - float yEnd = xy[1] + ((direction == Direction.UP) ? distanceFromEdge : (viewHeight - distanceFromEdge)); - - TouchUtils.drag(test, x, x, yStart, yEnd, DRAG_STEP_COUNT); - } - - public static ArrayList getDummyData() { - return getDummyData(NUM_OF_ITEMS); - } - - public static ArrayList getDummyData(int num) { - ArrayList items = new ArrayList(); - for (int i = 1; i <= num; i++) { - items.add("Item " + i); - } - return items; - } - - public static void setDummyData(Context context, GridView gridView) { - gridView.setAdapter(new ArrayAdapter(context, android.R.layout.simple_list_item_1, getDummyData())); - } - - public static void setDummyData(Context context, ListView listView) { - setDummyData(context, listView, NUM_OF_ITEMS); - } - - public static void setDummyDataFew(Context context, ListView listView) { - setDummyData(context, listView, NUM_OF_ITEMS_FEW); - } - - public static void setDummyData(Context context, ListView listView, int num) { - listView.setAdapter(new ArrayAdapter(context, android.R.layout.simple_list_item_1, getDummyData(num))); - } - - public static void setDummyDataWithHeader(Context context, ListView listView, View headerView) { - listView.addHeaderView(headerView); - setDummyData(context, listView); - } - - public static void setDummyData(Context context, RecyclerView recyclerView) { - setDummyData(context, recyclerView, NUM_OF_ITEMS); - } - - public static void setDummyDataFew(Context context, RecyclerView recyclerView) { - setDummyData(context, recyclerView, NUM_OF_ITEMS_FEW); - } - - public static void setDummyData(Context context, RecyclerView recyclerView, int num) { - recyclerView.setAdapter(new SimpleRecyclerAdapter(context, getDummyData(num))); - } - - public static void setDummyDataWithHeader(Context context, RecyclerView recyclerView, View headerView) { - recyclerView.setAdapter(new SimpleHeaderRecyclerAdapter(context, getDummyData(), headerView)); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2Activity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2Activity.java deleted file mode 100755 index ecde277728..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2Activity.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.content.res.TypedArray; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBarActivity; -import android.support.v7.widget.Toolbar; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.widget.FrameLayout; - -import com.github.ksoichiro.android.observablescrollview.CacheFragmentStatePagerAdapter; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.ScrollUtils; -import com.github.ksoichiro.android.observablescrollview.Scrollable; -import com.github.ksoichiro.android.observablescrollview.TouchInterceptionFrameLayout; -import com.google.samples.apps.iosched.ui.widget.SlidingTabLayout; -import com.nineoldandroids.animation.ValueAnimator; -import com.nineoldandroids.view.ViewHelper; - -public class ViewPagerTab2Activity extends ActionBarActivity implements ObservableScrollViewCallbacks { - - private View mToolbarView; - private TouchInterceptionFrameLayout mInterceptionLayout; - private ViewPager mPager; - private NavigationAdapter mPagerAdapter; - private int mSlop; - private boolean mScrolled; - private ScrollState mLastScrollState; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_viewpagertab2); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - mToolbarView = findViewById(R.id.toolbar); - mPagerAdapter = new NavigationAdapter(getSupportFragmentManager()); - mPager = (ViewPager) findViewById(R.id.pager); - mPager.setAdapter(mPagerAdapter); - // Padding for ViewPager must be set outside the ViewPager itself - // because with padding, EdgeEffect of ViewPager become strange. - final int tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height); - findViewById(R.id.pager_wrapper).setPadding(0, getActionBarSize() + tabHeight, 0, 0); - - SlidingTabLayout slidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs); - slidingTabLayout.setCustomTabView(R.layout.tab_indicator, android.R.id.text1); - slidingTabLayout.setSelectedIndicatorColors(getResources().getColor(R.color.accent)); - slidingTabLayout.setDistributeEvenly(true); - slidingTabLayout.setViewPager(mPager); - - ViewConfiguration vc = ViewConfiguration.get(this); - mSlop = vc.getScaledTouchSlop(); - mInterceptionLayout = (TouchInterceptionFrameLayout) findViewById(R.id.container); - mInterceptionLayout.setScrollInterceptionListener(mInterceptionListener); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - if (!mScrolled) { - // This event can be used only when TouchInterceptionFrameLayout - // doesn't handle the consecutive events. - adjustToolbar(scrollState); - } - } - - private TouchInterceptionFrameLayout.TouchInterceptionListener mInterceptionListener = new TouchInterceptionFrameLayout.TouchInterceptionListener() { - @Override - public boolean shouldInterceptTouchEvent(MotionEvent ev, boolean moving, float diffX, float diffY) { - if (!mScrolled && mSlop < Math.abs(diffX) && Math.abs(diffY) < Math.abs(diffX)) { - // Horizontal scroll is maybe handled by ViewPager - return false; - } - - Scrollable scrollable = getCurrentScrollable(); - if (scrollable == null) { - mScrolled = false; - return false; - } - - // If interceptionLayout can move, it should intercept. - // And once it begins to move, horizontal scroll shouldn't work any longer. - int toolbarHeight = mToolbarView.getHeight(); - int translationY = (int) ViewHelper.getTranslationY(mInterceptionLayout); - boolean scrollingUp = 0 < diffY; - boolean scrollingDown = diffY < 0; - if (scrollingUp) { - if (translationY < 0) { - mScrolled = true; - mLastScrollState = ScrollState.UP; - return true; - } - } else if (scrollingDown) { - if (-toolbarHeight < translationY) { - mScrolled = true; - mLastScrollState = ScrollState.DOWN; - return true; - } - } - mScrolled = false; - return false; - } - - @Override - public void onDownMotionEvent(MotionEvent ev) { - } - - @Override - public void onMoveMotionEvent(MotionEvent ev, float diffX, float diffY) { - float translationY = ScrollUtils.getFloat(ViewHelper.getTranslationY(mInterceptionLayout) + diffY, -mToolbarView.getHeight(), 0); - ViewHelper.setTranslationY(mInterceptionLayout, translationY); - if (translationY < 0) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInterceptionLayout.getLayoutParams(); - lp.height = (int) (-translationY + getScreenHeight()); - mInterceptionLayout.requestLayout(); - } - } - - @Override - public void onUpOrCancelMotionEvent(MotionEvent ev) { - mScrolled = false; - adjustToolbar(mLastScrollState); - } - }; - - public Scrollable getCurrentScrollable() { - Fragment fragment = getCurrentFragment(); - if (fragment == null) { - return null; - } - View view = fragment.getView(); - if (view == null) { - return null; - } - return (Scrollable) view.findViewById(R.id.scroll); - } - - private void adjustToolbar(ScrollState scrollState) { - int toolbarHeight = mToolbarView.getHeight(); - final Scrollable scrollable = getCurrentScrollable(); - if (scrollable == null) { - return; - } - int scrollY = scrollable.getCurrentScrollY(); - if (scrollState == ScrollState.DOWN) { - showToolbar(); - } else if (scrollState == ScrollState.UP) { - if (toolbarHeight <= scrollY) { - hideToolbar(); - } else { - showToolbar(); - } - } else if (!toolbarIsShown() && !toolbarIsHidden()) { - // Toolbar is moving but doesn't know which to move: - // you can change this to hideToolbar() - showToolbar(); - } - } - - private Fragment getCurrentFragment() { - return mPagerAdapter.getItemAt(mPager.getCurrentItem()); - } - - private boolean toolbarIsShown() { - return ViewHelper.getTranslationY(mInterceptionLayout) == 0; - } - - private boolean toolbarIsHidden() { - return ViewHelper.getTranslationY(mInterceptionLayout) == -mToolbarView.getHeight(); - } - - private void showToolbar() { - animateToolbar(0); - } - - private void hideToolbar() { - animateToolbar(-mToolbarView.getHeight()); - } - - private void animateToolbar(final float toY) { - float layoutTranslationY = ViewHelper.getTranslationY(mInterceptionLayout); - if (layoutTranslationY != toY) { - ValueAnimator animator = ValueAnimator.ofFloat(ViewHelper.getTranslationY(mInterceptionLayout), toY).setDuration(200); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float translationY = (float) animation.getAnimatedValue(); - ViewHelper.setTranslationY(mInterceptionLayout, translationY); - if (translationY < 0) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInterceptionLayout.getLayoutParams(); - lp.height = (int) (-translationY + getScreenHeight()); - mInterceptionLayout.requestLayout(); - } - } - }); - animator.start(); - } - } - - private int getActionBarSize() { - TypedValue typedValue = new TypedValue(); - int[] textSizeAttr = new int[]{R.attr.actionBarSize}; - int indexOfAttrTextSize = 0; - TypedArray a = obtainStyledAttributes(typedValue.data, textSizeAttr); - int actionBarSize = a.getDimensionPixelSize(indexOfAttrTextSize, -1); - a.recycle(); - return actionBarSize; - } - - private int getScreenHeight() { - return findViewById(android.R.id.content).getHeight(); - } - - /** - * This adapter provides two types of fragments as an example. - * {@linkplain #createItem(int)} should be modified if you use this example for your app. - */ - private static class NavigationAdapter extends CacheFragmentStatePagerAdapter { - - private static final String[] TITLES = new String[]{"Applepie", "Butter Cookie", "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb", "Ice Cream Sandwich", "Jelly Bean", "KitKat", "Lollipop"}; - - public NavigationAdapter(FragmentManager fm) { - super(fm); - } - - @Override - protected Fragment createItem(int position) { - Fragment f; - final int pattern = position % 5; - switch (pattern) { - case 0: - f = new ViewPagerTab2ScrollViewFragment(); - break; - case 1: - f = new ViewPagerTab2ListViewFragment(); - break; - case 2: - f = new ViewPagerTab2RecyclerViewFragment(); - break; - case 3: - f = new ViewPagerTab2GridViewFragment(); - break; - case 4: - default: - f = new ViewPagerTab2WebViewFragment(); - break; - } - return f; - } - - @Override - public int getCount() { - return TITLES.length; - } - - @Override - public CharSequence getPageTitle(int position) { - return TITLES[position]; - } - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ActivityTest.java deleted file mode 100755 index e1dc35a63b..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ActivityTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.test.ActivityInstrumentationTestCase2; -import android.view.View; - -public class ViewPagerTab2ActivityTest extends ActivityInstrumentationTestCase2 { - - private ViewPagerTab2Activity activity; - - public ViewPagerTab2ActivityTest() { - super(ViewPagerTab2Activity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - } - - public void testScroll() throws Throwable { - for (int i = 0; i < 5; i++) { - UiTestUtils.swipeHorizontally(this, activity.findViewById(R.id.pager), UiTestUtils.Direction.LEFT); - getInstrumentation().waitForIdleSync(); - scroll(); - } - for (int i = 0; i < 5; i++) { - UiTestUtils.swipeHorizontally(this, activity.findViewById(R.id.pager), UiTestUtils.Direction.RIGHT); - getInstrumentation().waitForIdleSync(); - scroll(); - } - } - - public void scroll() throws Throwable { - View scrollable = ((View) activity.getCurrentScrollable()).findViewById(R.id.scroll); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - for (int i = 0; i < 5; i++) { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - scroll(); - - UiTestUtils.swipeHorizontally(this, activity.findViewById(R.id.pager), UiTestUtils.Direction.LEFT); - getInstrumentation().waitForIdleSync(); - } - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2GridViewFragment.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2GridViewFragment.java deleted file mode 100755 index 401752a985..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2GridViewFragment.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.ksoichiro.android.observablescrollview.ObservableGridView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; - -public class ViewPagerTab2GridViewFragment extends Fragment { - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_gridview, container, false); - - Activity parentActivity = getActivity(); - final ObservableGridView gridView = (ObservableGridView) view.findViewById(R.id.scroll); - UiTestUtils.setDummyData(getActivity(), gridView); - gridView.setTouchInterceptionViewGroup((ViewGroup) parentActivity.findViewById(R.id.container)); - - if (parentActivity instanceof ObservableScrollViewCallbacks) { - gridView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity); - } - return view; - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ListViewFragment.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ListViewFragment.java deleted file mode 100755 index 33daf0c8e9..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ListViewFragment.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; - -public class ViewPagerTab2ListViewFragment extends Fragment { - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_listview, container, false); - - Activity parentActivity = getActivity(); - final ObservableListView listView = (ObservableListView) view.findViewById(R.id.scroll); - UiTestUtils.setDummyData(getActivity(), listView); - listView.setTouchInterceptionViewGroup((ViewGroup) parentActivity.findViewById(R.id.container)); - - if (parentActivity instanceof ObservableScrollViewCallbacks) { - listView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity); - } - return view; - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2RecyclerViewFragment.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2RecyclerViewFragment.java deleted file mode 100755 index 1e20ed93b0..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2RecyclerViewFragment.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.widget.LinearLayoutManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; - -public class ViewPagerTab2RecyclerViewFragment extends Fragment { - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_recyclerview, container, false); - - Activity parentActivity = getActivity(); - final ObservableRecyclerView recyclerView = (ObservableRecyclerView) view.findViewById(R.id.scroll); - recyclerView.setLayoutManager(new LinearLayoutManager(parentActivity)); - recyclerView.setHasFixedSize(false); - UiTestUtils.setDummyData(getActivity(), recyclerView); - recyclerView.setTouchInterceptionViewGroup((ViewGroup) parentActivity.findViewById(R.id.container)); - - if (parentActivity instanceof ObservableScrollViewCallbacks) { - recyclerView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity); - } - return view; - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ScrollViewFragment.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ScrollViewFragment.java deleted file mode 100755 index 286e96e5ae..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2ScrollViewFragment.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; - -public class ViewPagerTab2ScrollViewFragment extends Fragment { - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_scrollview_noheader, container, false); - - final ObservableScrollView scrollView = (ObservableScrollView) view.findViewById(R.id.scroll); - Activity parentActivity = getActivity(); - scrollView.setTouchInterceptionViewGroup((ViewGroup) parentActivity.findViewById(R.id.container)); - if (parentActivity instanceof ObservableScrollViewCallbacks) { - scrollView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity); - } - return view; - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2WebViewFragment.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2WebViewFragment.java deleted file mode 100755 index 582873f306..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTab2WebViewFragment.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ObservableWebView; - -public class ViewPagerTab2WebViewFragment extends Fragment { - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_webview, container, false); - - final ObservableWebView webView = (ObservableWebView) view.findViewById(R.id.scroll); - webView.loadUrl("file:///android_asset/lipsum.html"); - Activity parentActivity = getActivity(); - webView.setTouchInterceptionViewGroup((ViewGroup) parentActivity.findViewById(R.id.container)); - if (parentActivity instanceof ObservableScrollViewCallbacks) { - webView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity); - } - return view; - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabActivity.java deleted file mode 100755 index 3915379038..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabActivity.java +++ /dev/null @@ -1,288 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBarActivity; -import android.support.v7.widget.Toolbar; -import android.view.View; - -import com.github.ksoichiro.android.observablescrollview.CacheFragmentStatePagerAdapter; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.ScrollUtils; -import com.github.ksoichiro.android.observablescrollview.Scrollable; -import com.google.samples.apps.iosched.ui.widget.SlidingTabLayout; -import com.nineoldandroids.view.ViewHelper; -import com.nineoldandroids.view.ViewPropertyAnimator; - -public class ViewPagerTabActivity extends ActionBarActivity implements ObservableScrollViewCallbacks { - - private View mHeaderView; - private View mToolbarView; - private int mBaseTranslationY; - private ViewPager mPager; - private NavigationAdapter mPagerAdapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_viewpagertab); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - mHeaderView = findViewById(R.id.header); - mToolbarView = findViewById(R.id.toolbar); - mPagerAdapter = new NavigationAdapter(getSupportFragmentManager()); - mPager = (ViewPager) findViewById(R.id.pager); - mPager.setAdapter(mPagerAdapter); - - SlidingTabLayout slidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs); - slidingTabLayout.setCustomTabView(R.layout.tab_indicator, android.R.id.text1); - slidingTabLayout.setSelectedIndicatorColors(getResources().getColor(R.color.accent)); - slidingTabLayout.setDistributeEvenly(true); - slidingTabLayout.setViewPager(mPager); - - // When the page is selected, other fragments' scrollY should be adjusted - // according to the toolbar status(shown/hidden) - slidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int i, float v, int i2) { - } - - @Override - public void onPageSelected(int i) { - propagateToolbarState(toolbarIsShown()); - } - - @Override - public void onPageScrollStateChanged(int i) { - } - }); - - propagateToolbarState(toolbarIsShown()); - } - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - if (dragging) { - int toolbarHeight = mToolbarView.getHeight(); - float currentHeaderTranslationY = ViewHelper.getTranslationY(mHeaderView); - if (firstScroll) { - if (-toolbarHeight < currentHeaderTranslationY) { - mBaseTranslationY = scrollY; - } - } - float headerTranslationY = ScrollUtils.getFloat(-(scrollY - mBaseTranslationY), -toolbarHeight, 0); - ViewPropertyAnimator.animate(mHeaderView).cancel(); - ViewHelper.setTranslationY(mHeaderView, headerTranslationY); - } - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - mBaseTranslationY = 0; - - Fragment fragment = getCurrentFragment(); - if (fragment == null) { - return; - } - View view = fragment.getView(); - if (view == null) { - return; - } - - // ObservableXxxViews have same API - // but currently they don't have any common interfaces. - adjustToolbar(scrollState, view); - } - - public Scrollable getCurrentScrollable() { - Fragment fragment = getCurrentFragment(); - if (fragment == null) { - return null; - } - View view = fragment.getView(); - if (view == null) { - return null; - } - return (Scrollable) view.findViewById(R.id.scroll); - } - - private void adjustToolbar(ScrollState scrollState, View view) { - int toolbarHeight = mToolbarView.getHeight(); - final Scrollable scrollView = (Scrollable) view.findViewById(R.id.scroll); - if (scrollView == null) { - return; - } - int scrollY = scrollView.getCurrentScrollY(); - if (scrollState == ScrollState.DOWN) { - showToolbar(); - } else if (scrollState == ScrollState.UP) { - if (toolbarHeight <= scrollY) { - hideToolbar(); - } else { - showToolbar(); - } - } else { - // Even if onScrollChanged occurs without scrollY changing, toolbar should be adjusted - if (toolbarIsShown() || toolbarIsHidden()) { - // Toolbar is completely moved, so just keep its state - // and propagate it to other pages - propagateToolbarState(toolbarIsShown()); - } else { - // Toolbar is moving but doesn't know which to move: - // you can change this to hideToolbar() - showToolbar(); - } - } - } - - public Fragment getCurrentFragment() { - return mPagerAdapter.getItemAt(mPager.getCurrentItem()); - } - - private void propagateToolbarState(boolean isShown) { - int toolbarHeight = mToolbarView.getHeight(); - - // Set scrollY for the fragments that are not created yet - mPagerAdapter.setScrollY(isShown ? 0 : toolbarHeight); - - // Set scrollY for the active fragments - for (int i = 0; i < mPagerAdapter.getCount(); i++) { - // Skip current item - if (i == mPager.getCurrentItem()) { - continue; - } - - // Skip destroyed or not created item - Fragment f = mPagerAdapter.getItemAt(i); - if (f == null) { - continue; - } - - View view = f.getView(); - if (view == null) { - continue; - } - propagateToolbarState(isShown, view, toolbarHeight); - } - } - - private void propagateToolbarState(boolean isShown, View view, int toolbarHeight) { - Scrollable scrollView = (Scrollable) view.findViewById(R.id.scroll); - if (scrollView == null) { - return; - } - if (isShown) { - // Scroll up - if (0 < scrollView.getCurrentScrollY()) { - scrollView.scrollVerticallyTo(0); - } - } else { - // Scroll down (to hide padding) - if (scrollView.getCurrentScrollY() < toolbarHeight) { - scrollView.scrollVerticallyTo(toolbarHeight); - } - } - } - - private boolean toolbarIsShown() { - return ViewHelper.getTranslationY(mHeaderView) == 0; - } - - private boolean toolbarIsHidden() { - return ViewHelper.getTranslationY(mHeaderView) == -mToolbarView.getHeight(); - } - - private void showToolbar() { - float headerTranslationY = ViewHelper.getTranslationY(mHeaderView); - if (headerTranslationY != 0) { - ViewPropertyAnimator.animate(mHeaderView).cancel(); - ViewPropertyAnimator.animate(mHeaderView).translationY(0).setDuration(200).start(); - } - propagateToolbarState(true); - } - - private void hideToolbar() { - float headerTranslationY = ViewHelper.getTranslationY(mHeaderView); - int toolbarHeight = mToolbarView.getHeight(); - if (headerTranslationY != -toolbarHeight) { - ViewPropertyAnimator.animate(mHeaderView).cancel(); - ViewPropertyAnimator.animate(mHeaderView).translationY(-toolbarHeight).setDuration(200).start(); - } - propagateToolbarState(false); - } - - /** - * This adapter provides two types of fragments as an example. - * {@linkplain #createItem(int)} should be modified if you use this example for your app. - */ - private static class NavigationAdapter extends CacheFragmentStatePagerAdapter { - - private static final String[] TITLES = new String[]{"Applepie", "Butter Cookie", "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb", "Ice Cream Sandwich", "Jelly Bean", "KitKat", "Lollipop"}; - - private int mScrollY; - - public NavigationAdapter(FragmentManager fm) { - super(fm); - } - - public void setScrollY(int scrollY) { - mScrollY = scrollY; - } - - @Override - protected Fragment createItem(int position) { - // Initialize fragments. - // Please be sure to pass scroll position to each fragments using setArguments. - Fragment f; - final int pattern = position % 3; - switch (pattern) { - case 0: { - f = new ViewPagerTabScrollViewFragment(); - if (0 <= mScrollY) { - Bundle args = new Bundle(); - args.putInt(ViewPagerTabScrollViewFragment.ARG_SCROLL_Y, mScrollY); - f.setArguments(args); - } - break; - } - case 1: { - f = new ViewPagerTabListViewFragment(); - if (0 < mScrollY) { - Bundle args = new Bundle(); - args.putInt(ViewPagerTabListViewFragment.ARG_INITIAL_POSITION, 1); - f.setArguments(args); - } - break; - } - case 2: - default: { - f = new ViewPagerTabRecyclerViewFragment(); - if (0 < mScrollY) { - Bundle args = new Bundle(); - args.putInt(ViewPagerTabRecyclerViewFragment.ARG_INITIAL_POSITION, 1); - f.setArguments(args); - } - break; - } - } - return f; - } - - @Override - public int getCount() { - return TITLES.length; - } - - @Override - public CharSequence getPageTitle(int position) { - return TITLES[position]; - } - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabActivityTest.java deleted file mode 100755 index 6125d6ff26..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabActivityTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.test.ActivityInstrumentationTestCase2; -import android.view.View; - -public class ViewPagerTabActivityTest extends ActivityInstrumentationTestCase2 { - - private ViewPagerTabActivity activity; - - public ViewPagerTabActivityTest() { - super(ViewPagerTabActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - } - - public void testScroll() throws Throwable { - for (int i = 0; i < 3; i++) { - UiTestUtils.swipeHorizontally(this, activity.findViewById(R.id.pager), UiTestUtils.Direction.LEFT); - getInstrumentation().waitForIdleSync(); - scroll(); - } - for (int i = 0; i < 3; i++) { - UiTestUtils.swipeHorizontally(this, activity.findViewById(R.id.pager), UiTestUtils.Direction.RIGHT); - getInstrumentation().waitForIdleSync(); - scroll(); - } - } - - public void scroll() throws Throwable { - View scrollable = ((View) activity.getCurrentScrollable()).findViewById(R.id.scroll); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - for (int i = 0; i < 3; i++) { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - scroll(); - - UiTestUtils.swipeHorizontally(this, activity.findViewById(R.id.pager), UiTestUtils.Direction.LEFT); - getInstrumentation().waitForIdleSync(); - } - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabListViewFragment.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabListViewFragment.java deleted file mode 100755 index 447e54b2d4..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabListViewFragment.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.ksoichiro.android.observablescrollview.ObservableListView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollUtils; - -public class ViewPagerTabListViewFragment extends Fragment { - - public static final String ARG_INITIAL_POSITION = "ARG_INITIAL_POSITION"; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_listview, container, false); - - Activity parentActivity = getActivity(); - final ObservableListView listView = (ObservableListView) view.findViewById(R.id.scroll); - UiTestUtils.setDummyDataWithHeader(getActivity(), listView, inflater.inflate(R.layout.padding, null)); - - if (parentActivity instanceof ObservableScrollViewCallbacks) { - // Scroll to the specified position after layout - Bundle args = getArguments(); - if (args != null && args.containsKey(ARG_INITIAL_POSITION)) { - final int initialPosition = args.getInt(ARG_INITIAL_POSITION, 0); - ScrollUtils.addOnGlobalLayoutListener(listView, new Runnable() { - @Override - public void run() { - // scrollTo() doesn't work, should use setSelection() - listView.setSelection(initialPosition); - } - }); - } - listView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity); - } - return view; - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabRecyclerViewFragment.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabRecyclerViewFragment.java deleted file mode 100755 index f91988e8a7..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabRecyclerViewFragment.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.widget.LinearLayoutManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollUtils; - -public class ViewPagerTabRecyclerViewFragment extends Fragment { - - public static final String ARG_INITIAL_POSITION = "ARG_INITIAL_POSITION"; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_recyclerview, container, false); - - Activity parentActivity = getActivity(); - final ObservableRecyclerView recyclerView = (ObservableRecyclerView) view.findViewById(R.id.scroll); - recyclerView.setLayoutManager(new LinearLayoutManager(parentActivity)); - recyclerView.setHasFixedSize(false); - View headerView = LayoutInflater.from(parentActivity).inflate(R.layout.padding, null); - UiTestUtils.setDummyDataWithHeader(getActivity(), recyclerView, headerView); - - if (parentActivity instanceof ObservableScrollViewCallbacks) { - // Scroll to the specified offset after layout - Bundle args = getArguments(); - if (args != null && args.containsKey(ARG_INITIAL_POSITION)) { - final int initialPosition = args.getInt(ARG_INITIAL_POSITION, 0); - ScrollUtils.addOnGlobalLayoutListener(recyclerView, new Runnable() { - @Override - public void run() { - recyclerView.scrollVerticallyToPosition(initialPosition); - } - }); - } - recyclerView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity); - } - return view; - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabScrollViewFragment.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabScrollViewFragment.java deleted file mode 100755 index 16c6265836..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/ViewPagerTabScrollViewFragment.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2014 Soichiro Kashima - * - * 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.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollView; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ScrollUtils; - -public class ViewPagerTabScrollViewFragment extends Fragment { - - public static final String ARG_SCROLL_Y = "ARG_SCROLL_Y"; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_scrollview, container, false); - - final ObservableScrollView scrollView = (ObservableScrollView) view.findViewById(R.id.scroll); - Activity parentActivity = getActivity(); - if (parentActivity instanceof ObservableScrollViewCallbacks) { - // Scroll to the specified offset after layout - Bundle args = getArguments(); - if (args != null && args.containsKey(ARG_SCROLL_Y)) { - final int scrollY = args.getInt(ARG_SCROLL_Y, 0); - ScrollUtils.addOnGlobalLayoutListener(scrollView, new Runnable() { - @Override - public void run() { - scrollView.scrollTo(0, scrollY); - } - }); - } - scrollView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity); - } - return view; - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/WebViewActivity.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/WebViewActivity.java deleted file mode 100755 index dd13963a80..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/WebViewActivity.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.os.Bundle; - -import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; -import com.github.ksoichiro.android.observablescrollview.ObservableWebView; -import com.github.ksoichiro.android.observablescrollview.ScrollState; -import com.github.ksoichiro.android.observablescrollview.Scrollable; - -public class WebViewActivity extends Activity implements ObservableScrollViewCallbacks { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_webview); - ObservableWebView scrollable = (ObservableWebView) findViewById(R.id.scrollable); - scrollable.setScrollViewCallbacks(this); - scrollable.loadUrl("file:///android_asset/lipsum.html"); - } - - @Override - public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { - } - - @Override - public void onDownMotionEvent() { - } - - @Override - public void onUpOrCancelMotionEvent(ScrollState scrollState) { - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/WebViewActivityTest.java b/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/WebViewActivityTest.java deleted file mode 100755 index d6cc295eba..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/github/ksoichiro/android/observablescrollview/test/WebViewActivityTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.ksoichiro.android.observablescrollview.test; - -import android.app.Activity; -import android.test.ActivityInstrumentationTestCase2; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import com.github.ksoichiro.android.observablescrollview.ObservableWebView; - -public class WebViewActivityTest extends ActivityInstrumentationTestCase2 { - - private Activity activity; - private ObservableWebView scrollable; - - public WebViewActivityTest() { - super(WebViewActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(true); - activity = getActivity(); - scrollable = (ObservableWebView) activity.findViewById(R.id.scrollable); - } - - public void testScroll() throws Throwable { - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.UP); - getInstrumentation().waitForIdleSync(); - - UiTestUtils.swipeVertically(this, scrollable, UiTestUtils.Direction.DOWN); - getInstrumentation().waitForIdleSync(); - } - - public void testSaveAndRestoreInstanceState() throws Throwable { - UiTestUtils.saveAndRestoreInstanceState(this, activity); - testScroll(); - } - - public void testScrollVerticallyTo() throws Throwable { - final DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); - runTestOnUiThread(new Runnable() { - @Override - public void run() { - scrollable.scrollVerticallyTo((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, metrics)); - } - }); - getInstrumentation().waitForIdleSync(); - - runTestOnUiThread(new Runnable() { - @Override - public void run() { - scrollable.scrollVerticallyTo(0); - } - }); - getInstrumentation().waitForIdleSync(); - } -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/google/samples/apps/iosched/ui/widget/SlidingTabLayout.java b/eclipse-compile/observable/java2/androidTest/java/com/google/samples/apps/iosched/ui/widget/SlidingTabLayout.java deleted file mode 100755 index 06f60f7afc..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/google/samples/apps/iosched/ui/widget/SlidingTabLayout.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2014 Google Inc. All rights reserved. - * - * 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.google.samples.apps.iosched.ui.widget; - -import android.content.Context; -import android.graphics.Typeface; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.util.SparseArray; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.HorizontalScrollView; -import android.widget.LinearLayout; -import android.widget.TextView; - -/** - * To be used with ViewPager to provide a tab indicator component which give constant feedback as to - * the user's scroll progress. - *

- * To use the component, simply add it to your view hierarchy. Then in your - * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call - * {@link #setViewPager(android.support.v4.view.ViewPager)} providing it the ViewPager this layout is being used for. - *

- * The colors can be customized in two ways. The first and simplest is to provide an array of colors - * via {@link #setSelectedIndicatorColors(int...)}. The - * alternative is via the {@link com.google.samples.apps.iosched.ui.widget.SlidingTabLayout.TabColorizer} interface which provides you complete control over - * which color is used for any individual position. - *

- * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, - * providing the layout ID of your custom layout. - */ -public class SlidingTabLayout extends HorizontalScrollView { - /** - * Allows complete control over the colors drawn in the tab layout. Set with - * {@link #setCustomTabColorizer(com.google.samples.apps.iosched.ui.widget.SlidingTabLayout.TabColorizer)}. - */ - public interface TabColorizer { - - /** - * @return return the color of the indicator used when {@code position} is selected. - */ - int getIndicatorColor(int position); - - } - - private static final int TITLE_OFFSET_DIPS = 24; - private static final int TAB_VIEW_PADDING_DIPS = 16; - private static final int TAB_VIEW_TEXT_SIZE_SP = 12; - - private int mTitleOffset; - - private int mTabViewLayoutId; - private int mTabViewTextViewId; - private boolean mDistributeEvenly; - - private ViewPager mViewPager; - private SparseArray mContentDescriptions = new SparseArray(); - private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; - - private final SlidingTabStrip mTabStrip; - - public SlidingTabLayout(Context context) { - this(context, null); - } - - public SlidingTabLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - // Disable the Scroll Bar - setHorizontalScrollBarEnabled(false); - // Make sure that the Tab Strips fills this View - setFillViewport(true); - - mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); - - mTabStrip = new SlidingTabStrip(context); - addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } - - /** - * Set the custom {@link com.google.samples.apps.iosched.ui.widget.SlidingTabLayout.TabColorizer} to be used. - * - * If you only require simple custmisation then you can use - * {@link #setSelectedIndicatorColors(int...)} to achieve - * similar effects. - */ - public void setCustomTabColorizer(TabColorizer tabColorizer) { - mTabStrip.setCustomTabColorizer(tabColorizer); - } - - public void setDistributeEvenly(boolean distributeEvenly) { - mDistributeEvenly = distributeEvenly; - } - - /** - * Sets the colors to be used for indicating the selected tab. These colors are treated as a - * circular array. Providing one color will mean that all tabs are indicated with the same color. - */ - public void setSelectedIndicatorColors(int... colors) { - mTabStrip.setSelectedIndicatorColors(colors); - } - - /** - * Set the {@link android.support.v4.view.ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are - * required to set any {@link android.support.v4.view.ViewPager.OnPageChangeListener} through this method. This is so - * that the layout can update it's scroll position correctly. - * - * @see android.support.v4.view.ViewPager#setOnPageChangeListener(android.support.v4.view.ViewPager.OnPageChangeListener) - */ - public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { - mViewPagerPageChangeListener = listener; - } - - /** - * Set the custom layout to be inflated for the tab views. - * - * @param layoutResId Layout id to be inflated - * @param textViewId id of the {@link android.widget.TextView} in the inflated view - */ - public void setCustomTabView(int layoutResId, int textViewId) { - mTabViewLayoutId = layoutResId; - mTabViewTextViewId = textViewId; - } - - /** - * Sets the associated view pager. Note that the assumption here is that the pager content - * (number of tabs and tab titles) does not change after this call has been made. - */ - public void setViewPager(ViewPager viewPager) { - mTabStrip.removeAllViews(); - - mViewPager = viewPager; - if (viewPager != null) { - viewPager.setOnPageChangeListener(new InternalViewPagerListener()); - populateTabStrip(); - } - } - - /** - * Create a default view to be used for tabs. This is called if a custom tab view is not set via - * {@link #setCustomTabView(int, int)}. - */ - protected TextView createDefaultTabView(Context context) { - TextView textView = new TextView(context); - textView.setGravity(Gravity.CENTER); - textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); - textView.setTypeface(Typeface.DEFAULT_BOLD); - textView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - TypedValue outValue = new TypedValue(); - getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, - outValue, true); - textView.setBackgroundResource(outValue.resourceId); - textView.setAllCaps(true); - - int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); - textView.setPadding(padding, padding, padding, padding); - - return textView; - } - - private void populateTabStrip() { - final PagerAdapter adapter = mViewPager.getAdapter(); - final OnClickListener tabClickListener = new TabClickListener(); - - for (int i = 0; i < adapter.getCount(); i++) { - View tabView = null; - TextView tabTitleView = null; - - if (mTabViewLayoutId != 0) { - // If there is a custom tab view layout id set, try and inflate it - tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, - false); - tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); - } - - if (tabView == null) { - tabView = createDefaultTabView(getContext()); - } - - if (tabTitleView == null && TextView.class.isInstance(tabView)) { - tabTitleView = (TextView) tabView; - } - - if (mDistributeEvenly) { - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tabView.getLayoutParams(); - lp.width = 0; - lp.weight = 1; - } - - tabTitleView.setText(adapter.getPageTitle(i)); - tabView.setOnClickListener(tabClickListener); - String desc = mContentDescriptions.get(i, null); - if (desc != null) { - tabView.setContentDescription(desc); - } - - mTabStrip.addView(tabView); - if (i == mViewPager.getCurrentItem()) { - tabView.setSelected(true); - } - } - } - - public void setContentDescription(int i, String desc) { - mContentDescriptions.put(i, desc); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (mViewPager != null) { - scrollToTab(mViewPager.getCurrentItem(), 0); - } - } - - private void scrollToTab(int tabIndex, int positionOffset) { - final int tabStripChildCount = mTabStrip.getChildCount(); - if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { - return; - } - - View selectedChild = mTabStrip.getChildAt(tabIndex); - if (selectedChild != null) { - int targetScrollX = selectedChild.getLeft() + positionOffset; - - if (tabIndex > 0 || positionOffset > 0) { - // If we're not at the first child and are mid-scroll, make sure we obey the offset - targetScrollX -= mTitleOffset; - } - - scrollTo(targetScrollX, 0); - } - } - - private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { - private int mScrollState; - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - int tabStripChildCount = mTabStrip.getChildCount(); - if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { - return; - } - - mTabStrip.onViewPagerPageChanged(position, positionOffset); - - View selectedTitle = mTabStrip.getChildAt(position); - int extraOffset = (selectedTitle != null) - ? (int) (positionOffset * selectedTitle.getWidth()) - : 0; - scrollToTab(position, extraOffset); - - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, - positionOffsetPixels); - } - } - - @Override - public void onPageScrollStateChanged(int state) { - mScrollState = state; - - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageScrollStateChanged(state); - } - } - - @Override - public void onPageSelected(int position) { - if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { - mTabStrip.onViewPagerPageChanged(position, 0f); - scrollToTab(position, 0); - } - for (int i = 0; i < mTabStrip.getChildCount(); i++) { - mTabStrip.getChildAt(i).setSelected(position == i); - } - if (mViewPagerPageChangeListener != null) { - mViewPagerPageChangeListener.onPageSelected(position); - } - } - - } - - private class TabClickListener implements OnClickListener { - @Override - public void onClick(View v) { - for (int i = 0; i < mTabStrip.getChildCount(); i++) { - if (v == mTabStrip.getChildAt(i)) { - mViewPager.setCurrentItem(i); - return; - } - } - } - } - -} diff --git a/eclipse-compile/observable/java2/androidTest/java/com/google/samples/apps/iosched/ui/widget/SlidingTabStrip.java b/eclipse-compile/observable/java2/androidTest/java/com/google/samples/apps/iosched/ui/widget/SlidingTabStrip.java deleted file mode 100755 index 803c4fb41d..0000000000 --- a/eclipse-compile/observable/java2/androidTest/java/com/google/samples/apps/iosched/ui/widget/SlidingTabStrip.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2014 Google Inc. All rights reserved. - * - * 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.google.samples.apps.iosched.ui.widget; - -import android.R; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; -import android.widget.LinearLayout; - -class SlidingTabStrip extends LinearLayout { - - private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0; - private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; - private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 3; - private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5; - - private final int mBottomBorderThickness; - private final Paint mBottomBorderPaint; - - private final int mSelectedIndicatorThickness; - private final Paint mSelectedIndicatorPaint; - - private final int mDefaultBottomBorderColor; - - private int mSelectedPosition; - private float mSelectionOffset; - - private SlidingTabLayout.TabColorizer mCustomTabColorizer; - private final SimpleTabColorizer mDefaultTabColorizer; - - SlidingTabStrip(Context context) { - this(context, null); - } - - SlidingTabStrip(Context context, AttributeSet attrs) { - super(context, attrs); - setWillNotDraw(false); - - final float density = getResources().getDisplayMetrics().density; - - TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true); - final int themeForegroundColor = outValue.data; - - mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, - DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); - - mDefaultTabColorizer = new SimpleTabColorizer(); - mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); - - mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); - mBottomBorderPaint = new Paint(); - mBottomBorderPaint.setColor(mDefaultBottomBorderColor); - - mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); - mSelectedIndicatorPaint = new Paint(); - } - - void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { - mCustomTabColorizer = customTabColorizer; - invalidate(); - } - - void setSelectedIndicatorColors(int... colors) { - // Make sure that the custom colorizer is removed - mCustomTabColorizer = null; - mDefaultTabColorizer.setIndicatorColors(colors); - invalidate(); - } - - void onViewPagerPageChanged(int position, float positionOffset) { - mSelectedPosition = position; - mSelectionOffset = positionOffset; - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - final int height = getHeight(); - final int childCount = getChildCount(); - final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null - ? mCustomTabColorizer - : mDefaultTabColorizer; - - // Thick colored underline below the current selection - if (childCount > 0) { - View selectedTitle = getChildAt(mSelectedPosition); - int left = selectedTitle.getLeft(); - int right = selectedTitle.getRight(); - int color = tabColorizer.getIndicatorColor(mSelectedPosition); - - if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { - int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); - if (color != nextColor) { - color = blendColors(nextColor, color, mSelectionOffset); - } - - // Draw the selection partway between the tabs - View nextTitle = getChildAt(mSelectedPosition + 1); - left = (int) (mSelectionOffset * nextTitle.getLeft() + - (1.0f - mSelectionOffset) * left); - right = (int) (mSelectionOffset * nextTitle.getRight() + - (1.0f - mSelectionOffset) * right); - } - - mSelectedIndicatorPaint.setColor(color); - - canvas.drawRect(left, height - mSelectedIndicatorThickness, right, - height, mSelectedIndicatorPaint); - } - - // Thin underline along the entire bottom edge - canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); - } - - /** - * Set the alpha value of the {@code color} to be the given {@code alpha} value. - */ - private static int setColorAlpha(int color, byte alpha) { - return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); - } - - /** - * Blend {@code color1} and {@code color2} using the given ratio. - * - * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, - * 0.0 will return {@code color2}. - */ - private static int blendColors(int color1, int color2, float ratio) { - final float inverseRation = 1f - ratio; - float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); - float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); - float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); - return Color.rgb((int) r, (int) g, (int) b); - } - - private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer { - private int[] mIndicatorColors; - - @Override - public final int getIndicatorColor(int position) { - return mIndicatorColors[position % mIndicatorColors.length]; - } - - void setIndicatorColors(int... colors) { - mIndicatorColors = colors; - } - } -} diff --git a/eclipse-compile/observable/java2/androidTest/res/color/tab_text_color.xml b/eclipse-compile/observable/java2/androidTest/res/color/tab_text_color.xml deleted file mode 100755 index 48f21f866b..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/color/tab_text_color.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_gridview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_gridview.xml deleted file mode 100755 index d5ddb31dda..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_gridview.xml +++ /dev/null @@ -1,20 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_listview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_listview.xml deleted file mode 100755 index 18dd210b48..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_listview.xml +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_recyclerview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_recyclerview.xml deleted file mode 100755 index dce42c4710..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_recyclerview.xml +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_scrollview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_scrollview.xml deleted file mode 100755 index 972537fe60..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_scrollview.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_gridview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_gridview.xml deleted file mode 100755 index 043e6175c6..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_gridview.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_listview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_listview.xml deleted file mode 100755 index 05c8e29976..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_listview.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_recyclerview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_recyclerview.xml deleted file mode 100755 index c236b3d15a..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_recyclerview.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_scrollview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_scrollview.xml deleted file mode 100755 index de7cf631c7..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_scrollview.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_webview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_webview.xml deleted file mode 100755 index 8e834969ed..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_touchinterception_webview.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_viewpagertab.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_viewpagertab.xml deleted file mode 100755 index c55804bd5f..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_viewpagertab.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_viewpagertab2.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_viewpagertab2.xml deleted file mode 100755 index 510ed95cbe..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_viewpagertab2.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/activity_webview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/activity_webview.xml deleted file mode 100755 index 75d62617cf..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/activity_webview.xml +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_gridview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/fragment_gridview.xml deleted file mode 100755 index 65241a4d05..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_gridview.xml +++ /dev/null @@ -1,20 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_listview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/fragment_listview.xml deleted file mode 100755 index 08d5d41b8f..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_listview.xml +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_recyclerview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/fragment_recyclerview.xml deleted file mode 100755 index 49f0959042..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_recyclerview.xml +++ /dev/null @@ -1,20 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_scrollview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/fragment_scrollview.xml deleted file mode 100755 index 0be95c8542..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_scrollview.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_scrollview_noheader.xml b/eclipse-compile/observable/java2/androidTest/res/layout/fragment_scrollview_noheader.xml deleted file mode 100755 index 2ac408dd4b..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_scrollview_noheader.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_webview.xml b/eclipse-compile/observable/java2/androidTest/res/layout/fragment_webview.xml deleted file mode 100755 index b02d9ec387..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/fragment_webview.xml +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/padding.xml b/eclipse-compile/observable/java2/androidTest/res/layout/padding.xml deleted file mode 100755 index b3550e378e..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/padding.xml +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/eclipse-compile/observable/java2/androidTest/res/layout/tab_indicator.xml b/eclipse-compile/observable/java2/androidTest/res/layout/tab_indicator.xml deleted file mode 100755 index 3bf50059c8..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/layout/tab_indicator.xml +++ /dev/null @@ -1,25 +0,0 @@ - - \ No newline at end of file diff --git a/eclipse-compile/observable/java2/androidTest/res/values/colors.xml b/eclipse-compile/observable/java2/androidTest/res/values/colors.xml deleted file mode 100755 index 221c75c779..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/values/colors.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - #009688 - #00796b - #eeff41 - - diff --git a/eclipse-compile/observable/java2/androidTest/res/values/dimens.xml b/eclipse-compile/observable/java2/androidTest/res/values/dimens.xml deleted file mode 100755 index 286c5be17f..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/values/dimens.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - 48dp - 72dp - 56dp - 16dp - diff --git a/eclipse-compile/observable/java2/androidTest/res/values/strings.xml b/eclipse-compile/observable/java2/androidTest/res/values/strings.xml deleted file mode 100755 index 6f8f606284..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/values/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - Lorem ipsum dolor sit amet, ut duis lorem provident sed felis blandit, condimentum donec lectus ipsum et mauris, morbi porttitor interdum feugiat nulla donec sodales, vestibulum nisl primis a molestie vestibulum quam, sapien mauris metus risus suspendisse magnis. Augue viverra nulla faucibus egestas eu, a etiam id congue rutrum ante, arcu tincidunt donec quam felis at ornare, iaculis ligula sodales venenatis commodo volutpat neque, suspendisse elit praesent tellus felis mi amet. Inceptos amet tempor lectus lorem est non, ac donec ac libero neque mauris, tellus ante metus eget leo consequat. Scelerisque dolor curabitur pretium blandit ut feugiat, amet lacus pulvinar justo convallis ut, sed natoque ipsum urna posuere nibh eu. Sed at sed vulputate sit orci, facilisis a aliquam tellus quam aliquam, eu aliquam donec at molestie ante, pellentesque mauris lorem ultrices libero faucibus porta, imperdiet adipiscing sit hac diam ut nulla. Lacus enim elit pulvinar donec vehicula dapibus, accumsan purus officia cursus dolor sapien, eu amet dis mauris mi nulla ut. Non accusamus etiam pede non urna tempus, vestibulum aliquam tortor eget pharetra sodales, in vestibulum ut justo orci nulla, lobortis purus sem semper consectetuer magni purus. Dolor a leo vestibulum amet ut sit, arcu ut eaque urna fusce aliquet turpis, sed fermentum sed vestibulum nisl pede, tristique enim lorem posuere in laborum ut. Vestibulum id id justo leo nulla, magna lobortis ullamcorper et dignissim pellentesque, duis suspendisse quis id lorem ante. Vivamus a nullam ante adipiscing amet, mi vel consectetuer nunc aenean pede quisque, eget rhoncus dis porttitor habitant nunc vivamus, duis cubilia blandit non donec justo dictumst, praesent vitae nulla nam pulvinar urna. Adipiscing adipiscing justo urna pulvinar imperdiet nullam, vitae fusce rhoncus proin nonummy suscipit, ullamcorper amet et non potenti platea ultrices, mauris nullam sapien nunc justo vel, eu semper pellentesque arcu fusce augue. Malesuada mauris nibh sit a a scelerisque, velit sem lectus tellus convallis consectetuer, ultricies auctor a ante eros amet sed.\n\n -Risus lacus duis leo platea wisi, felis maecenas rutrum in id in donec, non id a potenti libero eget, posuere elit ea sed pellentesque quis. Sunt lacus urna lorem elit duis, nibh donec purus quisque consectetuer dolor, neque vestibulum proin ornare eros nonummy phasellus. Iaculis cras eu at egestas dolor montes, viverra quisque malesuada consectetuer semper maecenas, a sed vitae donec tempor aliqua metus, ornare mollis suscipit et erat fusce, sit orci aut auctor elementum fames aliquam. Platea dui integer magnis non metus, minus dignissimos ante massa nostra et, rutrum sapien egestas quis sapien donec donec. Erat sit a eros aenean natoque, quam libero id lorem enim proin, lorem ipsum fermentum mattis metus et. Aliquam aliquet suscipit purus conubia at neque, platea vivamus vestibulum nulla quibusdam senectus, et morbi lectus malesuada gravida donec, elementum sit convallis pellentesque velit amet. Et eveniet viverra vehicula consectetuer justo, provident sed commodo non lacinia velit, tempor phasellus vel leo nisl cras, vivamus et arcu interdum dui eu amet. Volutpat wisi rhoncus vel turpis diam quibusdam, dapibus elit est quisque cubilia mauris, nulla elit magna tempor accumsan bibendum, lorem varius sed interdum eget mattis, scelerisque egestas feugiat donec dui molestie. Leo facilisis nisl sit montes ligula sed, enim commodo consectetuer nunc est et, ut sed vehicula dolor luctus elit. Fermentum cras donec eget nibh est vel, sed justo risus et pharetra diam, eu vivamus egestas ligula risus diam, sed justo eget hac ut mauris. Vestibulum diam nec vitae mi eget suspendisse, aenean arcu purus facilisis purus class in, id aliquam sit id scelerisque sapien etiam. Ut nullam sit sed at mauris lobortis, consequat dolor autem ipsum euismod nulla, elit quis proin eget conubia varius, erat arcu massa mus in mauris, scelerisque ut eu sollicitudin libero leo urna.\n\n -Consectetuer luctus tempor elit ut dolor ligula, quis dui per dui hendrerit ante sagittis, in quisque pretium in eleifend enim. Condimentum iaculis vitae feugiat dis tellus vel, lectus dolor nec dui nulla nascetur, et pellentesque curabitur lorem leo velit eget. Id nascetur arcu lobortis suspendisse imperdiet urna, natoque nascetur ante in porta a, interdum hendrerit mi bibendum platea tellus, urna in enim ornare vestibulum faucibus enim. Leo fusce egestas ante nec volutpat, in tempor vel facilisis potenti ut, pede at non lorem a commodo, nulla dolor orci interdum vestibulum nulla. Dui nulla vestibulum quisque a pharetra porta, integer nec ipsum nec sed dui pharetra, magna et dignissim ipsum sed dictum, litora eros vivamus scelerisque libero ipsum. Sed ac ac lorem molestie adipiscing morbi, pellentesque imperdiet nunc quis morbi amet ante, libero dui ligula nec risus neque et, velit nonummy phasellus et facilisi amet, ligula in elementum non sapien pulvinar faucibus. Eu leo ut posuere sed aliquet, tincidunt vel urna volutpat tempus sem, sit felis aliquet vestibulum condimentum sit, amet nibh vel tellus purus ullamcorper libero, nulla vestibulum pede ut vestibulum pretium. Eu nulla vestibulum a neque in metus, quisquam nam sed cursus eget luctus, pede ultrices nec sed dignissim pellentesque, sit class cursus metus nulla placerat mauris, consequat mollis neque vivamus amet pede. Mauris dolor nulla diam eros bibendum, quam ante vestibulum morbi non ligula vel, molestie curabitur rhoncus nulla euismod interdum non. Nulla fringilla lorem mollis ad massa, sit molestie nibh lorem arcu volutpat, accumsan commodo lectus eu et donec, sit tempor tempus rutrum in curabitur amet. Nec urna euismod a tincidunt commodo, eu pede turpis libero vitae viverra, ante vestibulum nam non habitasse potenti, mauris imperdiet in in nunc convallis. Et nostra wisi in est accumsan vehicula, quisque vitae felis mauris sed vulputate nec, ante imperdiet sollicitudin massa iaculis massa sit.\n\n -Quam libero nulla netus eu porta curae, ut nulla bibendum facilisis et urna sed, quis congue vestibulum aliquam interdum etiam. Nulla vel lobortis ullamcorper vitae excepturi, neque urna feugiat lectus vel lacinia, massa pretium orci eu metus neque vulputate. Imperdiet ac velit rhoncus nulla malesuada nullam, nec pulvinar justo gravida lorem rutrum magna, habitasse repudiandae mi eros vestibulum ante, nec euismod dui iaculis in turpis pretium, ac id metus egestas proin lacus lectus. Laoreet lorem nec vitae risus erat arcu, vitae quam ut in ante tristique, porta dolor pede quam et odio nam, arcu lacus sem congue ante cursus massa. Et mattis sagittis erat accumsan fusce quam, vehicula ligula beatae natoque fusce sodales conubia, habitasse metus cum magnis viverra nam cursus, egestas urna wisi primis blandit eu magna, eget libero elit lacus lorem dis aliquam. Ut mauris ante natoque lacus massa, justo a lectus sodales enim adipiscing id, accumsan ut ipsum vestibulum sed enim auctor, vitae congue tincidunt id phasellus lacinia scelerisque, tincidunt sapien nulla euismod volutpat iaculis. Platea sociis nec aliquet nec molestie, in mi et augue sapien in vivamus, integer fames proin vitae in ullamcorper et. Fringilla etiam sapiente rhoncus suspendisse nec id, lobortis cras eget egestas dui ac nec, justo lacus ut lorem bibendum quia eros, eget a gravida id donec nunc suscipit, porta sed in sodales non rutrum. Lectus vel dui elementum pellentesque magna aliquam, vitae non sit pede et fusce nibh, id id deserunt ornare dui sit condimentum, in adipiscing imperdiet turpis nam aliquet, facilisis metus magna lacus wisi facilisis tortor. Vulputate elit accumsan quam amet ligula, suspendisse lacus mi nonummy integer urna, libero nulla nunc varius in odio, laoreet nulla amet placerat amet nec. Consectetuer vel massa hendrerit vitae iaculis id, sed ut ut laudantium odio in, elit vestibulum duis ante maecenas interdum in, neque vehicula ultrices varius in quam, pede tellus pellentesque sed nullam quis. - - diff --git a/eclipse-compile/observable/java2/androidTest/res/values/styles.xml b/eclipse-compile/observable/java2/androidTest/res/values/styles.xml deleted file mode 100755 index 4c1781a192..0000000000 --- a/eclipse-compile/observable/java2/androidTest/res/values/styles.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - diff --git a/eclipse-compile/observable/project.properties b/eclipse-compile/observable/project.properties deleted file mode 100644 index 0a24296b13..0000000000 --- a/eclipse-compile/observable/project.properties +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - -# Indicates whether an apk should be generated for each density. -split.density=false -# Project target. -target=android-21 -dex.force.jumbo=true -android.library=true -android.library.reference.1=../appcompat diff --git a/eclipse-compile/observable/res/.gitkeep b/eclipse-compile/observable/res/.gitkeep deleted file mode 100755 index e69de29bb2..0000000000 diff --git a/settings.gradle b/settings.gradle index 4c604d0ff3..169ef5962a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,5 @@ include ':OsmAnd-java' +include ':eclipse-compile:appcompat' include ':OsmAnd' include ':plugins:OsmAnd-AddressPlugin' include ':plugins:Osmand-ParkingPlugin'