From 019522cc93d947b5075ca90547b9e13f08787739 Mon Sep 17 00:00:00 2001 From: Trumeet Date: Thu, 3 Aug 2017 15:12:06 +0800 Subject: First Commit --- setupwizardlib/.gitignore | 1 + setupwizardlib/build.gradle | 27 ++ setupwizardlib/src/main/AndroidManifest.xml | 25 ++ .../com/android/setupwizardlib/GlifLayout.java | 291 ++++++++++++ .../com/android/setupwizardlib/GlifListLayout.java | 179 ++++++++ .../setupwizardlib/GlifPatternDrawable.java | 317 +++++++++++++ .../setupwizardlib/SetupWizardItemsLayout.java | 56 +++ .../android/setupwizardlib/SetupWizardLayout.java | 481 ++++++++++++++++++++ .../setupwizardlib/SetupWizardListLayout.java | 164 +++++++ .../com/android/setupwizardlib/TemplateLayout.java | 196 ++++++++ .../android/setupwizardlib/annotations/Keep.java | 49 ++ .../annotations/VisibleForTesting.java | 50 +++ .../gesture/ConsecutiveTapsGestureDetector.java | 114 +++++ .../android/setupwizardlib/items/AbstractItem.java | 53 +++ .../items/AbstractItemHierarchy.java | 67 +++ .../setupwizardlib/items/ButtonBarItem.java | 127 ++++++ .../android/setupwizardlib/items/ButtonItem.java | 150 +++++++ .../setupwizardlib/items/GenericInflater.java | 495 +++++++++++++++++++++ .../com/android/setupwizardlib/items/IItem.java | 48 ++ .../com/android/setupwizardlib/items/Item.java | 154 +++++++ .../android/setupwizardlib/items/ItemAdapter.java | 131 ++++++ .../android/setupwizardlib/items/ItemGroup.java | 215 +++++++++ .../setupwizardlib/items/ItemHierarchy.java | 75 ++++ .../android/setupwizardlib/items/ItemInflater.java | 64 +++ .../com/android/setupwizardlib/span/LinkSpan.java | 87 ++++ .../android/setupwizardlib/span/SpanHelper.java | 36 ++ .../util/AbstractRequireScrollHelper.java | 78 ++++ .../util/DrawableLayoutDirectionHelper.java | 77 ++++ .../util/ListViewRequireScrollHelper.java | 81 ++++ .../com/android/setupwizardlib/util/Partner.java | 162 +++++++ .../setupwizardlib/util/RequireScrollHelper.java | 64 +++ .../android/setupwizardlib/util/ResultCodes.java | 28 ++ .../setupwizardlib/util/SystemBarHelper.java | 348 +++++++++++++++ .../setupwizardlib/util/WizardManagerHelper.java | 216 +++++++++ .../setupwizardlib/view/BottomScrollView.java | 104 +++++ .../android/setupwizardlib/view/Illustration.java | 221 +++++++++ .../view/IntrinsicSizeFrameLayout.java | 92 ++++ .../android/setupwizardlib/view/NavigationBar.java | 138 ++++++ .../setupwizardlib/view/NavigationBarButton.java | 32 ++ .../view/StatusBarBackgroundLayout.java | 113 +++++ .../setupwizardlib/view/StickyHeaderListView.java | 163 +++++++ .../view/StickyHeaderScrollView.java | 117 +++++ .../src/main/res/anim-ldrtl/suw_slide_back_in.xml | 22 + .../src/main/res/anim-ldrtl/suw_slide_back_out.xml | 22 + .../src/main/res/anim-ldrtl/suw_slide_next_in.xml | 22 + .../src/main/res/anim-ldrtl/suw_slide_next_out.xml | 22 + .../src/main/res/anim/suw_slide_back_in.xml | 22 + .../src/main/res/anim/suw_slide_back_out.xml | 22 + .../src/main/res/anim/suw_slide_next_in.xml | 22 + .../src/main/res/anim/suw_slide_next_out.xml | 22 + .../res/animator-ldrtl-v11/suw_slide_back_in.xml | 26 ++ .../res/animator-ldrtl-v11/suw_slide_back_out.xml | 26 ++ .../res/animator-ldrtl-v11/suw_slide_next_in.xml | 26 ++ .../res/animator-ldrtl-v11/suw_slide_next_out.xml | 26 ++ .../main/res/animator-v11/suw_slide_back_in.xml | 26 ++ .../main/res/animator-v11/suw_slide_back_out.xml | 26 ++ .../main/res/animator-v11/suw_slide_next_in.xml | 26 ++ .../main/res/animator-v11/suw_slide_next_out.xml | 26 ++ .../res/drawable-anydpi-v21/suw_navbar_ic_back.xml | 31 ++ .../res/drawable-anydpi-v21/suw_navbar_ic_more.xml | 30 ++ .../res/drawable-anydpi-v21/suw_navbar_ic_next.xml | 31 ++ .../src/main/res/drawable-v21/suw_card_bg.xml | 27 ++ .../main/res/drawable-v21/suw_navbar_btn_bg.xml | 20 + .../main/res/drawable/suw_layout_background.xml | 24 + .../src/main/res/layout-v21/suw_progress_bar.xml | 25 ++ .../res/layout/suw_glif_blank_template_card.xml | 46 ++ .../res/layout/suw_glif_blank_template_compact.xml | 29 ++ .../src/main/res/layout/suw_glif_header.xml | 47 ++ .../res/layout/suw_glif_list_template_card.xml | 49 ++ .../res/layout/suw_glif_list_template_compact.xml | 26 ++ .../res/layout/suw_glif_list_template_content.xml | 37 ++ .../src/main/res/layout/suw_glif_template_card.xml | 49 ++ .../main/res/layout/suw_glif_template_compact.xml | 26 ++ .../main/res/layout/suw_glif_template_content.xml | 53 +++ .../src/main/res/layout/suw_items_button_bar.xml | 23 + .../src/main/res/layout/suw_items_default.xml | 69 +++ .../src/main/res/layout/suw_items_description.xml | 69 +++ .../src/main/res/layout/suw_items_verbose.xml | 67 +++ .../src/main/res/layout/suw_list_header.xml | 47 ++ .../src/main/res/layout/suw_list_template_card.xml | 71 +++ .../res/layout/suw_list_template_card_wide.xml | 72 +++ .../main/res/layout/suw_list_template_header.xml | 38 ++ .../layout/suw_list_template_header_collapsed.xml | 54 +++ .../src/main/res/layout/suw_navbar_view.xml | 56 +++ .../res/layout/suw_no_scroll_template_card.xml | 74 +++ .../layout/suw_no_scroll_template_card_wide.xml | 75 ++++ .../res/layout/suw_no_scroll_template_header.xml | 69 +++ .../suw_no_scroll_template_header_collapsed.xml | 56 +++ .../src/main/res/layout/suw_progress_bar_stub.xml | 25 ++ .../src/main/res/layout/suw_template_card.xml | 80 ++++ .../src/main/res/layout/suw_template_card_wide.xml | 80 ++++ .../src/main/res/layout/suw_template_header.xml | 81 ++++ .../res/layout/suw_template_header_collapsed.xml | 69 +++ setupwizardlib/src/main/res/values-af/strings.xml | 23 + setupwizardlib/src/main/res/values-am/strings.xml | 23 + setupwizardlib/src/main/res/values-ar/strings.xml | 23 + .../src/main/res/values-az-rAZ/strings.xml | 23 + .../src/main/res/values-b+sr+Latn/strings.xml | 23 + .../src/main/res/values-be-rBY/strings.xml | 23 + setupwizardlib/src/main/res/values-bg/strings.xml | 23 + .../src/main/res/values-bn-rBD/strings.xml | 23 + .../src/main/res/values-bs-rBA/strings.xml | 23 + setupwizardlib/src/main/res/values-ca/strings.xml | 23 + setupwizardlib/src/main/res/values-cs/strings.xml | 23 + setupwizardlib/src/main/res/values-da/strings.xml | 23 + setupwizardlib/src/main/res/values-de/strings.xml | 23 + setupwizardlib/src/main/res/values-el/strings.xml | 23 + .../src/main/res/values-en-rAU/strings.xml | 23 + .../src/main/res/values-en-rGB/strings.xml | 23 + .../src/main/res/values-en-rIN/strings.xml | 23 + .../src/main/res/values-es-rUS/strings.xml | 23 + setupwizardlib/src/main/res/values-es/strings.xml | 23 + .../src/main/res/values-et-rEE/strings.xml | 23 + .../src/main/res/values-eu-rES/strings.xml | 23 + setupwizardlib/src/main/res/values-fa/strings.xml | 23 + setupwizardlib/src/main/res/values-fi/strings.xml | 23 + .../src/main/res/values-fr-rCA/strings.xml | 23 + setupwizardlib/src/main/res/values-fr/strings.xml | 23 + .../src/main/res/values-gl-rES/strings.xml | 23 + .../src/main/res/values-gu-rIN/strings.xml | 23 + setupwizardlib/src/main/res/values-hi/strings.xml | 23 + setupwizardlib/src/main/res/values-hr/strings.xml | 23 + setupwizardlib/src/main/res/values-hu/strings.xml | 23 + .../src/main/res/values-hy-rAM/strings.xml | 23 + setupwizardlib/src/main/res/values-in/strings.xml | 23 + .../src/main/res/values-is-rIS/strings.xml | 23 + setupwizardlib/src/main/res/values-it/strings.xml | 23 + setupwizardlib/src/main/res/values-iw/strings.xml | 23 + setupwizardlib/src/main/res/values-ja/strings.xml | 23 + .../src/main/res/values-ka-rGE/strings.xml | 23 + .../src/main/res/values-kk-rKZ/strings.xml | 23 + .../src/main/res/values-km-rKH/strings.xml | 23 + .../src/main/res/values-kn-rIN/strings.xml | 23 + setupwizardlib/src/main/res/values-ko/strings.xml | 23 + .../src/main/res/values-ky-rKG/strings.xml | 23 + setupwizardlib/src/main/res/values-land/dimens.xml | 36 ++ .../src/main/res/values-land/layouts.xml | 28 ++ setupwizardlib/src/main/res/values-land/styles.xml | 26 ++ .../src/main/res/values-lo-rLA/strings.xml | 23 + setupwizardlib/src/main/res/values-lt/strings.xml | 23 + setupwizardlib/src/main/res/values-lv/strings.xml | 23 + .../src/main/res/values-mk-rMK/strings.xml | 23 + .../src/main/res/values-ml-rIN/strings.xml | 23 + .../src/main/res/values-mn-rMN/strings.xml | 23 + .../src/main/res/values-mr-rIN/strings.xml | 23 + .../src/main/res/values-ms-rMY/strings.xml | 23 + .../src/main/res/values-my-rMM/strings.xml | 23 + setupwizardlib/src/main/res/values-nb/strings.xml | 23 + .../src/main/res/values-ne-rNP/strings.xml | 23 + setupwizardlib/src/main/res/values-nl/strings.xml | 23 + .../src/main/res/values-pa-rIN/strings.xml | 23 + setupwizardlib/src/main/res/values-pl/strings.xml | 23 + .../src/main/res/values-pt-rBR/strings.xml | 23 + .../src/main/res/values-pt-rPT/strings.xml | 23 + setupwizardlib/src/main/res/values-pt/strings.xml | 23 + setupwizardlib/src/main/res/values-ro/strings.xml | 23 + setupwizardlib/src/main/res/values-ru/strings.xml | 23 + .../src/main/res/values-si-rLK/strings.xml | 23 + setupwizardlib/src/main/res/values-sk/strings.xml | 23 + setupwizardlib/src/main/res/values-sl/strings.xml | 23 + .../src/main/res/values-sq-rAL/strings.xml | 23 + setupwizardlib/src/main/res/values-sr/strings.xml | 23 + setupwizardlib/src/main/res/values-sv/strings.xml | 23 + setupwizardlib/src/main/res/values-sw/strings.xml | 23 + .../src/main/res/values-sw360dp/dimens.xml | 20 + .../src/main/res/values-sw600dp-land/dimens.xml | 26 ++ .../src/main/res/values-sw600dp-land/layouts.xml | 28 ++ .../src/main/res/values-sw600dp/config.xml | 23 + .../src/main/res/values-sw600dp/dimens.xml | 28 ++ .../src/main/res/values-sw600dp/layouts.xml | 32 ++ .../src/main/res/values-ta-rIN/strings.xml | 23 + .../src/main/res/values-te-rIN/strings.xml | 23 + setupwizardlib/src/main/res/values-th/strings.xml | 23 + setupwizardlib/src/main/res/values-tl/strings.xml | 23 + setupwizardlib/src/main/res/values-tr/strings.xml | 23 + setupwizardlib/src/main/res/values-uk/strings.xml | 23 + .../src/main/res/values-ur-rPK/strings.xml | 23 + .../src/main/res/values-uz-rUZ/strings.xml | 23 + setupwizardlib/src/main/res/values-v21/styles.xml | 90 ++++ setupwizardlib/src/main/res/values-v21/themes.xml | 117 +++++ setupwizardlib/src/main/res/values-vi/strings.xml | 23 + .../src/main/res/values-zh-rCN/strings.xml | 23 + .../src/main/res/values-zh-rHK/strings.xml | 23 + .../src/main/res/values-zh-rTW/strings.xml | 23 + setupwizardlib/src/main/res/values-zu/strings.xml | 23 + setupwizardlib/src/main/res/values/attrs.xml | 124 ++++++ setupwizardlib/src/main/res/values/colors.xml | 40 ++ setupwizardlib/src/main/res/values/config.xml | 26 ++ setupwizardlib/src/main/res/values/dimens.xml | 122 +++++ setupwizardlib/src/main/res/values/layouts.xml | 32 ++ setupwizardlib/src/main/res/values/strings.xml | 27 ++ setupwizardlib/src/main/res/values/styles.xml | 200 +++++++++ setupwizardlib/src/main/res/values/themes.xml | 97 ++++ 193 files changed, 10780 insertions(+) create mode 100644 setupwizardlib/.gitignore create mode 100644 setupwizardlib/build.gradle create mode 100644 setupwizardlib/src/main/AndroidManifest.xml create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/GlifLayout.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/GlifListLayout.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/GlifPatternDrawable.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardItemsLayout.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardLayout.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardListLayout.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/TemplateLayout.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/annotations/Keep.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/annotations/VisibleForTesting.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/AbstractItem.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/AbstractItemHierarchy.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/ButtonBarItem.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/ButtonItem.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/GenericInflater.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/IItem.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/Item.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemAdapter.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemGroup.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemHierarchy.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemInflater.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/span/LinkSpan.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/span/SpanHelper.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/util/Partner.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/util/RequireScrollHelper.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/util/ResultCodes.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/util/SystemBarHelper.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/util/WizardManagerHelper.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/view/BottomScrollView.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/view/Illustration.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/view/NavigationBar.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/view/NavigationBarButton.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/view/StickyHeaderListView.java create mode 100644 setupwizardlib/src/main/java/com/android/setupwizardlib/view/StickyHeaderScrollView.java create mode 100644 setupwizardlib/src/main/res/anim-ldrtl/suw_slide_back_in.xml create mode 100644 setupwizardlib/src/main/res/anim-ldrtl/suw_slide_back_out.xml create mode 100644 setupwizardlib/src/main/res/anim-ldrtl/suw_slide_next_in.xml create mode 100644 setupwizardlib/src/main/res/anim-ldrtl/suw_slide_next_out.xml create mode 100644 setupwizardlib/src/main/res/anim/suw_slide_back_in.xml create mode 100644 setupwizardlib/src/main/res/anim/suw_slide_back_out.xml create mode 100644 setupwizardlib/src/main/res/anim/suw_slide_next_in.xml create mode 100644 setupwizardlib/src/main/res/anim/suw_slide_next_out.xml create mode 100644 setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_back_in.xml create mode 100644 setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_back_out.xml create mode 100644 setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_next_in.xml create mode 100644 setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_next_out.xml create mode 100644 setupwizardlib/src/main/res/animator-v11/suw_slide_back_in.xml create mode 100644 setupwizardlib/src/main/res/animator-v11/suw_slide_back_out.xml create mode 100644 setupwizardlib/src/main/res/animator-v11/suw_slide_next_in.xml create mode 100644 setupwizardlib/src/main/res/animator-v11/suw_slide_next_out.xml create mode 100644 setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_back.xml create mode 100644 setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_more.xml create mode 100644 setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_next.xml create mode 100644 setupwizardlib/src/main/res/drawable-v21/suw_card_bg.xml create mode 100644 setupwizardlib/src/main/res/drawable-v21/suw_navbar_btn_bg.xml create mode 100644 setupwizardlib/src/main/res/drawable/suw_layout_background.xml create mode 100644 setupwizardlib/src/main/res/layout-v21/suw_progress_bar.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_blank_template_card.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_blank_template_compact.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_header.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_list_template_card.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_list_template_compact.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_list_template_content.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_template_card.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_template_compact.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_glif_template_content.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_items_button_bar.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_items_default.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_items_description.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_items_verbose.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_list_header.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_list_template_card.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_list_template_card_wide.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_list_template_header.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_list_template_header_collapsed.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_navbar_view.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_no_scroll_template_card.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_no_scroll_template_card_wide.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_no_scroll_template_header.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_no_scroll_template_header_collapsed.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_progress_bar_stub.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_template_card.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_template_card_wide.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_template_header.xml create mode 100644 setupwizardlib/src/main/res/layout/suw_template_header_collapsed.xml create mode 100644 setupwizardlib/src/main/res/values-af/strings.xml create mode 100644 setupwizardlib/src/main/res/values-am/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ar/strings.xml create mode 100644 setupwizardlib/src/main/res/values-az-rAZ/strings.xml create mode 100644 setupwizardlib/src/main/res/values-b+sr+Latn/strings.xml create mode 100644 setupwizardlib/src/main/res/values-be-rBY/strings.xml create mode 100644 setupwizardlib/src/main/res/values-bg/strings.xml create mode 100644 setupwizardlib/src/main/res/values-bn-rBD/strings.xml create mode 100644 setupwizardlib/src/main/res/values-bs-rBA/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ca/strings.xml create mode 100644 setupwizardlib/src/main/res/values-cs/strings.xml create mode 100644 setupwizardlib/src/main/res/values-da/strings.xml create mode 100644 setupwizardlib/src/main/res/values-de/strings.xml create mode 100644 setupwizardlib/src/main/res/values-el/strings.xml create mode 100644 setupwizardlib/src/main/res/values-en-rAU/strings.xml create mode 100644 setupwizardlib/src/main/res/values-en-rGB/strings.xml create mode 100644 setupwizardlib/src/main/res/values-en-rIN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-es-rUS/strings.xml create mode 100644 setupwizardlib/src/main/res/values-es/strings.xml create mode 100644 setupwizardlib/src/main/res/values-et-rEE/strings.xml create mode 100644 setupwizardlib/src/main/res/values-eu-rES/strings.xml create mode 100644 setupwizardlib/src/main/res/values-fa/strings.xml create mode 100644 setupwizardlib/src/main/res/values-fi/strings.xml create mode 100644 setupwizardlib/src/main/res/values-fr-rCA/strings.xml create mode 100644 setupwizardlib/src/main/res/values-fr/strings.xml create mode 100644 setupwizardlib/src/main/res/values-gl-rES/strings.xml create mode 100644 setupwizardlib/src/main/res/values-gu-rIN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-hi/strings.xml create mode 100644 setupwizardlib/src/main/res/values-hr/strings.xml create mode 100644 setupwizardlib/src/main/res/values-hu/strings.xml create mode 100644 setupwizardlib/src/main/res/values-hy-rAM/strings.xml create mode 100644 setupwizardlib/src/main/res/values-in/strings.xml create mode 100644 setupwizardlib/src/main/res/values-is-rIS/strings.xml create mode 100644 setupwizardlib/src/main/res/values-it/strings.xml create mode 100644 setupwizardlib/src/main/res/values-iw/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ja/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ka-rGE/strings.xml create mode 100644 setupwizardlib/src/main/res/values-kk-rKZ/strings.xml create mode 100644 setupwizardlib/src/main/res/values-km-rKH/strings.xml create mode 100644 setupwizardlib/src/main/res/values-kn-rIN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ko/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ky-rKG/strings.xml create mode 100644 setupwizardlib/src/main/res/values-land/dimens.xml create mode 100644 setupwizardlib/src/main/res/values-land/layouts.xml create mode 100644 setupwizardlib/src/main/res/values-land/styles.xml create mode 100644 setupwizardlib/src/main/res/values-lo-rLA/strings.xml create mode 100644 setupwizardlib/src/main/res/values-lt/strings.xml create mode 100644 setupwizardlib/src/main/res/values-lv/strings.xml create mode 100644 setupwizardlib/src/main/res/values-mk-rMK/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ml-rIN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-mn-rMN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-mr-rIN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ms-rMY/strings.xml create mode 100644 setupwizardlib/src/main/res/values-my-rMM/strings.xml create mode 100644 setupwizardlib/src/main/res/values-nb/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ne-rNP/strings.xml create mode 100644 setupwizardlib/src/main/res/values-nl/strings.xml create mode 100644 setupwizardlib/src/main/res/values-pa-rIN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-pl/strings.xml create mode 100644 setupwizardlib/src/main/res/values-pt-rBR/strings.xml create mode 100644 setupwizardlib/src/main/res/values-pt-rPT/strings.xml create mode 100644 setupwizardlib/src/main/res/values-pt/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ro/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ru/strings.xml create mode 100644 setupwizardlib/src/main/res/values-si-rLK/strings.xml create mode 100644 setupwizardlib/src/main/res/values-sk/strings.xml create mode 100644 setupwizardlib/src/main/res/values-sl/strings.xml create mode 100644 setupwizardlib/src/main/res/values-sq-rAL/strings.xml create mode 100644 setupwizardlib/src/main/res/values-sr/strings.xml create mode 100644 setupwizardlib/src/main/res/values-sv/strings.xml create mode 100644 setupwizardlib/src/main/res/values-sw/strings.xml create mode 100644 setupwizardlib/src/main/res/values-sw360dp/dimens.xml create mode 100644 setupwizardlib/src/main/res/values-sw600dp-land/dimens.xml create mode 100644 setupwizardlib/src/main/res/values-sw600dp-land/layouts.xml create mode 100644 setupwizardlib/src/main/res/values-sw600dp/config.xml create mode 100644 setupwizardlib/src/main/res/values-sw600dp/dimens.xml create mode 100644 setupwizardlib/src/main/res/values-sw600dp/layouts.xml create mode 100644 setupwizardlib/src/main/res/values-ta-rIN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-te-rIN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-th/strings.xml create mode 100644 setupwizardlib/src/main/res/values-tl/strings.xml create mode 100644 setupwizardlib/src/main/res/values-tr/strings.xml create mode 100644 setupwizardlib/src/main/res/values-uk/strings.xml create mode 100644 setupwizardlib/src/main/res/values-ur-rPK/strings.xml create mode 100644 setupwizardlib/src/main/res/values-uz-rUZ/strings.xml create mode 100644 setupwizardlib/src/main/res/values-v21/styles.xml create mode 100644 setupwizardlib/src/main/res/values-v21/themes.xml create mode 100644 setupwizardlib/src/main/res/values-vi/strings.xml create mode 100644 setupwizardlib/src/main/res/values-zh-rCN/strings.xml create mode 100644 setupwizardlib/src/main/res/values-zh-rHK/strings.xml create mode 100644 setupwizardlib/src/main/res/values-zh-rTW/strings.xml create mode 100644 setupwizardlib/src/main/res/values-zu/strings.xml create mode 100644 setupwizardlib/src/main/res/values/attrs.xml create mode 100644 setupwizardlib/src/main/res/values/colors.xml create mode 100644 setupwizardlib/src/main/res/values/config.xml create mode 100644 setupwizardlib/src/main/res/values/dimens.xml create mode 100644 setupwizardlib/src/main/res/values/layouts.xml create mode 100644 setupwizardlib/src/main/res/values/strings.xml create mode 100644 setupwizardlib/src/main/res/values/styles.xml create mode 100644 setupwizardlib/src/main/res/values/themes.xml (limited to 'setupwizardlib') diff --git a/setupwizardlib/.gitignore b/setupwizardlib/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/setupwizardlib/.gitignore @@ -0,0 +1 @@ +/build diff --git a/setupwizardlib/build.gradle b/setupwizardlib/build.gradle new file mode 100644 index 0000000..1f9b350 --- /dev/null +++ b/setupwizardlib/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' +apply plugin: 'com.github.dcendents.android-maven' +group='com.github.AndroidSnippet' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.1" + + defaultConfig { + targetSdkVersion 26 + minSdkVersion 14 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile 'com.android.support:appcompat-v7:26.0.0' +} + diff --git a/setupwizardlib/src/main/AndroidManifest.xml b/setupwizardlib/src/main/AndroidManifest.xml new file mode 100644 index 0000000..308a7e5 --- /dev/null +++ b/setupwizardlib/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifLayout.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifLayout.java new file mode 100644 index 0000000..765e6a6 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifLayout.java @@ -0,0 +1,291 @@ +/* + * 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 com.android.setupwizardlib; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.view.InflateException; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewStub; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.android.setupwizardlib.view.StatusBarBackgroundLayout; + +/** + * Layout for the GLIF theme used in Setup Wizard for N. + * + *

Example usage: + *

{@code
+ * <com.android.setupwizardlib.GlifLayout
+ *     xmlns:android="http://schemas.android.com/apk/res/android"
+ *     xmlns:app="http://schemas.android.com/apk/res-auto"
+ *     android:layout_width="match_parent"
+ *     android:layout_height="match_parent"
+ *     android:icon="@drawable/my_icon"
+ *     app:suwHeaderText="@string/my_title">
+ *
+ *     <!-- Content here -->
+ *
+ * </com.android.setupwizardlib.GlifLayout>
+ * }
+ */ +public class GlifLayout extends TemplateLayout { + + private static final String TAG = "GlifLayout"; + + private ColorStateList mPrimaryColor; + + public GlifLayout(Context context) { + this(context, 0, 0); + } + + public GlifLayout(Context context, int template) { + this(context, template, 0); + } + + public GlifLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, R.attr.suwLayoutTheme); + } + + public GlifLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, R.attr.suwLayoutTheme); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public GlifLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + // All the constructors delegate to this init method. The 3-argument constructor is not + // available in LinearLayout before v11, so call super with the exact same arguments. + private void init(AttributeSet attrs, int defStyleAttr) { + final TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.SuwGlifLayout, defStyleAttr, 0); + + final Drawable icon = a.getDrawable(R.styleable.SuwGlifLayout_android_icon); + if (icon != null) { + setIcon(icon); + } + + // Set the header color + final ColorStateList headerColor = + a.getColorStateList(R.styleable.SuwGlifLayout_suwHeaderColor); + if (headerColor != null) { + setHeaderColor(headerColor); + } + + + // Set the header text + final CharSequence headerText = + a.getText(R.styleable.SuwGlifLayout_suwHeaderText); + if (headerText != null) { + setHeaderText(headerText); + } + + final ColorStateList primaryColor = + a.getColorStateList(R.styleable.SuwGlifLayout_android_colorPrimary); + setPrimaryColor(primaryColor); + + a.recycle(); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_glif_template; + } + try { + return super.onInflateTemplate(inflater, template); + } catch (RuntimeException e) { + // Versions before M throws RuntimeException for unsuccessful attribute resolution + // Versions M+ will throw an InflateException (which extends from RuntimeException) + throw new InflateException("Unable to inflate layout. Are you using " + + "@style/SuwThemeGlif (or its descendant) as your theme?", e); + } + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_layout_content; + } + return super.findContainer(containerId); + } + + /** + * Same as {@link android.view.View#findViewById(int)}, but may include views that are managed + * by this view but not currently added to the view hierarchy. e.g. recycler view or list view + * headers that are not currently shown. + */ + protected View findManagedViewById(int id) { + return findViewById(id); + } + + public ScrollView getScrollView() { + final View view = findManagedViewById(R.id.suw_scroll_view); + return view instanceof ScrollView ? (ScrollView) view : null; + } + + public TextView getHeaderTextView() { + return (TextView) findManagedViewById(R.id.suw_layout_title); + } + + public void setHeaderText(int title) { + setHeaderText(getContext().getResources().getText(title)); + } + + public void setHeaderText(CharSequence title) { + final TextView titleView = getHeaderTextView(); + if (titleView != null) { + titleView.setText(title); + } + } + + public CharSequence getHeaderText() { + final TextView titleView = getHeaderTextView(); + return titleView != null ? titleView.getText() : null; + } + + public void setHeaderColor(ColorStateList color) { + final TextView titleView = getHeaderTextView(); + if (titleView != null) { + titleView.setTextColor(color); + } + } + + public ColorStateList getHeaderColor() { + final TextView titleView = getHeaderTextView(); + return titleView != null ? titleView.getTextColors() : null; + } + + public void setIcon(Drawable icon) { + final ImageView iconView = getIconView(); + if (iconView != null) { + iconView.setImageDrawable(icon); + } + } + + public Drawable getIcon() { + final ImageView iconView = getIconView(); + return iconView != null ? iconView.getDrawable() : null; + } + + protected ImageView getIconView() { + return (ImageView) findManagedViewById(R.id.suw_layout_icon); + } + + public void setPrimaryColor(ColorStateList color) { + mPrimaryColor = color; + setGlifPatternColor(color); + setProgressBarColor(color); + } + + public ColorStateList getPrimaryColor() { + return mPrimaryColor; + } + + private void setGlifPatternColor(ColorStateList color) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + final View patternBg = findManagedViewById(R.id.suw_pattern_bg); + if (patternBg != null) { + final GlifPatternDrawable background = + new GlifPatternDrawable(color.getDefaultColor()); + if (patternBg instanceof StatusBarBackgroundLayout) { + ((StatusBarBackgroundLayout) patternBg).setStatusBarBackground(background); + } else { + patternBg.setBackground(background); + } + } + } + } + + public boolean isProgressBarShown() { + final View progressBar = findManagedViewById(R.id.suw_layout_progress); + return progressBar != null && progressBar.getVisibility() == View.VISIBLE; + } + + public void setProgressBarShown(boolean shown) { + if (shown) { + View progressBar = getProgressBar(); + if (progressBar != null) { + progressBar.setVisibility(View.VISIBLE); + } + } else { + View progressBar = peekProgressBar(); + if (progressBar != null) { + progressBar.setVisibility(View.GONE); + } + } + } + + /** + * Gets the progress bar in the layout. If the progress bar has not been used before, it will be + * installed (i.e. inflated from its view stub). + * + * @return The progress bar of this layout. May be null only if the template used doesn't have a + * progress bar built-in. + */ + private ProgressBar getProgressBar() { + final View progressBar = peekProgressBar(); + if (progressBar == null) { + final ViewStub progressBarStub = + (ViewStub) findManagedViewById(R.id.suw_layout_progress_stub); + if (progressBarStub != null) { + progressBarStub.inflate(); + } + setProgressBarColor(mPrimaryColor); + } + return peekProgressBar(); + } + + /** + * Gets the progress bar in the layout only if it has been installed. + * {@link #setProgressBarShown(boolean)} should be called before this to ensure the progress bar + * is set up correctly. + * + * @return The progress bar of this layout, or null if the progress bar is not installed. The + * null case can happen either if {@link #setProgressBarShown(boolean)} with true was + * not called before this, or if the template does not contain a progress bar. + */ + public ProgressBar peekProgressBar() { + return (ProgressBar) findManagedViewById(R.id.suw_layout_progress); + } + + private void setProgressBarColor(ColorStateList color) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + final ProgressBar bar = peekProgressBar(); + if (bar != null) { + bar.setIndeterminateTintList(color); + } + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifListLayout.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifListLayout.java new file mode 100644 index 0000000..e5f0d42 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifListLayout.java @@ -0,0 +1,179 @@ +/* + * 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 com.android.setupwizardlib; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HeaderViewListAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; + +import com.android.setupwizardlib.items.ItemAdapter; +import com.android.setupwizardlib.items.ItemGroup; +import com.android.setupwizardlib.items.ItemInflater; +import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper; + +/** + * A GLIF themed layout with a ListView. {@code android:entries} can also be used to specify an + * {@link com.android.setupwizardlib.items.ItemHierarchy} to be used with this layout in XML. + */ +public class GlifListLayout extends GlifLayout { + + /* static section */ + + private static final String TAG = "GlifListLayout"; + + /* non-static section */ + + private ListView mListView; + private Drawable mDivider; + private Drawable mDefaultDivider; + private int mDividerInset; + + public GlifListLayout(Context context) { + this(context, 0, 0); + } + + public GlifListLayout(Context context, int template) { + this(context, template, 0); + } + + public GlifListLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(context, null, 0); + } + + public GlifListLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public GlifListLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwGlifListLayout, + defStyleAttr, 0); + final int xml = a.getResourceId(R.styleable.SuwGlifListLayout_android_entries, 0); + if (xml != 0) { + final ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(xml); + setAdapter(new ItemAdapter(inflated)); + } + int dividerInset = + a.getDimensionPixelSize(R.styleable.SuwGlifListLayout_suwDividerInset, 0); + if (dividerInset == 0) { + dividerInset = getResources() + .getDimensionPixelSize(R.dimen.suw_items_glif_icon_divider_inset); + } + setDividerInset(dividerInset); + a.recycle(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (mDivider == null) { + // Update divider in case layout direction has just been resolved + updateDivider(); + } + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_glif_list_template; + } + return super.onInflateTemplate(inflater, template); + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = android.R.id.list; + } + return super.findContainer(containerId); + } + + @Override + protected void onTemplateInflated() { + mListView = (ListView) findViewById(android.R.id.list); + } + + public ListView getListView() { + return mListView; + } + + public void setAdapter(ListAdapter adapter) { + getListView().setAdapter(adapter); + } + + public ListAdapter getAdapter() { + final ListAdapter adapter = getListView().getAdapter(); + if (adapter instanceof HeaderViewListAdapter) { + return ((HeaderViewListAdapter) adapter).getWrappedAdapter(); + } + return adapter; + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and inset it {@code inset} pixels to the right (or left in RTL layouts). + * + * @param inset The number of pixels to inset on the "start" side of the list divider. Typically + * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or + * {@code @dimen/suw_items_glif_text_divider_inset}. + */ + public void setDividerInset(int inset) { + mDividerInset = inset; + updateDivider(); + } + + public int getDividerInset() { + return mDividerInset; + } + + private void updateDivider() { + boolean shouldUpdate = true; + if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + shouldUpdate = isLayoutDirectionResolved(); + } + if (shouldUpdate) { + final ListView listView = getListView(); + if (mDefaultDivider == null) { + mDefaultDivider = listView.getDivider(); + } + mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable(mDefaultDivider, + mDividerInset /* start */, 0 /* top */, 0 /* end */, 0 /* bottom */, this); + listView.setDivider(mDivider); + } + } + + public Drawable getDivider() { + return mDivider; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifPatternDrawable.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifPatternDrawable.java new file mode 100644 index 0000000..6d082a1 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/GlifPatternDrawable.java @@ -0,0 +1,317 @@ +/* + * 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 com.android.setupwizardlib; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; + +import com.android.setupwizardlib.annotations.VisibleForTesting; + +import java.lang.ref.SoftReference; + +/** + * This class draws the GLIF pattern used as the status bar background for phones and background for + * tablets in GLIF layout. + */ +public class GlifPatternDrawable extends Drawable { + /* + * This class essentially implements a simple SVG in Java code, with some special handling of + * scaling when given different bounds. + */ + + /* static section */ + + @SuppressLint("InlinedApi") + private static final int[] ATTRS_PRIMARY_COLOR = new int[]{ android.R.attr.colorPrimary }; + + private static final float VIEWBOX_HEIGHT = 768f; + private static final float VIEWBOX_WIDTH = 1366f; + // X coordinate of scale focus, as a fraction of of the width. (Range is 0 - 1) + private static final float SCALE_FOCUS_X = .146f; + // Y coordinate of scale focus, as a fraction of of the height. (Range is 0 - 1) + private static final float SCALE_FOCUS_Y = .228f; + + // Alpha component of the color to be drawn, on top of the grayscale pattern. (Range is 0 - 1) + private static final float COLOR_ALPHA = .8f; + // Int version of COLOR_ALPHA. (Range is 0 - 255) + private static final int COLOR_ALPHA_INT = (int) (COLOR_ALPHA * 255); + + // Cap the bitmap size, such that it won't hurt the performance too much + // and it won't crash due to a very large scale. + // The drawable will look blurry above this size. + // This is a multiplier applied on top of the viewbox size. + // Resulting max cache size = (1.5 x 1366, 1.5 x 768) = (2049, 1152) + private static final float MAX_CACHED_BITMAP_SCALE = 1.5f; + + private static final int NUM_PATHS = 7; + + private static SoftReference sBitmapCache; + private static Path[] sPatternPaths; + private static int[] sPatternLightness; + + public static GlifPatternDrawable getDefault(Context context) { + int colorPrimary = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + final TypedArray a = context.obtainStyledAttributes(ATTRS_PRIMARY_COLOR); + colorPrimary = a.getColor(0, Color.BLACK); + a.recycle(); + } + return new GlifPatternDrawable(colorPrimary); + } + + @VisibleForTesting + public static void invalidatePattern() { + sBitmapCache = null; + } + + /* non-static section */ + + private int mColor; + private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private ColorFilter mColorFilter; + + public GlifPatternDrawable(int color) { + setColor(color); + } + + @Override + public void draw(Canvas canvas) { + final Rect bounds = getBounds(); + int drawableWidth = bounds.width(); + int drawableHeight = bounds.height(); + Bitmap bitmap = null; + if (sBitmapCache != null) { + bitmap = sBitmapCache.get(); + } + if (bitmap != null) { + final int bitmapWidth = bitmap.getWidth(); + final int bitmapHeight = bitmap.getHeight(); + // Invalidate the cache if this drawable is bigger and we can still create a bigger + // cache. + if (drawableWidth > bitmapWidth + && bitmapWidth < VIEWBOX_WIDTH * MAX_CACHED_BITMAP_SCALE) { + bitmap = null; + } else if (drawableHeight > bitmapHeight + && bitmapHeight < VIEWBOX_HEIGHT * MAX_CACHED_BITMAP_SCALE) { + bitmap = null; + } + } + + if (bitmap == null) { + // Reset the paint so it can be used to draw the paths in renderOnCanvas + mTempPaint.reset(); + + bitmap = createBitmapCache(drawableWidth, drawableHeight); + sBitmapCache = new SoftReference<>(bitmap); + + // Reset the paint to so it can be used to draw the bitmap + mTempPaint.reset(); + } + + canvas.save(); + canvas.clipRect(bounds); + + scaleCanvasToBounds(canvas, bitmap, bounds); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB + && canvas.isHardwareAccelerated()) { + mTempPaint.setColorFilter(mColorFilter); + canvas.drawBitmap(bitmap, 0, 0, mTempPaint); + } else { + // Software renderer doesn't work properly with ColorMatrix filter on ALPHA_8 bitmaps. + canvas.drawColor(Color.BLACK); + mTempPaint.setColor(Color.WHITE); + canvas.drawBitmap(bitmap, 0, 0, mTempPaint); + canvas.drawColor(mColor); + } + + canvas.restore(); + } + + @VisibleForTesting + public Bitmap createBitmapCache(int drawableWidth, int drawableHeight) { + float scaleX = drawableWidth / VIEWBOX_WIDTH; + float scaleY = drawableHeight / VIEWBOX_HEIGHT; + float scale = Math.max(scaleX, scaleY); + scale = Math.min(MAX_CACHED_BITMAP_SCALE, scale); + + + int scaledWidth = (int) (VIEWBOX_WIDTH * scale); + int scaledHeight = (int) (VIEWBOX_HEIGHT * scale); + + // Use ALPHA_8 mask to save memory, since the pattern is grayscale only anyway. + Bitmap bitmap = Bitmap.createBitmap( + scaledWidth, + scaledHeight, + Bitmap.Config.ALPHA_8); + Canvas bitmapCanvas = new Canvas(bitmap); + renderOnCanvas(bitmapCanvas, scale); + return bitmap; + } + + private void renderOnCanvas(Canvas canvas, float scale) { + canvas.save(); + canvas.scale(scale, scale); + + mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + + // Draw the pattern by creating the paths, adjusting the colors and drawing them. The path + // values are extracted from the SVG of the pattern file. + + if (sPatternPaths == null) { + sPatternPaths = new Path[NUM_PATHS]; + // Lightness values of the pattern, range 0 - 255 + sPatternLightness = new int[] { 10, 40, 51, 66, 91, 112, 130 }; + + Path p = sPatternPaths[0] = new Path(); + p.moveTo(1029.4f, 357.5f); + p.lineTo(1366f, 759.1f); + p.lineTo(1366f, 0f); + p.lineTo(1137.7f, 0f); + p.close(); + + p = sPatternPaths[1] = new Path(); + p.moveTo(1138.1f, 0f); + p.rLineTo(-144.8f, 768f); + p.rLineTo(372.7f, 0f); + p.rLineTo(0f, -524f); + p.cubicTo(1290.7f, 121.6f, 1219.2f, 41.1f, 1178.7f, 0f); + p.close(); + + p = sPatternPaths[2] = new Path(); + p.moveTo(949.8f, 768f); + p.rCubicTo(92.6f, -170.6f, 213f, -440.3f, 269.4f, -768f); + p.lineTo(585f, 0f); + p.rLineTo(2.1f, 766f); + p.close(); + + p = sPatternPaths[3] = new Path(); + p.moveTo(471.1f, 768f); + p.rMoveTo(704.5f, 0f); + p.cubicTo(1123.6f, 563.3f, 1027.4f, 275.2f, 856.2f, 0f); + p.lineTo(476.4f, 0f); + p.rLineTo(-5.3f, 768f); + p.close(); + + p = sPatternPaths[4] = new Path(); + p.moveTo(323.1f, 768f); + p.moveTo(777.5f, 768f); + p.cubicTo(661.9f, 348.8f, 427.2f, 21.4f, 401.2f, 25.4f); + p.lineTo(323.1f, 768f); + p.close(); + + p = sPatternPaths[5] = new Path(); + p.moveTo(178.44286f, 766.85714f); + p.lineTo(308.7f, 768f); + p.cubicTo(381.7f, 604.6f, 481.6f, 344.3f, 562.2f, 0f); + p.lineTo(0f, 0f); + p.close(); + + p = sPatternPaths[6] = new Path(); + p.moveTo(146f, 0f); + p.lineTo(0f, 0f); + p.lineTo(0f, 768f); + p.lineTo(394.2f, 768f); + p.cubicTo(327.7f, 475.3f, 228.5f, 201f, 146f, 0f); + p.close(); + } + + for (int i = 0; i < NUM_PATHS; i++) { + // Color is 0xAARRGGBB, so alpha << 24 will create a color with (alpha)% black. + // Although the color components don't really matter, since the backing bitmap cache is + // ALPHA_8. + mTempPaint.setColor(sPatternLightness[i] << 24); + canvas.drawPath(sPatternPaths[i], mTempPaint); + } + + canvas.restore(); + mTempPaint.reset(); + } + + @VisibleForTesting + public void scaleCanvasToBounds(Canvas canvas, Bitmap bitmap, Rect drawableBounds) { + int bitmapWidth = bitmap.getWidth(); + int bitmapHeight = bitmap.getHeight(); + float scaleX = drawableBounds.width() / (float) bitmapWidth; + float scaleY = drawableBounds.height() / (float) bitmapHeight; + + // First scale both sides to fit independently. + canvas.scale(scaleX, scaleY); + if (scaleY > scaleX) { + // Adjust x-scale to maintain aspect ratio using the pivot, so that more of the texture + // and less of the blank space on the left edge is seen. + canvas.scale(scaleY / scaleX, 1f, SCALE_FOCUS_X * bitmapWidth, 0f); + } else if (scaleX > scaleY) { + // Adjust y-scale to maintain aspect ratio using the pivot, so that an intersection of + // two "circles" can always be seen. + canvas.scale(1f, scaleX / scaleY, 0f, SCALE_FOCUS_Y * bitmapHeight); + } + } + + @Override + public void setAlpha(int i) { + // Ignore + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + // Ignore + } + + @Override + public int getOpacity() { + return 0; + } + + /** + * Sets the color used as the base color of this pattern drawable. The alpha component of the + * color will be ignored. + */ + public void setColor(int color) { + final int r = Color.red(color); + final int g = Color.green(color); + final int b = Color.blue(color); + mColor = Color.argb(COLOR_ALPHA_INT, r, g, b); + mColorFilter = new ColorMatrixColorFilter(new float[] { + 0, 0, 0, 1 - COLOR_ALPHA, r * COLOR_ALPHA, + 0, 0, 0, 1 - COLOR_ALPHA, g * COLOR_ALPHA, + 0, 0, 0, 1 - COLOR_ALPHA, b * COLOR_ALPHA, + 0, 0, 0, 0, 255 + }); + invalidateSelf(); + } + + /** + * @return The color used as the base color of this pattern drawable. The alpha component of + * this is always 255. + */ + public int getColor() { + return Color.argb(255, Color.red(mColor), Color.green(mColor), Color.blue(mColor)); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardItemsLayout.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardItemsLayout.java new file mode 100644 index 0000000..0a03878 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardItemsLayout.java @@ -0,0 +1,56 @@ +/* + * 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 com.android.setupwizardlib; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +import com.android.setupwizardlib.items.ItemAdapter; +import com.android.setupwizardlib.items.ItemGroup; +import com.android.setupwizardlib.items.ItemInflater; + +public class SetupWizardItemsLayout extends SetupWizardListLayout { + + private ItemAdapter mAdapter; + + public SetupWizardItemsLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public SetupWizardItemsLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwSetupWizardItemsLayout, + defStyleAttr, 0); + int xml = a.getResourceId(R.styleable.SuwSetupWizardItemsLayout_android_entries, 0); + if (xml != 0) { + ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(xml); + mAdapter = new ItemAdapter(inflated); + setAdapter(mAdapter); + } + a.recycle(); + } + + public ItemAdapter getAdapter() { + return mAdapter; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardLayout.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardLayout.java new file mode 100644 index 0000000..ce4896f --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardLayout.java @@ -0,0 +1,481 @@ +/* + * 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 com.android.setupwizardlib; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.Shader.TileMode; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.InflateException; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewStub; +import android.widget.ProgressBar; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.android.setupwizardlib.util.RequireScrollHelper; +import com.android.setupwizardlib.view.BottomScrollView; +import com.android.setupwizardlib.view.Illustration; +import com.android.setupwizardlib.view.NavigationBar; + +public class SetupWizardLayout extends TemplateLayout { + + private static final String TAG = "SetupWizardLayout"; + + private ColorStateList mProgressBarColor; + + public SetupWizardLayout(Context context) { + super(context, 0, 0); + init(null, R.attr.suwLayoutTheme); + } + + public SetupWizardLayout(Context context, int template) { + this(context, template, 0); + } + + public SetupWizardLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, R.attr.suwLayoutTheme); + } + + public SetupWizardLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, R.attr.suwLayoutTheme); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public SetupWizardLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + // All the constructors delegate to this init method. The 3-argument constructor is not + // available in LinearLayout before v11, so call super with the exact same arguments. + private void init(AttributeSet attrs, int defStyleAttr) { + final TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.SuwSetupWizardLayout, defStyleAttr, 0); + + // Set the background from XML, either directly or built from a bitmap tile + final Drawable background = + a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackground); + if (background != null) { + setLayoutBackground(background); + } else { + final Drawable backgroundTile = + a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackgroundTile); + if (backgroundTile != null) { + setBackgroundTile(backgroundTile); + } + } + + // Set the illustration from XML, either directly or built from image + horizontal tile + final Drawable illustration = + a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustration); + if (illustration != null) { + setIllustration(illustration); + } else { + final Drawable illustrationImage = + a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustrationImage); + final Drawable horizontalTile = a.getDrawable( + R.styleable.SuwSetupWizardLayout_suwIllustrationHorizontalTile); + if (illustrationImage != null && horizontalTile != null) { + setIllustration(illustrationImage, horizontalTile); + } + } + + // Set the top padding of the illustration + int decorPaddingTop = a.getDimensionPixelSize( + R.styleable.SuwSetupWizardLayout_suwDecorPaddingTop, -1); + if (decorPaddingTop == -1) { + decorPaddingTop = getResources().getDimensionPixelSize(R.dimen.suw_decor_padding_top); + } + setDecorPaddingTop(decorPaddingTop); + + + // Set the illustration aspect ratio. See Illustration.setAspectRatio(float). This will + // override suwIllustrationPaddingTop if its value is not 0. + float illustrationAspectRatio = a.getFloat( + R.styleable.SuwSetupWizardLayout_suwIllustrationAspectRatio, -1f); + if (illustrationAspectRatio == -1f) { + final TypedValue out = new TypedValue(); + getResources().getValue(R.dimen.suw_illustration_aspect_ratio, out, true); + illustrationAspectRatio = out.getFloat(); + } + setIllustrationAspectRatio(illustrationAspectRatio); + + // Set the header text + final CharSequence headerText = + a.getText(R.styleable.SuwSetupWizardLayout_suwHeaderText); + if (headerText != null) { + setHeaderText(headerText); + } + + a.recycle(); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable parcelable = super.onSaveInstanceState(); + final SavedState ss = new SavedState(parcelable); + ss.mIsProgressBarShown = isProgressBarShown(); + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + Log.w(TAG, "Ignoring restore instance state " + state); + super.onRestoreInstanceState(state); + return; + } + + final SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + final boolean isProgressBarShown = ss.mIsProgressBarShown; + if (isProgressBarShown) { + showProgressBar(); + } else { + hideProgressBar(); + } + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_template; + } + try { + return super.onInflateTemplate(inflater, template); + } catch (RuntimeException e) { + // Versions before M throws RuntimeException for unsuccessful attribute resolution + // Versions M+ will throw an InflateException (which extends from RuntimeException) + throw new InflateException("Unable to inflate layout. Are you using " + + "@style/SuwThemeMaterial (or its descendant) as your theme?", e); + } + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_layout_content; + } + return super.findContainer(containerId); + } + + public NavigationBar getNavigationBar() { + final View view = findManagedViewById(R.id.suw_layout_navigation_bar); + return view instanceof NavigationBar ? (NavigationBar) view : null; + } + + public ScrollView getScrollView() { + final View view = findManagedViewById(R.id.suw_bottom_scroll_view); + return view instanceof ScrollView ? (ScrollView) view : null; + } + + public void requireScrollToBottom() { + final NavigationBar navigationBar = getNavigationBar(); + final ScrollView scrollView = getScrollView(); + if (navigationBar != null && (scrollView instanceof BottomScrollView)) { + RequireScrollHelper.requireScroll(navigationBar, (BottomScrollView) scrollView); + } else { + Log.e(TAG, "Both suw_layout_navigation_bar and suw_bottom_scroll_view must exist in" + + " the template to require scrolling."); + } + } + + public void setHeaderText(int title) { + final TextView titleView = getHeaderTextView(); + if (titleView != null) { + titleView.setText(title); + } + } + + public void setHeaderText(CharSequence title) { + final TextView titleView = getHeaderTextView(); + if (titleView != null) { + titleView.setText(title); + } + } + + public CharSequence getHeaderText() { + final TextView titleView = getHeaderTextView(); + return titleView != null ? titleView.getText() : null; + } + + public TextView getHeaderTextView() { + return (TextView) findManagedViewById(R.id.suw_layout_title); + } + + /** + * Set the illustration of the layout. The drawable will be applied as is, and the bounds will + * be set as implemented in {@link com.android.setupwizardlib.view.Illustration}. To create + * a suitable drawable from an asset and a horizontal repeating tile, use + * {@link #setIllustration(int, int)} instead. + * + * @param drawable The drawable specifying the illustration. + */ + public void setIllustration(Drawable drawable) { + final View view = findManagedViewById(R.id.suw_layout_decor); + if (view instanceof Illustration) { + final Illustration illustration = (Illustration) view; + illustration.setIllustration(drawable); + } + } + + /** + * Set the illustration of the layout, which will be created asset and the horizontal tile as + * suitable. On phone layouts (not sw600dp), the asset will be scaled, maintaining aspect ratio. + * On tablets (sw600dp), the assets will always have 256dp height and the rest of the + * illustration area that the asset doesn't fill will be covered by the horizontalTile. + * + * @param asset Resource ID of the illustration asset. + * @param horizontalTile Resource ID of the horizontally repeating tile for tablet layout. + */ + public void setIllustration(int asset, int horizontalTile) { + final View view = findManagedViewById(R.id.suw_layout_decor); + if (view instanceof Illustration) { + final Illustration illustration = (Illustration) view; + final Drawable illustrationDrawable = getIllustration(asset, horizontalTile); + illustration.setIllustration(illustrationDrawable); + } + } + + private void setIllustration(Drawable asset, Drawable horizontalTile) { + final View view = findManagedViewById(R.id.suw_layout_decor); + if (view instanceof Illustration) { + final Illustration illustration = (Illustration) view; + final Drawable illustrationDrawable = getIllustration(asset, horizontalTile); + illustration.setIllustration(illustrationDrawable); + } + } + + /** + * Sets the aspect ratio of the illustration. This will be the space (padding top) reserved + * above the header text. This will override the padding top of the illustration. + * + * @param aspectRatio The aspect ratio + * @see com.android.setupwizardlib.view.Illustration#setAspectRatio(float) + */ + public void setIllustrationAspectRatio(float aspectRatio) { + final View view = findManagedViewById(R.id.suw_layout_decor); + if (view instanceof Illustration) { + final Illustration illustration = (Illustration) view; + illustration.setAspectRatio(aspectRatio); + } + } + + /** + * Set the top padding of the decor view. If the decor is an Illustration and the aspect ratio + * is set, this value will be overridden. + * + *

Note: Currently the default top padding for tablet landscape is 128dp, which is the offset + * of the card from the top. This is likely to change in future versions so this value aligns + * with the height of the illustration instead. + * + * @param paddingTop The top padding in pixels. + */ + public void setDecorPaddingTop(int paddingTop) { + final View view = findManagedViewById(R.id.suw_layout_decor); + if (view != null) { + view.setPadding(view.getPaddingLeft(), paddingTop, view.getPaddingRight(), + view.getPaddingBottom()); + } + } + + /** + * Set the background of the layout, which is expected to be able to extend infinitely. If it is + * a bitmap tile and you want it to repeat, use {@link #setBackgroundTile(int)} instead. + */ + public void setLayoutBackground(Drawable background) { + final View view = findManagedViewById(R.id.suw_layout_decor); + if (view != null) { + //noinspection deprecation + view.setBackgroundDrawable(background); + } + } + + /** + * Set the background of the layout to a repeating bitmap tile. To use a different kind of + * drawable, use {@link #setLayoutBackground(android.graphics.drawable.Drawable)} instead. + */ + public void setBackgroundTile(int backgroundTile) { + final Drawable backgroundTileDrawable = + getContext().getResources().getDrawable(backgroundTile); + setBackgroundTile(backgroundTileDrawable); + } + + private void setBackgroundTile(Drawable backgroundTile) { + if (backgroundTile instanceof BitmapDrawable) { + ((BitmapDrawable) backgroundTile).setTileModeXY(TileMode.REPEAT, TileMode.REPEAT); + } + setLayoutBackground(backgroundTile); + } + + private Drawable getIllustration(int asset, int horizontalTile) { + final Context context = getContext(); + final Drawable assetDrawable = context.getResources().getDrawable(asset); + final Drawable tile = context.getResources().getDrawable(horizontalTile); + return getIllustration(assetDrawable, tile); + } + + @SuppressLint("RtlHardcoded") + private Drawable getIllustration(Drawable asset, Drawable horizontalTile) { + final Context context = getContext(); + if (context.getResources().getBoolean(R.bool.suwUseTabletLayout)) { + // If it is a "tablet" (sw600dp), create a LayerDrawable with the horizontal tile. + if (horizontalTile instanceof BitmapDrawable) { + ((BitmapDrawable) horizontalTile).setTileModeX(TileMode.REPEAT); + ((BitmapDrawable) horizontalTile).setGravity(Gravity.TOP); + } + if (asset instanceof BitmapDrawable) { + // Always specify TOP | LEFT, Illustration will flip the entire LayerDrawable. + ((BitmapDrawable) asset).setGravity(Gravity.TOP | Gravity.LEFT); + } + final LayerDrawable layers = + new LayerDrawable(new Drawable[] { horizontalTile, asset }); + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + layers.setAutoMirrored(true); + } + return layers; + } else { + // If it is a "phone" (not sw600dp), simply return the illustration + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + asset.setAutoMirrored(true); + } + return asset; + } + } + + /** + * Same as {@link android.view.View#findViewById(int)}, but may include views that are managed + * by this view but not currently added to the view hierarchy. e.g. recycler view or list view + * headers that are not currently shown. + */ + protected View findManagedViewById(int id) { + return findViewById(id); + } + + public boolean isProgressBarShown() { + final View progressBar = findManagedViewById(R.id.suw_layout_progress); + return progressBar != null && progressBar.getVisibility() == View.VISIBLE; + } + + /** + * Sets whether the progress bar below the header text is shown or not. The progress bar is + * a lazily inflated ViewStub, which means the progress bar will not actually be part of the + * view hierarchy until the first time this is set to {@code true}. + */ + public void setProgressBarShown(boolean shown) { + final View progressBar = findManagedViewById(R.id.suw_layout_progress); + if (progressBar != null) { + progressBar.setVisibility(shown ? View.VISIBLE : View.GONE); + } else if (shown) { + final ViewStub progressBarStub = + (ViewStub) findManagedViewById(R.id.suw_layout_progress_stub); + if (progressBarStub != null) { + progressBarStub.inflate(); + } + if (mProgressBarColor != null) { + setProgressBarColor(mProgressBarColor); + } + } + } + + /** + * @deprecated Use {@link #setProgressBarShown(boolean)} + */ + @Deprecated + public void showProgressBar() { + setProgressBarShown(true); + } + + /** + * @deprecated Use {@link #setProgressBarShown(boolean)} + */ + @Deprecated + public void hideProgressBar() { + setProgressBarShown(false); + } + + public void setProgressBarColor(ColorStateList color) { + mProgressBarColor = color; + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + // Suppress lint error caused by + // https://code.google.com/p/android/issues/detail?id=183136 + // noinspection AndroidLintWrongViewCast + final ProgressBar bar = (ProgressBar) findViewById(R.id.suw_layout_progress); + if (bar != null) { + bar.setIndeterminateTintList(color); + } + } + } + + public ColorStateList getProgressBarColor() { + return mProgressBarColor; + } + + /* Misc */ + + protected static class SavedState extends BaseSavedState { + + boolean mIsProgressBarShown = false; + + public SavedState(Parcelable parcelable) { + super(parcelable); + } + + public SavedState(Parcel source) { + super(source); + mIsProgressBarShown = source.readInt() != 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mIsProgressBarShown ? 1 : 0); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SavedState createFromParcel(Parcel parcel) { + return new SavedState(parcel); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardListLayout.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardListLayout.java new file mode 100644 index 0000000..3410e9b --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/SetupWizardListLayout.java @@ -0,0 +1,164 @@ +/* + * 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 com.android.setupwizardlib; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListAdapter; +import android.widget.ListView; + +import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper; +import com.android.setupwizardlib.util.ListViewRequireScrollHelper; +import com.android.setupwizardlib.view.NavigationBar; + +public class SetupWizardListLayout extends SetupWizardLayout { + + private static final String TAG = "SetupWizardListLayout"; + private ListView mListView; + private Drawable mDivider; + private Drawable mDefaultDivider; + private int mDividerInset; + + public SetupWizardListLayout(Context context) { + this(context, 0, 0); + } + + public SetupWizardListLayout(Context context, int template) { + this(context, template, 0); + } + + public SetupWizardListLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(context, null, 0); + } + + public SetupWizardListLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public SetupWizardListLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SuwSetupWizardListLayout, defStyleAttr, 0); + int dividerInset = + a.getDimensionPixelSize(R.styleable.SuwSetupWizardListLayout_suwDividerInset, 0); + setDividerInset(dividerInset); + a.recycle(); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_list_template; + } + return super.onInflateTemplate(inflater, template); + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = android.R.id.list; + } + return super.findContainer(containerId); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (mDivider == null) { + // Update divider in case layout direction has just been resolved + updateDivider(); + } + } + + @Override + protected void onTemplateInflated() { + mListView = (ListView) findViewById(android.R.id.list); + } + + public ListView getListView() { + return mListView; + } + + public void setAdapter(ListAdapter adapter) { + getListView().setAdapter(adapter); + } + + @Override + public void requireScrollToBottom() { + final NavigationBar navigationBar = getNavigationBar(); + final ListView listView = getListView(); + if (navigationBar != null && listView != null) { + ListViewRequireScrollHelper.requireScroll(navigationBar, listView); + } else { + Log.e(TAG, "Both suw_layout_navigation_bar and list must exist in" + + " the template to require scrolling."); + } + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and inset it {@code inset} pixels to the right (or left in RTL layouts). + * + * @param inset The number of pixels to inset on the "start" side of the list divider. Typically + * this will be either {@code @dimen/suw_items_icon_divider_inset} or + * {@code @dimen/suw_items_text_divider_inset}. + */ + public void setDividerInset(int inset) { + mDividerInset = inset; + updateDivider(); + } + + public int getDividerInset() { + return mDividerInset; + } + + private void updateDivider() { + boolean shouldUpdate = true; + if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + shouldUpdate = isLayoutDirectionResolved(); + } + if (shouldUpdate) { + final ListView listView = getListView(); + if (mDefaultDivider == null) { + mDefaultDivider = listView.getDivider(); + } + mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable(mDefaultDivider, + mDividerInset /* start */, 0 /* top */, 0 /* end */, 0 /* bottom */, this); + listView.setDivider(mDivider); + } + } + + public Drawable getDivider() { + return mDivider; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/TemplateLayout.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/TemplateLayout.java new file mode 100644 index 0000000..3ffc3d8 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/TemplateLayout.java @@ -0,0 +1,196 @@ +/* + * 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 com.android.setupwizardlib; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; + +import com.android.setupwizardlib.annotations.Keep; + +/** + * A generic template class that inflates a template, provided in the constructor or in + * {@code android:layout} through XML, and adds its children to a "container" in the template. When + * inflating this layout from XML, the {@code android:layout} and {@code suwContainer} attributes + * are required. + */ +public class TemplateLayout extends FrameLayout { + + /** + * The container of the actual content. This will be a view in the template, which child views + * will be added to when {@link #addView(View)} is called. + */ + private ViewGroup mContainer; + + public TemplateLayout(Context context, int template, int containerId) { + super(context); + init(template, containerId, null, R.attr.suwLayoutTheme); + } + + public TemplateLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(0, 0, attrs, R.attr.suwLayoutTheme); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public TemplateLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(0, 0, attrs, defStyleAttr); + } + + // All the constructors delegate to this init method. The 3-argument constructor is not + // available in LinearLayout before v11, so call super with the exact same arguments. + private void init(int template, int containerId, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.SuwTemplateLayout, defStyleAttr, 0); + if (template == 0) { + template = a.getResourceId(R.styleable.SuwTemplateLayout_android_layout, 0); + } + if (containerId == 0) { + containerId = a.getResourceId(R.styleable.SuwTemplateLayout_suwContainer, 0); + } + inflateTemplate(template, containerId); + + a.recycle(); + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + mContainer.addView(child, index, params); + } + + private void addViewInternal(View child) { + super.addView(child, -1, generateDefaultLayoutParams()); + } + + private void inflateTemplate(int templateResource, int containerId) { + final LayoutInflater inflater = LayoutInflater.from(getContext()); + final View templateRoot = onInflateTemplate(inflater, templateResource); + addViewInternal(templateRoot); + + mContainer = findContainer(containerId); + if (mContainer == null) { + throw new IllegalArgumentException("Container cannot be null in TemplateLayout"); + } + onTemplateInflated(); + } + + /** + * This method inflates the template. Subclasses can override this method to customize the + * template inflation, or change to a different default template. The root of the inflated + * layout should be returned, and not added to the view hierarchy. + * + * @param inflater A LayoutInflater to inflate the template. + * @param template The resource ID of the template to be inflated, or 0 if no template is + * specified. + * @return Root of the inflated layout. + */ + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + throw new IllegalArgumentException("android:layout not specified for TemplateLayout"); + } + return inflater.inflate(template, this, false); + } + + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + // Maintain compatibility with the deprecated way of specifying container ID. + containerId = getContainerId(); + } + return (ViewGroup) findViewById(containerId); + } + + /** + * This is called after the template has been inflated and added to the view hierarchy. + * Subclasses can implement this method to modify the template as necessary, such as caching + * views retrieved from findViewById, or other view operations that need to be done in code. + * You can think of this as {@link View#onFinishInflate()} but for inflation of the + * template instead of for child views. + */ + protected void onTemplateInflated() { + } + + /** + * @return ID of the default container for this layout. This will be used to find the container + * ViewGroup, which all children views of this layout will be placed in. + * @deprecated Override {@link #findContainer(int)} instead. + */ + @Deprecated + protected int getContainerId() { + return 0; + } + + /* Animator support */ + + private float mXFraction; + private ViewTreeObserver.OnPreDrawListener mPreDrawListener; + + /** + * Set the X translation as a fraction of the width of this view. Make sure this method is not + * stripped out by proguard when using this with {@link android.animation.ObjectAnimator}. You + * may need to add + * + * -keep @com.android.setupwizardlib.annotations.Keep class * + * + * to your proguard configuration if you are seeing mysterious {@link NoSuchMethodError} at + * runtime. + */ + @Keep + @TargetApi(VERSION_CODES.HONEYCOMB) + public void setXFraction(float fraction) { + mXFraction = fraction; + final int width = getWidth(); + if (width != 0) { + setTranslationX(width * fraction); + } else { + // If we haven't done a layout pass yet, wait for one and then set the fraction before + // the draw occurs using an OnPreDrawListener. Don't call translationX until we know + // getWidth() has a reliable, non-zero value or else we will see the fragment flicker on + // screen. + if (mPreDrawListener == null) { + mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener); + setXFraction(mXFraction); + return true; + } + }; + getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); + } + } + } + + /** + * Return the X translation as a fraction of the width, as previously set in + * {@link #setXFraction(float)}. + * + * @see #setXFraction(float) + */ + @Keep + @TargetApi(VERSION_CODES.HONEYCOMB) + public float getXFraction() { + return mXFraction; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/annotations/Keep.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/annotations/Keep.java new file mode 100644 index 0000000..6ad3bf2 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/annotations/Keep.java @@ -0,0 +1,49 @@ +/* + * 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 com.android.setupwizardlib.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element should not be removed when + * the code is minified at build time. This is typically used + * on methods and classes that are accessed only via reflection + * so a compiler may think that the code is unused. + *

+ * Example: + *

{@code
+ *  @Keep
+ *  public void foo() {
+ *      ...
+ *  }
+ * }
+ * + * Copied from android.support.annotation.Keep + * TODO: Add support annotation library as a dependency and use that. + */ +@Retention(CLASS) +@Target({PACKAGE,TYPE,ANNOTATION_TYPE,CONSTRUCTOR,METHOD,FIELD}) +public @interface Keep { +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/annotations/VisibleForTesting.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/annotations/VisibleForTesting.java new file mode 100644 index 0000000..7115116 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/annotations/VisibleForTesting.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Denotes that the class, method or field has its visibility relaxed so + * that unit tests can access it. + *

+ * The visibility argument can be used to specific what the original + * visibility should have been if it had not been made public or package-private for testing. + * The default is to consider the element private. + */ +@Retention(RetentionPolicy.SOURCE) +public @interface VisibleForTesting { + /** + * Intended visibility if the element had not been made public or package-private for + * testing. + */ + enum Visibility { + /** The element should be considered protected. */ + PROTECTED, + /** The element should be considered package-private. */ + PACKAGE, + /** The element should be considered private. */ + PRIVATE + } + + /** + * Intended visibility if the element had not been made public or package-private for testing. + * If not specified, one should assume the element originally intended to be private. + */ + Visibility visibility() default Visibility.PRIVATE; +} \ No newline at end of file diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java new file mode 100644 index 0000000..8325232 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.gesture; + +import android.graphics.Rect; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +/** + * Helper class to detect the consective-tap gestures on a view. + * + *

This class is instantiated and used similar to a GestureDetector, where onTouchEvent should + * be called when there are MotionEvents this detector should know about. + */ +public final class ConsecutiveTapsGestureDetector { + + public interface OnConsecutiveTapsListener { + /** + * Callback method when the user tapped on the target view X number of times. + */ + void onConsecutiveTaps(int numOfConsecutiveTaps); + } + + private final View mView; + private final OnConsecutiveTapsListener mListener; + private final int mConsecutiveTapTouchSlopSquare; + + private int mConsecutiveTapsCounter = 0; + private MotionEvent mPreviousTapEvent; + + /** + * @param listener The listener that responds to the gesture. + * @param view The target view that associated with consecutive-tap gesture. + */ + public ConsecutiveTapsGestureDetector( + OnConsecutiveTapsListener listener, + View view) { + mListener = listener; + mView = view; + int doubleTapSlop = ViewConfiguration.get(mView.getContext()).getScaledDoubleTapSlop(); + mConsecutiveTapTouchSlopSquare = doubleTapSlop * doubleTapSlop; + } + + /** + * This method should be called from the relevant activity or view, typically in + * onTouchEvent, onInterceptTouchEvent or dispatchTouchEvent. + * + * @param ev The motion event + */ + public void onTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + Rect viewRect = new Rect(); + int[] leftTop = new int[2]; + mView.getLocationOnScreen(leftTop); + viewRect.set( + leftTop[0], + leftTop[1], + leftTop[0] + mView.getWidth(), + leftTop[1] + mView.getHeight()); + if (viewRect.contains((int) ev.getX(), (int) ev.getY())) { + if (isConsecutiveTap(ev)) { + mConsecutiveTapsCounter++; + } else { + mConsecutiveTapsCounter = 1; + } + mListener.onConsecutiveTaps(mConsecutiveTapsCounter); + } else { + // Touch outside the target view. Reset counter. + mConsecutiveTapsCounter = 0; + } + + if (mPreviousTapEvent != null) { + mPreviousTapEvent.recycle(); + } + mPreviousTapEvent = MotionEvent.obtain(ev); + } + } + + /** + * Resets the consecutive-tap counter to zero. + */ + public void resetCounter() { + mConsecutiveTapsCounter = 0; + } + + /** + * Returns true if the distance between consecutive tap is within + * {@link #mConsecutiveTapTouchSlopSquare}. False, otherwise. + */ + private boolean isConsecutiveTap(MotionEvent currentTapEvent) { + if (mPreviousTapEvent == null) { + return false; + } + + double deltaX = mPreviousTapEvent.getX() - currentTapEvent.getX(); + double deltaY = mPreviousTapEvent.getY() - currentTapEvent.getY(); + return (deltaX * deltaX + deltaY * deltaY <= mConsecutiveTapTouchSlopSquare); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/AbstractItem.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/AbstractItem.java new file mode 100644 index 0000000..047d18a --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/AbstractItem.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 com.android.setupwizardlib.items; + +import android.content.Context; +import android.util.AttributeSet; + +/** + * Abstract implementation of an item, which implements {@link IItem} and takes care of implementing + * methods for {@link ItemHierarchy} for items representing itself. + */ +public abstract class AbstractItem extends AbstractItemHierarchy implements IItem { + + public AbstractItem() { + super(); + } + + public AbstractItem(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public int getCount() { + return 1; + } + + @Override + public IItem getItemAt(int position) { + return this; + } + + @Override + public ItemHierarchy findItemById(int id) { + if (id == getId()) { + return this; + } + return null; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/AbstractItemHierarchy.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/AbstractItemHierarchy.java new file mode 100644 index 0000000..c56e3bc --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/AbstractItemHierarchy.java @@ -0,0 +1,67 @@ +/* + * 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 com.android.setupwizardlib.items; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +import com.android.setupwizardlib.R; + +import java.util.ArrayList; + +/** + * An abstract item hierarchy; provides default implementation for ID and observers. + */ +public abstract class AbstractItemHierarchy implements ItemHierarchy { + + private ArrayList mObservers = new ArrayList<>(); + private int mId = 0; + + public AbstractItemHierarchy() { + } + + public AbstractItemHierarchy(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwAbstractItem); + mId = a.getResourceId(R.styleable.SuwAbstractItem_android_id, 0); + a.recycle(); + } + + public void setId(int id) { + mId = id; + } + + public int getId() { + return mId; + } + + @Override + public void registerObserver(Observer observer) { + mObservers.add(observer); + } + + @Override + public void unregisterObserver(Observer observer) { + mObservers.remove(observer); + } + + public void notifyChanged() { + for (Observer observer : mObservers) { + observer.onChanged(this); + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ButtonBarItem.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ButtonBarItem.java new file mode 100644 index 0000000..55bbe75 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ButtonBarItem.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.items; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.android.setupwizardlib.R; + +import java.util.ArrayList; + +/** + * A list item with one or more buttons, declared as + * {@link com.android.setupwizardlib.items.ButtonItem}. + * + *

Example usage: + *

{@code
+ * <ButtonBarItem>
+ *
+ *     <ButtonItem
+ *         android:id="@+id/skip_button"
+ *         android:text="@string/skip_button_label />
+ *
+ *     <ButtonItem
+ *         android:id="@+id/next_button"
+ *         android:text="@string/next_button_label
+ *         android:theme="@style/SuwButtonItem.Colored" />
+ *
+ * </ButtonBarItem>
+ * }
+ */ +public class ButtonBarItem extends AbstractItem implements ItemInflater.ItemParent { + + private final ArrayList mButtons = new ArrayList<>(); + private boolean mVisible = true; + + public ButtonBarItem() { + super(); + } + + public ButtonBarItem(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public int getCount() { + return isVisible() ? 1 : 0; + } + + @Override + public boolean isEnabled() { + // The children buttons are enabled and clickable, but the item itself is not + return false; + } + + @Override + public int getLayoutResource() { + return R.layout.suw_items_button_bar; + } + + public void setVisible(boolean visible) { + mVisible = visible; + } + + public boolean isVisible() { + return mVisible; + } + + public int getViewId() { + return getId(); + } + + @Override + public void onBindView(View view) { + // Note: The efficiency could be improved by trying to recycle the buttons created by + // ButtonItem + final LinearLayout layout = (LinearLayout) view; + layout.removeAllViews(); + + for (ButtonItem buttonItem : mButtons) { + Button button = buttonItem.createButton(layout); + layout.addView(button); + } + + view.setId(getViewId()); + } + + @Override + public void addChild(ItemHierarchy child) { + if (child instanceof ButtonItem) { + mButtons.add((ButtonItem) child); + } else { + throw new UnsupportedOperationException("Cannot add non-button item to Button Bar"); + } + } + + @Override + public ItemHierarchy findItemById(int id) { + if (getId() == id) { + return this; + } + for (ButtonItem button : mButtons) { + final ItemHierarchy item = button.findItemById(id); + if (item != null) { + return item; + } + } + return null; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ButtonItem.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ButtonItem.java new file mode 100644 index 0000000..2ec6489 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ButtonItem.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.items; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import com.android.setupwizardlib.R; + +/** + * Description of a button inside {@link com.android.setupwizardlib.items.ButtonBarItem}. This item + * will not be bound by the adapter, and must be a child of {@code ButtonBarItem}. + */ +public class ButtonItem extends AbstractItem implements View.OnClickListener { + + public interface OnClickListener { + void onClick(ButtonItem item); + } + + private boolean mEnabled = true; + private CharSequence mText; + private int mTheme = R.style.SuwButtonItem; + private OnClickListener mListener; + + private Button mButton; + + public ButtonItem() { + super(); + } + + public ButtonItem(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwButtonItem); + mEnabled = a.getBoolean(R.styleable.SuwButtonItem_android_enabled, true); + mText = a.getText(R.styleable.SuwButtonItem_android_text); + mTheme = a.getResourceId(R.styleable.SuwButtonItem_android_theme, R.style.SuwButtonItem); + a.recycle(); + } + + public void setOnClickListener(OnClickListener listener) { + mListener = listener; + } + + public void setText(CharSequence text) { + mText = text; + } + + public CharSequence getText() { + return mText; + } + + /** + * The theme to use for this button. This can be used to create button of a particular style + * (e.g. a colored or borderless button). Typically {@code android:buttonStyle} will be set in + * the theme to change the style applied by the button. + * + * @param theme Resource ID of the theme + */ + public void setTheme(int theme) { + mTheme = theme; + mButton = null; + } + + /** + * @return Resource ID of the theme used by this button. + */ + public int getTheme() { + return mTheme; + } + + public void setEnabled(boolean enabled) { + mEnabled = enabled; + } + + @Override + public int getCount() { + return 0; + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public int getLayoutResource() { + return 0; + } + + /** + * Do not use this since ButtonItem is not directly part of a list. + */ + @Override + public final void onBindView(View view) { + throw new UnsupportedOperationException("Cannot bind to ButtonItem's view"); + } + + /** + * Create a button according to this button item. + * + * @param parent The parent of the button, used to retrieve the theme and context for this + * button. + * @return A button that can be added to the parent. + */ + protected Button createButton(ViewGroup parent) { + if (mButton == null) { + Context context = parent.getContext(); + if (mTheme != 0) { + context = new ContextThemeWrapper(context, mTheme); + } + mButton = new Button(context); + mButton.setOnClickListener(this); + } else { + if (mButton.getParent() instanceof ViewGroup) { + // A view cannot be added to a different parent if one already exists. Remove this + // button from its parent before returning. + ((ViewGroup) mButton.getParent()).removeView(mButton); + } + } + mButton.setEnabled(mEnabled); + mButton.setText(mText); + return mButton; + } + + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onClick(this); + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/GenericInflater.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/GenericInflater.java new file mode 100644 index 0000000..f3b6e19 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/GenericInflater.java @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.items; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.HashMap; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; +import android.view.ContextThemeWrapper; +import android.view.InflateException; + +/** + * Generic XML inflater. This class is modeled after {@code android.preference.GenericInflater}, + * which is in turn modeled after {@code LayoutInflater}. This can be used to recursively inflate a + * hierarchy of items. All items in the hierarchy must inherit the generic type {@code T}, and the + * specific implementation is expected to handle inserting child items into the parent, by + * implementing {@link #onAddChildItem(Object, Object)}. + * + * @param Type of the items to inflate + */ +public abstract class GenericInflater { + + private static final String TAG = "GenericInflater"; + private static final boolean DEBUG = false; + + protected final Context mContext; + + // these are optional, set by the caller + private boolean mFactorySet; + private Factory mFactory; + + private final Object[] mConstructorArgs = new Object[2]; + + private static final Class[] mConstructorSignature = new Class[] { + Context.class, AttributeSet.class}; + + private static final HashMap> sConstructorMap = new HashMap<>(); + + private String mDefaultPackage; + + public interface Factory { + /** + * Hook you can supply that is called when inflating from a + * inflater. You can use this to customize the tag + * names available in your XML files. + *

+ * Note that it is good practice to prefix these custom names with your + * package (i.e., com.coolcompany.apps) to avoid conflicts with system + * names. + * + * @param name Tag name to be inflated. + * @param context The context the item is being created in. + * @param attrs Inflation attributes as specified in XML file. + * @return Newly created item. Return null for the default behavior. + */ + T onCreateItem(String name, Context context, AttributeSet attrs); + } + + private static class FactoryMerger implements Factory { + private final Factory mF1, mF2; + + FactoryMerger(Factory f1, Factory f2) { + mF1 = f1; + mF2 = f2; + } + + public T onCreateItem(String name, Context context, AttributeSet attrs) { + T v = mF1.onCreateItem(name, context, attrs); + if (v != null) return v; + return mF2.onCreateItem(name, context, attrs); + } + } + + /** + * Create a new inflater instance associated with a + * particular Context. + * + * @param context The Context in which this inflater will + * create its items; most importantly, this supplies the theme + * from which the default values for their attributes are + * retrieved. + */ + protected GenericInflater(Context context) { + mContext = context; + } + + /** + * Create a new inflater instance that is a copy of an + * existing inflater, optionally with its Context + * changed. For use in implementing {@link #cloneInContext}. + * + * @param original The original inflater to copy. + * @param newContext The new Context to use. + */ + protected GenericInflater(GenericInflater original, Context newContext) { + mContext = newContext; + mFactory = original.mFactory; + } + + /** + * Create a copy of the existing inflater object, with the copy + * pointing to a different Context than the original. This is used by + * {@link ContextThemeWrapper} to create a new inflater to go along + * with the new Context theme. + * + * @param newContext The new Context to associate with the new inflater. + * May be the same as the original Context if desired. + * + * @return Returns a brand spanking new inflater object associated with + * the given Context. + */ + public abstract GenericInflater cloneInContext(Context newContext); + + /** + * Sets the default package that will be searched for classes to construct + * for tag names that have no explicit package. + * + * @param defaultPackage The default package. This will be prepended to the + * tag name, so it should end with a period. + */ + public void setDefaultPackage(String defaultPackage) { + mDefaultPackage = defaultPackage; + } + + /** + * Returns the default package, or null if it is not set. + * + * @see #setDefaultPackage(String) + * @return The default package. + */ + public String getDefaultPackage() { + return mDefaultPackage; + } + + /** + * Return the context we are running in, for access to resources, class + * loader, etc. + */ + public Context getContext() { + return mContext; + } + + /** + * Return the current factory (or null). This is called on each element + * name. If the factory returns an item, add that to the hierarchy. If it + * returns null, proceed to call onCreateItem(name). + */ + public final Factory getFactory() { + return mFactory; + } + + /** + * Attach a custom Factory interface for creating items while using this + * inflater. This must not be null, and can only be set + * once; after setting, you can not change the factory. This is called on + * each element name as the XML is parsed. If the factory returns an item, + * that is added to the hierarchy. If it returns null, the next factory + * default {@link #onCreateItem} method is called. + *

+ * If you have an existing inflater and want to add your + * own factory to it, use {@link #cloneInContext} to clone the existing + * instance and then you can use this function (once) on the returned new + * instance. This will merge your own factory with whatever factory the + * original instance is using. + */ + public void setFactory(Factory factory) { + if (mFactorySet) { + throw new IllegalStateException("" + + "A factory has already been set on this inflater"); + } + if (factory == null) { + throw new NullPointerException("Given factory can not be null"); + } + mFactorySet = true; + if (mFactory == null) { + mFactory = factory; + } else { + mFactory = new FactoryMerger<>(factory, mFactory); + } + } + + public T inflate(int resource) { + return inflate(resource, null); + } + + + /** + * Inflate a new item hierarchy from the specified xml resource. Throws + * InflaterException if there is an error. + * + * @param resource ID for an XML resource to load (e.g., + * R.layout.main_page) + * @param root Optional parent of the generated hierarchy. + * @return The root of the inflated hierarchy. If root was supplied, + * this is the root item; otherwise it is the root of the inflated + * XML file. + */ + public T inflate(int resource, T root) { + return inflate(resource, root, root != null); + } + + /** + * Inflate a new hierarchy from the specified xml node. Throws + * InflaterException if there is an error. * + *

+ * Important   For performance + * reasons, inflation relies heavily on pre-processing of XML files + * that is done at build time. Therefore, it is not currently possible to + * use inflater with an XmlPullParser over a plain XML file at runtime. + * + * @param parser XML dom node containing the description of the + * hierarchy. + * @param root Optional parent of the generated hierarchy. + * @return The root of the inflated hierarchy. If root was supplied, + * this is the that; otherwise it is the root of the inflated + * XML file. + */ + public T inflate(XmlPullParser parser, T root) { + return inflate(parser, root, root != null); + } + + /** + * Inflate a new hierarchy from the specified xml resource. Throws + * InflaterException if there is an error. + * + * @param resource ID for an XML resource to load (e.g., + * R.layout.main_page) + * @param root Optional root to be the parent of the generated hierarchy (if + * attachToRoot is true), or else simply an object that + * provides a set of values for root of the returned + * hierarchy (if attachToRoot is false.) + * @param attachToRoot Whether the inflated hierarchy should be attached to + * the root parameter? + * @return The root of the inflated hierarchy. If root was supplied and + * attachToRoot is true, this is root; otherwise it is the root of + * the inflated XML file. + */ + public T inflate(int resource, T root, boolean attachToRoot) { + if (DEBUG) Log.v(TAG, "INFLATING from resource: " + resource); + XmlResourceParser parser = getContext().getResources().getXml(resource); + try { + return inflate(parser, root, attachToRoot); + } finally { + parser.close(); + } + } + + /** + * Inflate a new hierarchy from the specified XML node. Throws + * InflaterException if there is an error. + *

+ * Important   For performance + * reasons, inflation relies heavily on pre-processing of XML files + * that is done at build time. Therefore, it is not currently possible to + * use inflater with an XmlPullParser over a plain XML file at runtime. + * + * @param parser XML dom node containing the description of the + * hierarchy. + * @param root Optional to be the parent of the generated hierarchy (if + * attachToRoot is true), or else simply an object that + * provides a set of values for root of the returned + * hierarchy (if attachToRoot is false.) + * @param attachToRoot Whether the inflated hierarchy should be attached to + * the root parameter? + * @return The root of the inflated hierarchy. If root was supplied and + * attachToRoot is true, this is root; otherwise it is the root of + * the inflated XML file. + */ + public T inflate(XmlPullParser parser, T root, boolean attachToRoot) { + synchronized (mConstructorArgs) { + final AttributeSet attrs = Xml.asAttributeSet(parser); + mConstructorArgs[0] = mContext; + T result; + + try { + // Look for the root node. + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + } + + if (type != XmlPullParser.START_TAG) { + throw new InflateException(parser.getPositionDescription() + + ": No start tag found!"); + } + + if (DEBUG) { + Log.v(TAG, "**************************"); + Log.v(TAG, "Creating root: " + + parser.getName()); + Log.v(TAG, "**************************"); + } + // Temp is the root that was found in the xml + T xmlRoot = createItemFromTag(parser, parser.getName(), attrs); + + result = onMergeRoots(root, attachToRoot, xmlRoot); + + if (DEBUG) Log.v(TAG, "-----> start inflating children"); + // Inflate all children under temp + rInflate(parser, result, attrs); + if (DEBUG) Log.v(TAG, "-----> done inflating children"); + } catch (XmlPullParserException e) { + InflateException ex = new InflateException(e.getMessage()); + ex.initCause(e); + throw ex; + } catch (IOException e) { + InflateException ex = new InflateException( + parser.getPositionDescription() + + ": " + e.getMessage()); + ex.initCause(e); + throw ex; + } + + return result; + } + } + + /** + * Low-level function for instantiating by name. This attempts to + * instantiate class of the given name found in this + * inflater's ClassLoader. + * + *

+ * There are two things that can happen in an error case: either the + * exception describing the error will be thrown, or a null will be + * returned. You must deal with both possibilities -- the former will happen + * the first time createItem() is called for a class of a particular name, + * the latter every time there-after for that class name. + * + * @param name The full name of the class to be instantiated. + * @param attrs The XML attributes supplied for this instance. + * + * @return The newly instantiated item, or null. + */ + public final T createItem(String name, String prefix, AttributeSet attrs) + 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); + constructor = clazz.getConstructor(mConstructorSignature); + constructor.setAccessible(true); + sConstructorMap.put(name, constructor); + } + + Object[] args = mConstructorArgs; + args[1] = attrs; + //noinspection unchecked + return (T) constructor.newInstance(args); + } catch (NoSuchMethodException e) { + InflateException ie = new InflateException(attrs.getPositionDescription() + + ": Error inflating class " + + (prefix != null ? (prefix + name) : name)); + ie.initCause(e); + throw ie; + + } catch (ClassNotFoundException e) { + // If loadClass fails, we should propagate the exception. + throw e; + } catch (Exception e) { + InflateException ie = new InflateException(attrs.getPositionDescription() + + ": Error inflating class " + + (prefix != null ? (prefix + name) : name)); + ie.initCause(e); + throw ie; + } + } + + /** + * This routine is responsible for creating the correct subclass of item + * given the xml element name. Override it to handle custom item objects. If + * you override this in your subclass be sure to call through to + * super.onCreateItem(name) for names you do not recognize. + * + * @param name The fully qualified class name of the item to be create. + * @param attrs An AttributeSet of attributes to apply to the item. + * @return The item created. + */ + protected T onCreateItem(String name, AttributeSet attrs) throws ClassNotFoundException { + return createItem(name, mDefaultPackage, attrs); + } + + private T createItemFromTag(XmlPullParser parser, String name, AttributeSet attrs) { + if (DEBUG) Log.v(TAG, "******** Creating item: " + name); + + try { + T item = (mFactory == null) ? null : mFactory.onCreateItem(name, mContext, attrs); + + if (item == null) { + if (-1 == name.indexOf('.')) { + item = onCreateItem(name, attrs); + } else { + item = createItem(name, null, attrs); + } + } + + if (DEBUG) Log.v(TAG, "Created item is: " + item); + return item; + + } catch (InflateException e) { + throw e; + + } catch (Exception e) { + InflateException ie = new InflateException(attrs + .getPositionDescription() + + ": Error inflating class " + name); + ie.initCause(e); + throw ie; + } + } + + /** + * Recursive method used to descend down the xml hierarchy and instantiate + * items, instantiate their children, and then call onFinishInflate(). + */ + private void rInflate(XmlPullParser parser, T node, final AttributeSet attrs) + throws XmlPullParserException, IOException { + final int depth = parser.getDepth(); + + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + if (onCreateCustomFromTag(parser, node, attrs)) { + continue; + } + + if (DEBUG) Log.v(TAG, "Now inflating tag: " + parser.getName()); + String name = parser.getName(); + + T item = createItemFromTag(parser, name, attrs); + + if (DEBUG) Log.v(TAG, "Creating params from parent: " + node); + + onAddChildItem(node, item); + + + if (DEBUG) Log.v(TAG, "-----> start inflating children"); + rInflate(parser, item, attrs); + if (DEBUG) Log.v(TAG, "-----> done inflating children"); + } + } + + /** + * Before this inflater tries to create an item from the tag, this method + * will be called. The parser will be pointing to the start of a tag, you + * must stop parsing and return when you reach the end of this element! + * + * @param parser XML dom node containing the description of the hierarchy. + * @param node The item that should be the parent of whatever you create. + * @param attrs An AttributeSet of attributes to apply to the item. + * @return Whether you created a custom object (true), or whether this + * inflater should proceed to create an item. + */ + protected boolean onCreateCustomFromTag(XmlPullParser parser, T node, + final AttributeSet attrs) throws XmlPullParserException { + return false; + } + + protected abstract void onAddChildItem(T parent, T child); + + protected T onMergeRoots(T givenRoot, boolean attachToGivenRoot, T xmlRoot) { + return xmlRoot; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/IItem.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/IItem.java new file mode 100644 index 0000000..26391dc --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/IItem.java @@ -0,0 +1,48 @@ +/* + * 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 com.android.setupwizardlib.items; + +import android.view.View; + +/** + * Representation of an item in an {@link ItemHierarchy}. + */ +public interface IItem { + + /** + * Get the Android resource ID for locating the layout for this item. + * + * @return Resource ID for the layout of this item. This layout will be used to inflate the View + * passed to {@link #onBindView(android.view.View)}. + */ + int getLayoutResource(); + + /** + * Called by items framework to display the data specified by this item. This method should + * update {@code view} to reflect its data. + * + * @param view A view inflated from {@link #getLayoutResource()}, which should be updated to + * display data from this item. This view may be recycled from other items with the + * same layout resource. + */ + void onBindView(View view); + + /** + * @return True if this item is enabled. + */ + boolean isEnabled(); +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/Item.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/Item.java new file mode 100644 index 0000000..796d533 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/Item.java @@ -0,0 +1,154 @@ +/* + * 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 com.android.setupwizardlib.items; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.setupwizardlib.R; + +/** + * Definition of an item in SetupWizardItemsLayout. An item is usually defined in XML and inflated + * using {@link ItemInflater}. + */ +public class Item extends AbstractItem { + + private boolean mEnabled = true; + private Drawable mIcon; + private int mLayoutRes; + private CharSequence mSummary; + private CharSequence mTitle; + private boolean mVisible = true; + + public Item() { + super(); + mLayoutRes = getDefaultLayoutResource(); + } + + public Item(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwItem); + mEnabled = a.getBoolean(R.styleable.SuwItem_android_enabled, true); + mIcon = a.getDrawable(R.styleable.SuwItem_android_icon); + mTitle = a.getText(R.styleable.SuwItem_android_title); + mSummary = a.getText(R.styleable.SuwItem_android_summary); + mLayoutRes = a.getResourceId(R.styleable.SuwItem_android_layout, + getDefaultLayoutResource()); + mVisible = a.getBoolean(R.styleable.SuwItem_android_visible, true); + a.recycle(); + } + + protected int getDefaultLayoutResource() { + return R.layout.suw_items_default; + } + + public void setEnabled(boolean enabled) { + mEnabled = enabled; + } + + @Override + public int getCount() { + return isVisible() ? 1 : 0; + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + public void setIcon(Drawable icon) { + mIcon = icon; + } + + public Drawable getIcon() { + return mIcon; + } + + public void setLayoutResource(int layoutResource) { + mLayoutRes = layoutResource; + } + + @Override + public int getLayoutResource() { + return mLayoutRes; + } + + public void setSummary(CharSequence summary) { + mSummary = summary; + } + + public CharSequence getSummary() { + return mSummary; + } + + public void setTitle(CharSequence title) { + mTitle = title; + } + + public CharSequence getTitle() { + return mTitle; + } + + public void setVisible(boolean visible) { + mVisible = visible; + } + + public boolean isVisible() { + return mVisible; + } + + public int getViewId() { + return getId(); + } + + @Override + public void onBindView(View view) { + TextView label = (TextView) view.findViewById(R.id.suw_items_title); + label.setText(getTitle()); + + TextView summaryView = (TextView) view.findViewById(R.id.suw_items_summary); + CharSequence summary = getSummary(); + if (summary != null && summary.length() > 0) { + summaryView.setText(summary); + summaryView.setVisibility(View.VISIBLE); + } else { + summaryView.setVisibility(View.GONE); + } + + final View iconContainer = view.findViewById(R.id.suw_items_icon_container); + final Drawable icon = getIcon(); + if (icon != null) { + final ImageView iconView = (ImageView) view.findViewById(R.id.suw_items_icon); + // Set the image drawable to null before setting the state and level to avoid affecting + // any recycled drawable in the ImageView + iconView.setImageDrawable(null); + iconView.setImageState(icon.getState(), false /* merge */); + iconView.setImageLevel(icon.getLevel()); + iconView.setImageDrawable(icon); + iconContainer.setVisibility(View.VISIBLE); + } else { + iconContainer.setVisibility(View.GONE); + } + + view.setId(getViewId()); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemAdapter.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemAdapter.java new file mode 100644 index 0000000..669d104 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemAdapter.java @@ -0,0 +1,131 @@ +/* + * 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 com.android.setupwizardlib.items; + +import android.util.SparseIntArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +/** + * An adapter typically used with ListView to display an + * {@link com.android.setupwizardlib.items.ItemHierarchy}. The item hierarchy used to create this + * adapter can be inflated by {@link ItemInflater} from XML. + */ +public class ItemAdapter extends BaseAdapter implements ItemHierarchy.Observer { + + private final ItemHierarchy mItemHierarchy; + private ViewTypes mViewTypes = new ViewTypes(); + + public ItemAdapter(ItemHierarchy hierarchy) { + mItemHierarchy = hierarchy; + mItemHierarchy.registerObserver(this); + refreshViewTypes(); + } + + @Override + public int getCount() { + return mItemHierarchy.getCount(); + } + + @Override + public IItem getItem(int position) { + return mItemHierarchy.getItemAt(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getItemViewType(int position) { + IItem item = getItem(position); + int layoutRes = item.getLayoutResource(); + return mViewTypes.get(layoutRes); + } + + @Override + public int getViewTypeCount() { + return mViewTypes.size(); + } + + private void refreshViewTypes() { + for (int i = 0; i < getCount(); i++) { + IItem item = getItem(i); + mViewTypes.add(item.getLayoutResource()); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + IItem item = getItem(position); + if (convertView == null) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + convertView = inflater.inflate(item.getLayoutResource(), parent, false); + } + item.onBindView(convertView); + return convertView; + } + + @Override + public void onChanged(ItemHierarchy hierarchy) { + refreshViewTypes(); + notifyDataSetChanged(); + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + public ItemHierarchy findItemById(int id) { + return mItemHierarchy.findItemById(id); + } + + public ItemHierarchy getRootItemHierarchy() { + return mItemHierarchy; + } + + /** + * A helper class to pack a sparse set of integers (e.g. resource IDs) to a contiguous list of + * integers (e.g. adapter positions), providing mapping to retrieve the original ID from a given + * position. This is used to pack the view types of the adapter into contiguous integers from + * a given layout resource. + */ + private static class ViewTypes { + private SparseIntArray mPositionMap = new SparseIntArray(); + private int nextPosition = 0; + + public int add(int id) { + if (mPositionMap.indexOfKey(id) < 0) { + mPositionMap.put(id, nextPosition); + nextPosition++; + } + return mPositionMap.get(id); + } + + public int size() { + return mPositionMap.size(); + } + + public int get(int id) { + return mPositionMap.get(id); + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemGroup.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemGroup.java new file mode 100644 index 0000000..e449a95 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemGroup.java @@ -0,0 +1,215 @@ +/* + * 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 com.android.setupwizardlib.items; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.SparseIntArray; + +import java.util.ArrayList; +import java.util.List; + +public class ItemGroup extends AbstractItemHierarchy implements ItemInflater.ItemParent, + ItemHierarchy.Observer { + + /* static section */ + + /** + * Binary search for the closest value that's smaller than or equal to {@code value}, and + * return the corresponding key. + */ + private static int binarySearch(SparseIntArray array, int value) { + final int size = array.size(); + int lo = 0; + int hi = size - 1; + + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final int midVal = array.valueAt(mid); + + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return array.keyAt(mid); // value found + } + } + // Value not found. Return the last item before our search range, which is the closest + // value smaller than the value we are looking for. + return array.keyAt(lo - 1); + } + + /* non-static section */ + + private List mChildren = new ArrayList<>(); + + /** + * A mapping from the index of an item hierarchy in mChildren, to the first position in which + * the corresponding child hierarchy represents. For example: + * + * ItemHierarchy Item Item Position + * Index + * + * 0 [ Wi-Fi AP 1 ] 0 + * | Wi-Fi AP 2 | 1 + * | Wi-Fi AP 3 | 2 + * | Wi-Fi AP 4 | 3 + * [ Wi-Fi AP 5 ] 4 + * + * 1 [ ] + * + * 2 [ Use cellular data ] 5 + * + * 3 [ Don't connect ] 6 + * + * For this example of Wi-Fi screen, the following mapping will be produced: + * [ 0 -> 0 | 2 -> 5 | 3 -> 6 ] + * + * Also note how ItemHierarchy index 1 is not present in the map, because it is empty. + * + * ItemGroup uses this map to look for which ItemHierarchy an item at a given position belongs + * to. + */ + private SparseIntArray mHierarchyStart = new SparseIntArray(); + + private int mCount = 0; + private boolean mDirty = false; + + public ItemGroup() { + super(); + } + + public ItemGroup(Context context, AttributeSet attrs) { + // Constructor for XML inflation + super(context, attrs); + } + + /** + * Add a child hierarchy to this item group. + */ + @Override + public void addChild(ItemHierarchy child) { + mChildren.add(child); + child.registerObserver(this); + onHierarchyChanged(); + } + + /** + * Remove a previously added child from this item group. + * + * @return True if there is a match for the child and it is removed. False if the child could + * not be found in our list of child hierarchies. + */ + public boolean removeChild(ItemHierarchy child) { + if (mChildren.remove(child)) { + child.unregisterObserver(this); + onHierarchyChanged(); + return true; + } + return false; + } + + /** + * Remove all children from this hierarchy. + */ + public void clear() { + if (mChildren.size() == 0) { + return; + } + + for (ItemHierarchy item : mChildren) { + item.unregisterObserver(this); + } + mChildren.clear(); + onHierarchyChanged(); + } + + @Override + public int getCount() { + updateDataIfNeeded(); + return mCount; + } + + @Override + public IItem getItemAt(int position) { + int itemIndex = getItemIndex(position); + ItemHierarchy item = mChildren.get(itemIndex); + int subpos = position - mHierarchyStart.get(itemIndex); + return item.getItemAt(subpos); + } + + @Override + public void onChanged(ItemHierarchy hierarchy) { + // Need to set dirty, because our children may have gotten more items. + mDirty = true; + notifyChanged(); + } + + private void onHierarchyChanged() { + onChanged(null); + } + + @Override + public ItemHierarchy findItemById(int id) { + if (id == getId()) { + return this; + } + for (ItemHierarchy child : mChildren) { + ItemHierarchy childFindItem = child.findItemById(id); + if (childFindItem != null) { + return childFindItem; + } + } + return null; + } + + /** + * If dirty, this method will recalculate the number of items and mHierarchyStart. + */ + private void updateDataIfNeeded() { + if (mDirty) { + mCount = 0; + mHierarchyStart.clear(); + for (int itemIndex = 0; itemIndex < mChildren.size(); itemIndex++) { + ItemHierarchy item = mChildren.get(itemIndex); + if (item.getCount() > 0) { + mHierarchyStart.put(itemIndex, mCount); + } + mCount += item.getCount(); + } + mDirty = false; + } + } + + /** + * Use binary search to locate the item hierarchy a position is contained in. + * + * @return Index of the item hierarchy which is responsible for the item at {@code position}. + */ + private int getItemIndex(int position) { + updateDataIfNeeded(); + if (position < 0 || position >= mCount) { + throw new IndexOutOfBoundsException("size=" + mCount + "; index=" + position); + } + int result = binarySearch(mHierarchyStart, position); + if (result < 0) { + throw new IllegalStateException("Cannot have item start index < 0"); + } + return result; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemHierarchy.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemHierarchy.java new file mode 100644 index 0000000..40a56d5 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemHierarchy.java @@ -0,0 +1,75 @@ +/* + * 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 com.android.setupwizardlib.items; + +/** + * Representation of zero or more items in a list. Each instance of ItemHierarchy should be capable + * of being wrapped in ItemAdapter and be displayed. + * + * For example, {@link com.android.setupwizardlib.items.Item} is a representation of a single item, + * typically with data provided from XML. {@link com.android.setupwizardlib.items.ItemGroup} + * represents a list of child item hierarchies it contains, but itself does not do any display. + */ +public interface ItemHierarchy { + + /** + * Observer for any changes in this hierarchy. If anything updated that causes this hierarchy to + * show different content, this observer should be called. + */ + interface Observer { + /** + * Called when an underlying data update that can cause this hierarchy to show different + * content has occurred. + */ + void onChanged(ItemHierarchy itemHierarchy); + } + + /** + * Register an observer to observe changes for this item hierarchy. + */ + void registerObserver(Observer observer); + + /** + * Unregister a previously registered observer. + */ + void unregisterObserver(Observer observer); + + /** + * @return the number of items this item hierarchy represent. + */ + int getCount(); + + /** + * Get the item at position. + * + * @param position An integer from 0 to {@link #getCount()}}, which indicates the position in + * this item hierarchy to get the child item. + * @return A representation of the item at {@code position}. Must not be {@code null}. + */ + IItem getItemAt(int position); + + /** + * Find an item hierarchy within this hierarchy which has the given ID. Or null if no match is + * found. This hierarchy will be returned if our ID matches. Same restrictions for Android + * resource IDs apply to this ID. In fact, typically this ID is a resource ID generated from + * XML. + * + * @param id An ID to search for in this item hierarchy. + * @return An ItemHierarchy which matches the given ID. + */ + ItemHierarchy findItemById(int id); +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemInflater.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemInflater.java new file mode 100644 index 0000000..cadf1a4 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/items/ItemInflater.java @@ -0,0 +1,64 @@ +/* + * 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 com.android.setupwizardlib.items; + +import android.content.Context; + +/** + * Inflate {@link Item} hierarchies from XML files. + * + * Modified from android.support.v7.preference.PreferenceInflater + */ +public class ItemInflater extends GenericInflater { + + private static final String TAG = "ItemInflater"; + + public interface ItemParent { + void addChild(ItemHierarchy child); + } + + private final Context mContext; + + public ItemInflater(Context context) { + super(context); + mContext = context; + setDefaultPackage(Item.class.getPackage().getName() + "."); + } + + @Override + public ItemInflater cloneInContext(Context newContext) { + return new ItemInflater(newContext); + } + + /** + * Return the context we are running in, for access to resources, class + * loader, etc. + */ + @Override + public Context getContext() { + return mContext; + } + + @Override + protected void onAddChildItem(ItemHierarchy parent, ItemHierarchy child) { + if (parent instanceof ItemParent) { + ((ItemParent) parent).addChild(child); + } else { + throw new IllegalArgumentException("Cannot add child item to " + parent); + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/span/LinkSpan.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/span/LinkSpan.java new file mode 100644 index 0000000..e4f9854 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/span/LinkSpan.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.span; + +import android.content.Context; +import android.graphics.Typeface; +import android.os.Build; +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.util.Log; +import android.view.View; + +/** + * A clickable span that will listen for click events and send it back to the context. To use this + * class, implement {@link com.android.setupwizardlib.span.LinkSpan.OnClickListener} in your + * context (typically your Activity). + * + *

Note on accessibility: For TalkBack to be able to traverse and interact with the links, you + * should use {@code LinkAccessibilityHelper} in your {@code TextView} subclass. Optionally you can + * also use {@code RichTextView}, which includes link support. + */ +public class LinkSpan extends ClickableSpan { + + /* + * Implementation note: When the orientation changes, TextView retains a reference to this span + * instead of writing it to a parcel (ClickableSpan is not Parcelable). If this class has any + * reference to the containing Activity (i.e. the activity context, or any views in the + * activity), it will cause memory leak. + */ + + /* static section */ + + private static final String TAG = "LinkSpan"; + + private static final Typeface TYPEFACE_MEDIUM = + Typeface.create("sans-serif-medium", Typeface.NORMAL); + + public interface OnClickListener { + void onClick(LinkSpan span); + } + + /* non-static section */ + + private final String mId; + + public LinkSpan(String id) { + mId = id; + } + + @Override + public void onClick(View view) { + final Context context = view.getContext(); + if (context instanceof OnClickListener) { + ((OnClickListener) context).onClick(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + view.cancelPendingInputEvents(); + } + } else { + Log.w(TAG, "Dropping click event. No listener attached."); + } + } + + @Override + public void updateDrawState(TextPaint drawState) { + super.updateDrawState(drawState); + drawState.setUnderlineText(false); + drawState.setTypeface(TYPEFACE_MEDIUM); + } + + public String getId() { + return mId; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/span/SpanHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/span/SpanHelper.java new file mode 100644 index 0000000..d75e338 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/span/SpanHelper.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.span; + +import android.text.Spannable; + +/** + * Contains helper methods for dealing with text spans, e.g. the ones in {@code android.text.style}. + */ +public class SpanHelper { + + /** + * Add {@code newSpan} at the same start and end indices as {@code oldSpan} and remove + * {@code oldSpan} from the {@code spannable}. + */ + public static void replaceSpan(Spannable spannable, Object oldSpan, Object newSpan) { + final int spanStart = spannable.getSpanStart(oldSpan); + final int spanEnd = spannable.getSpanEnd(oldSpan); + spannable.removeSpan(oldSpan); + spannable.setSpan(newSpan, spanStart, spanEnd, 0); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java new file mode 100644 index 0000000..2697371 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.util; + +import android.view.View; + +import com.android.setupwizardlib.view.NavigationBar; + +/** + * Add this helper to require the scroll view to be scrolled to the bottom, making sure that the + * user sees all content on the screen. This will change the navigation bar to show the more button + * instead of the next button when there is more content to be seen. When the more button is + * clicked, the scroll view will be scrolled one page down. + */ +public abstract class AbstractRequireScrollHelper implements View.OnClickListener { + + private final NavigationBar mNavigationBar; + + private boolean mScrollNeeded; + // Whether the user have seen the more button yet. + private boolean mScrollNotified = false; + + protected AbstractRequireScrollHelper(NavigationBar navigationBar) { + mNavigationBar = navigationBar; + } + + protected void requireScroll() { + mNavigationBar.getMoreButton().setOnClickListener(this); + } + + protected void notifyScrolledToBottom() { + if (mScrollNeeded) { + mNavigationBar.post(new Runnable() { + @Override + public void run() { + mNavigationBar.getNextButton().setVisibility(View.VISIBLE); + mNavigationBar.getMoreButton().setVisibility(View.GONE); + } + }); + mScrollNeeded = false; + mScrollNotified = true; + } + } + + protected void notifyRequiresScroll() { + if (!mScrollNeeded && !mScrollNotified) { + mNavigationBar.post(new Runnable() { + @Override + public void run() { + mNavigationBar.getNextButton().setVisibility(View.GONE); + mNavigationBar.getMoreButton().setVisibility(View.VISIBLE); + } + }); + mScrollNeeded = true; + } + } + + @Override + public void onClick(View view) { + pageScrollDown(); + } + + protected abstract void pageScrollDown(); +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java new file mode 100644 index 0000000..bf4c0c2 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java @@ -0,0 +1,77 @@ +/* + * 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 com.android.setupwizardlib.util; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.os.Build; +import android.view.View; + +/** + * Provides convenience methods to handle drawable layout directions in different SDK versions. + */ +public class DrawableLayoutDirectionHelper { + + /** + * Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction + * of {@code view}. + */ + public static InsetDrawable createRelativeInsetDrawable(Drawable drawable, + int insetStart, int insetTop, int insetEnd, int insetBottom, View view) { + boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 + && view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom, + isRtl); + } + + /** + * Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction + * of {@code context}. + */ + public static InsetDrawable createRelativeInsetDrawable(Drawable drawable, + int insetStart, int insetTop, int insetEnd, int insetBottom, Context context) { + boolean isRtl = false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + final int layoutDirection = + context.getResources().getConfiguration().getLayoutDirection(); + isRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL; + } + return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom, + isRtl); + } + + /** + * Creates an {@link android.graphics.drawable.InsetDrawable} according to + * {@code layoutDirection}. + */ + public static InsetDrawable createRelativeInsetDrawable(Drawable drawable, + int insetStart, int insetTop, int insetEnd, int insetBottom, int layoutDirection) { + //noinspection AndroidLintInlinedApi + return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom, + layoutDirection == View.LAYOUT_DIRECTION_RTL); + } + + private static InsetDrawable createRelativeInsetDrawable(Drawable drawable, + int insetStart, int insetTop, int insetEnd, int insetBottom, boolean isRtl) { + if (isRtl) { + return new InsetDrawable(drawable, insetEnd, insetTop, insetStart, insetBottom); + } else { + return new InsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom); + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java new file mode 100644 index 0000000..7877569 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.util; + +import android.os.Build; +import android.widget.AbsListView; +import android.widget.ListAdapter; +import android.widget.ListView; + +import com.android.setupwizardlib.view.NavigationBar; + +/** + * Add this helper to require the list view to be scrolled to the bottom, making sure that the + * user sees all content on the screen. This will change the navigation bar to show the more button + * instead of the next button when there is more content to be seen. When the more button is + * clicked, the list view will be scrolled one page down. + */ +public class ListViewRequireScrollHelper extends AbstractRequireScrollHelper + implements AbsListView.OnScrollListener { + + public static void requireScroll(NavigationBar navigationBar, ListView listView) { + new ListViewRequireScrollHelper(navigationBar, listView).requireScroll(); + } + + private final ListView mListView; + + private ListViewRequireScrollHelper(NavigationBar navigationBar, ListView listView) { + super(navigationBar); + mListView = listView; + } + + @Override + protected void requireScroll() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { + // APIs to scroll a list only exists on Froyo or above. + super.requireScroll(); + mListView.setOnScrollListener(this); + + final ListAdapter adapter = mListView.getAdapter(); + if (mListView.getLastVisiblePosition() < adapter.getCount()) { + notifyRequiresScroll(); + } + } + } + + @Override + protected void pageScrollDown() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { + final int height = mListView.getHeight(); + mListView.smoothScrollBy(height, 500); + } + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + if (firstVisibleItem + visibleItemCount >= totalItemCount) { + notifyScrolledToBottom(); + } else { + notifyRequiresScroll(); + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/Partner.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/Partner.java new file mode 100644 index 0000000..fd187d9 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/Partner.java @@ -0,0 +1,162 @@ +/* + * 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 com.android.setupwizardlib.util; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import com.android.setupwizardlib.annotations.VisibleForTesting; + +/** + * Utilities to discover and interact with partner customizations. An overlay package is one that + * registers the broadcast receiver for {@code com.android.setupwizard.action.PARTNER_CUSTOMIZATION} + * in its manifest. There can only be one customization APK on a device, and it must be bundled with + * the system. + * + *

Derived from {@code com.android.launcher3/Partner.java} + */ +public class Partner { + private static final String TAG = "(SUW) Partner"; + + /** Marker action used to discover partner */ + private static final String + ACTION_PARTNER_CUSTOMIZATION = "com.android.setupwizard.action.PARTNER_CUSTOMIZATION"; + + private static boolean sSearched = false; + private static Partner sPartner; + + /** + * Convenience to get a drawable from partner overlay, or if not available, the drawable from + * the original context. + * + * @see #getResourceEntry(android.content.Context, int) + */ + public static Drawable getDrawable(Context context, int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getDrawable(entry.id); + } + + /** + * Convenience to get a string from partner overlay, or if not available, the string from the + * original context. + * + * @see #getResourceEntry(android.content.Context, int) + */ + public static String getString(Context context, int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getString(entry.id); + } + + /** + * Find an entry of resource in the overlay package provided by partners. It will first look for + * the resource in the overlay package, and if not available, will return the one in the + * original context. + * + * @return a ResourceEntry in the partner overlay's resources, if one is defined. Otherwise the + * resources from the original context is returned. Clients can then get the resource by + * {@code entry.resources.getString(entry.id)}, or other methods available in + * {@link android.content.res.Resources}. + */ + public static ResourceEntry getResourceEntry(Context context, int id) { + final Partner partner = Partner.get(context); + if (partner != null) { + final Resources ourResources = context.getResources(); + final String name = ourResources.getResourceEntryName(id); + final String type = ourResources.getResourceTypeName(id); + final int partnerId = partner.getIdentifier(name, type); + if (partnerId != 0) { + return new ResourceEntry(partner.mResources, partnerId, true); + } + } + return new ResourceEntry(context.getResources(), id, false); + } + + public static class ResourceEntry { + public Resources resources; + public int id; + public boolean isOverlay; + + ResourceEntry(Resources resources, int id, boolean isOverlay) { + this.resources = resources; + this.id = id; + this.isOverlay = isOverlay; + } + } + + /** + * Find and return partner details, or {@code null} if none exists. A partner package is marked + * by a broadcast receiver declared in the manifest that handles the + * {@code com.android.setupwizard.action.PARTNER_CUSTOMIZATION} intent action. The overlay + * package must also be a system package. + */ + public static synchronized Partner get(Context context) { + if (!sSearched) { + PackageManager pm = context.getPackageManager(); + final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION); + for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) { + if (info.activityInfo == null) { + continue; + } + final ApplicationInfo appInfo = info.activityInfo.applicationInfo; + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + try { + final Resources res = pm.getResourcesForApplication(appInfo); + sPartner = new Partner(appInfo.packageName, res); + break; + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + appInfo.packageName); + } + } + } + sSearched = true; + } + return sPartner; + } + + @VisibleForTesting + public static synchronized void resetForTesting() { + sSearched = false; + sPartner = null; + } + + private final String mPackageName; + private final Resources mResources; + + private Partner(String packageName, Resources res) { + mPackageName = packageName; + mResources = res; + } + + public String getPackageName() { + return mPackageName; + } + + public Resources getResources() { + return mResources; + } + + public int getIdentifier(String name, String defType) { + return mResources.getIdentifier(name, defType, mPackageName); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/RequireScrollHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/RequireScrollHelper.java new file mode 100644 index 0000000..cce336f --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/RequireScrollHelper.java @@ -0,0 +1,64 @@ +/* + * 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 com.android.setupwizardlib.util; + +import android.widget.ScrollView; + +import com.android.setupwizardlib.view.BottomScrollView; +import com.android.setupwizardlib.view.NavigationBar; + +/** + * Add this helper to require the scroll view to be scrolled to the bottom, making sure that the + * user sees all content on the screen. This will change the navigation bar to show the more button + * instead of the next button when there is more content to be seen. When the more button is + * clicked, the scroll view will be scrolled one page down. + */ +public class RequireScrollHelper extends AbstractRequireScrollHelper + implements BottomScrollView.BottomScrollListener { + + public static void requireScroll(NavigationBar navigationBar, BottomScrollView scrollView) { + new RequireScrollHelper(navigationBar, scrollView).requireScroll(); + } + + private final BottomScrollView mScrollView; + + private RequireScrollHelper(NavigationBar navigationBar, BottomScrollView scrollView) { + super(navigationBar); + mScrollView = scrollView; + } + + @Override + protected void requireScroll() { + super.requireScroll(); + mScrollView.setBottomScrollListener(this); + } + + @Override + protected void pageScrollDown() { + mScrollView.pageScroll(ScrollView.FOCUS_DOWN); + } + + @Override + public void onScrolledToBottom() { + notifyScrolledToBottom(); + } + + @Override + public void onRequiresScroll() { + notifyRequiresScroll(); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ResultCodes.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ResultCodes.java new file mode 100644 index 0000000..a429e73 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ResultCodes.java @@ -0,0 +1,28 @@ +/* + * 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 com.android.setupwizardlib.util; + +import static android.app.Activity.RESULT_FIRST_USER; + +public final class ResultCodes { + + public static final int RESULT_SKIP = RESULT_FIRST_USER; + public static final int RESULT_RETRY = RESULT_FIRST_USER + 1; + public static final int RESULT_ACTIVITY_NOT_FOUND = RESULT_FIRST_USER + 2; + + public static final int RESULT_FIRST_SETUP_USER = RESULT_FIRST_USER + 100; +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/SystemBarHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/SystemBarHelper.java new file mode 100644 index 0000000..44bcefc --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/SystemBarHelper.java @@ -0,0 +1,348 @@ +/* + * 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 com.android.setupwizardlib.util; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Dialog; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Handler; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; + +import com.android.setupwizardlib.R; + +/** + * A helper class to manage the system navigation bar and status bar. This will add various + * systemUiVisibility flags to the given Window or View to make them follow the Setup Wizard style. + * + * When the useImmersiveMode intent extra is true, a screen in Setup Wizard should hide the system + * bars using methods from this class. For Lollipop, {@link #hideSystemBars(android.view.Window)} + * will completely hide the system navigation bar and change the status bar to transparent, and + * layout the screen contents (usually the illustration) behind it. + */ +public class SystemBarHelper { + + private static final String TAG = "SystemBarHelper"; + + @SuppressLint("InlinedApi") + private static final int DEFAULT_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + + @SuppressLint("InlinedApi") + private static final int DIALOG_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + /** + * Needs to be equal to View.STATUS_BAR_DISABLE_BACK + */ + private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; + + /** + * The maximum number of retries when peeking the decor view. When polling for the decor view, + * waiting it to be installed, set a maximum number of retries. + */ + private static final int PEEK_DECOR_VIEW_RETRIES = 3; + + /** + * Hide the navigation bar for a dialog. + * + *

This will only take effect in versions Lollipop or above. Otherwise this is a no-op. + */ + public static void hideSystemBars(final Dialog dialog) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + final Window window = dialog.getWindow(); + temporarilyDisableDialogFocus(window); + addVisibilityFlag(window, DIALOG_IMMERSIVE_FLAGS); + addImmersiveFlagsToDecorView(window, DIALOG_IMMERSIVE_FLAGS); + + // Also set the navigation bar and status bar to transparent color. Note that this + // doesn't work if android.R.boolean.config_enableTranslucentDecor is false. + window.setNavigationBarColor(0); + window.setStatusBarColor(0); + } + } + + /** + * Hide the navigation bar, make the color of the status and navigation bars transparent, and + * specify {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} flag so that the content is laid-out + * behind the transparent status bar. This is commonly used with + * {@link android.app.Activity#getWindow()} to make the navigation and status bars follow the + * Setup Wizard style. + * + *

This will only take effect in versions Lollipop or above. Otherwise this is a no-op. + */ + public static void hideSystemBars(final Window window) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + addVisibilityFlag(window, DEFAULT_IMMERSIVE_FLAGS); + addImmersiveFlagsToDecorView(window, DEFAULT_IMMERSIVE_FLAGS); + + // Also set the navigation bar and status bar to transparent color. Note that this + // doesn't work if android.R.boolean.config_enableTranslucentDecor is false. + window.setNavigationBarColor(0); + window.setStatusBarColor(0); + } + } + + /** + * Revert the actions of hideSystemBars. Note that this will remove the system UI visibility + * flags regardless of whether it is originally present. You should also manually reset the + * navigation bar and status bar colors, as this method doesn't know what value to revert it to. + */ + public static void showSystemBars(final Dialog dialog, final Context context) { + showSystemBars(dialog.getWindow(), context); + } + + /** + * Revert the actions of hideSystemBars. Note that this will remove the system UI visibility + * flags regardless of whether it is originally present. You should also manually reset the + * navigation bar and status bar colors, as this method doesn't know what value to revert it to. + */ + public static void showSystemBars(final Window window, final Context context) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + removeVisibilityFlag(window, DEFAULT_IMMERSIVE_FLAGS); + removeImmersiveFlagsFromDecorView(window, DEFAULT_IMMERSIVE_FLAGS); + + if (context != null) { + //noinspection AndroidLintInlinedApi + final TypedArray typedArray = context.obtainStyledAttributes(new int[]{ + android.R.attr.statusBarColor, android.R.attr.navigationBarColor}); + final int statusBarColor = typedArray.getColor(0, 0); + final int navigationBarColor = typedArray.getColor(1, 0); + window.setStatusBarColor(statusBarColor); + window.setNavigationBarColor(navigationBarColor); + typedArray.recycle(); + } + } + } + + /** + * Convenience method to add a visibility flag in addition to the existing ones. + */ + public static void addVisibilityFlag(final View view, final int flag) { + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + final int vis = view.getSystemUiVisibility(); + view.setSystemUiVisibility(vis | flag); + } + } + + /** + * Convenience method to add a visibility flag in addition to the existing ones. + */ + public static void addVisibilityFlag(final Window window, final int flag) { + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.systemUiVisibility |= flag; + window.setAttributes(attrs); + } + } + + /** + * Convenience method to remove a visibility flag from the view, leaving other flags that are + * not specified intact. + */ + public static void removeVisibilityFlag(final View view, final int flag) { + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + final int vis = view.getSystemUiVisibility(); + view.setSystemUiVisibility(vis & ~flag); + } + } + + /** + * Convenience method to remove a visibility flag from the window, leaving other flags that are + * not specified intact. + */ + public static void removeVisibilityFlag(final Window window, final int flag) { + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.systemUiVisibility &= ~flag; + window.setAttributes(attrs); + } + } + + public static void setBackButtonVisible(final Window window, final boolean visible) { + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + if (visible) { + removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + } else { + addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + } + } + } + + /** + * Set a view to be resized when the keyboard is shown. This will set the bottom margin of the + * view to be immediately above the keyboard, and assumes that the view sits immediately above + * the navigation bar. + * + *

Note that you must set {@link android.R.attr#windowSoftInputMode} to {@code adjustResize} + * for this class to work. Otherwise window insets are not dispatched and this method will have + * no effect. + * + *

This will only take effect in versions Lollipop or above. Otherwise this is a no-op. + * + * @param view The view to be resized when the keyboard is shown. + */ + public static void setImeInsetView(final View view) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + view.setOnApplyWindowInsetsListener(new WindowInsetsListener()); + } + } + + /** + * Add the specified immersive flags to the decor view of the window, because + * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} only takes effect when it is added to a view + * instead of the window. + */ + @TargetApi(VERSION_CODES.LOLLIPOP) + private static void addImmersiveFlagsToDecorView(final Window window, final int vis) { + getDecorView(window, new OnDecorViewInstalledListener() { + @Override + public void onDecorViewInstalled(View decorView) { + addVisibilityFlag(decorView, vis); + } + }); + } + + @TargetApi(VERSION_CODES.LOLLIPOP) + private static void removeImmersiveFlagsFromDecorView(final Window window, final int vis) { + getDecorView(window, new OnDecorViewInstalledListener() { + @Override + public void onDecorViewInstalled(View decorView) { + removeVisibilityFlag(decorView, vis); + } + }); + } + + private static void getDecorView(Window window, OnDecorViewInstalledListener callback) { + new DecorViewFinder().getDecorView(window, callback, PEEK_DECOR_VIEW_RETRIES); + } + + private static class DecorViewFinder { + + private final Handler mHandler = new Handler(); + private Window mWindow; + private int mRetries; + private OnDecorViewInstalledListener mCallback; + + private Runnable mCheckDecorViewRunnable = new Runnable() { + @Override + public void run() { + // Use peekDecorView instead of getDecorView so that clients can still set window + // features after calling this method. + final View decorView = mWindow.peekDecorView(); + if (decorView != null) { + mCallback.onDecorViewInstalled(decorView); + } else { + mRetries--; + if (mRetries >= 0) { + // If the decor view is not installed yet, try again in the next loop. + mHandler.post(mCheckDecorViewRunnable); + } else { + Log.w(TAG, "Cannot get decor view of window: " + mWindow); + } + } + } + }; + + public void getDecorView(Window window, OnDecorViewInstalledListener callback, + int retries) { + mWindow = window; + mRetries = retries; + mCallback = callback; + mCheckDecorViewRunnable.run(); + } + } + + private interface OnDecorViewInstalledListener { + + void onDecorViewInstalled(View decorView); + } + + /** + * Apply a hack to temporarily set the window to not focusable, so that the navigation bar + * will not show up during the transition. + */ + private static void temporarilyDisableDialogFocus(final Window window) { + window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + // Add the SOFT_INPUT_IS_FORWARD_NAVIGATION_FLAG. This is normally done by the system when + // FLAG_NOT_FOCUSABLE is not set. Setting this flag allows IME to be shown automatically + // if the dialog has editable text fields. + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION); + new Handler().post(new Runnable() { + @Override + public void run() { + window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + } + }); + } + + @TargetApi(VERSION_CODES.LOLLIPOP) + private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener { + private int mBottomOffset; + private boolean mHasCalculatedBottomOffset = false; + + @Override + public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { + if (!mHasCalculatedBottomOffset) { + mBottomOffset = getBottomDistance(view); + mHasCalculatedBottomOffset = true; + } + + int bottomInset = insets.getSystemWindowInsetBottom(); + + final int bottomMargin = Math.max( + insets.getSystemWindowInsetBottom() - mBottomOffset, 0); + + final ViewGroup.MarginLayoutParams lp = + (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + // Check that we have enough space to apply the bottom margins before applying it. + // Otherwise the framework may think that the view is empty and exclude it from layout. + if (bottomMargin < lp.bottomMargin + view.getHeight()) { + lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin); + view.setLayoutParams(lp); + bottomInset = 0; + } + + + return insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), + bottomInset + ); + } + } + + private static int getBottomDistance(View view) { + int[] coords = new int[2]; + view.getLocationInWindow(coords); + return view.getRootView().getHeight() - coords[1] - view.getHeight(); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/WizardManagerHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/WizardManagerHelper.java new file mode 100644 index 0000000..10172ce --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/WizardManagerHelper.java @@ -0,0 +1,216 @@ +/* + * 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 com.android.setupwizardlib.util; + +import android.content.Context; +import android.content.Intent; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.provider.Settings; + +public class WizardManagerHelper { + + private static final String ACTION_NEXT = "com.android.wizard.NEXT"; + + /* + * EXTRA_SCRIPT_URI and EXTRA_ACTION_ID will be removed once all outstanding references have + * transitioned to using EXTRA_WIZARD_BUNDLE. + */ + @Deprecated + private static final String EXTRA_SCRIPT_URI = "scriptUri"; + @Deprecated + private static final String EXTRA_ACTION_ID = "actionId"; + + private static final String EXTRA_WIZARD_BUNDLE = "wizardBundle"; + private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode"; + private static final String EXTRA_IS_FIRST_RUN = "firstRun"; + + public static final String EXTRA_THEME = "theme"; + public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode"; + + public static final String SETTINGS_GLOBAL_DEVICE_PROVISIONED = "device_provisioned"; + public static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; + + public static final String THEME_HOLO = "holo"; + public static final String THEME_HOLO_LIGHT = "holo_light"; + public static final String THEME_MATERIAL = "material"; + public static final String THEME_MATERIAL_LIGHT = "material_light"; + + /** + * @deprecated This constant is not used and will not be passed by any released version of setup + * wizard. + */ + @Deprecated + public static final String THEME_MATERIAL_BLUE = "material_blue"; + + /** + * @deprecated This constant is not used and will not be passed by any released version of setup + * wizard. + */ + @Deprecated + public static final String THEME_MATERIAL_BLUE_LIGHT = "material_blue_light"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the + * theme used in setup wizard for NYC. + */ + public static final String THEME_GLIF = "glif"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in + * setup wizard for NYC. + */ + public static final String THEME_GLIF_LIGHT = "glif_light"; + + /** + * Get an intent that will invoke the next step of setup wizard. + * + * @param originalIntent The original intent that was used to start the step, usually via + * {@link android.app.Activity#getIntent()}. + * @param resultCode The result code of the step. See {@link ResultCodes}. + * @return A new intent that can be used with + * {@link android.app.Activity#startActivityForResult(Intent, int)} to start the next + * step of the setup flow. + */ + public static Intent getNextIntent(Intent originalIntent, int resultCode) { + return getNextIntent(originalIntent, resultCode, null); + } + + /** + * Get an intent that will invoke the next step of setup wizard. + * + * @param originalIntent The original intent that was used to start the step, usually via + * {@link android.app.Activity#getIntent()}. + * @param resultCode The result code of the step. See {@link ResultCodes}. + * @param data An intent containing extra result data. + * @return A new intent that can be used with + * {@link android.app.Activity#startActivityForResult(Intent, int)} to start the next + * step of the setup flow. + */ + public static Intent getNextIntent(Intent originalIntent, int resultCode, Intent data) { + Intent intent = new Intent(ACTION_NEXT); + copyWizardManagerExtras(originalIntent, intent); + intent.putExtra(EXTRA_RESULT_CODE, resultCode); + if (data != null && data.getExtras() != null) { + intent.putExtras(data.getExtras()); + } + intent.putExtra(EXTRA_THEME, originalIntent.getStringExtra(EXTRA_THEME)); + + return intent; + } + + /** + * Copy the internal extras used by setup wizard from one intent to another. For low-level use + * only, such as when using {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT} to relay to another + * intent. + * + * @param srcIntent Intent to get the wizard manager extras from. + * @param dstIntent Intent to copy the wizard manager extras to. + */ + public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) { + dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE)); + dstIntent.putExtra(EXTRA_IS_FIRST_RUN, + srcIntent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false)); + dstIntent.putExtra(EXTRA_SCRIPT_URI, srcIntent.getStringExtra(EXTRA_SCRIPT_URI)); + dstIntent.putExtra(EXTRA_ACTION_ID, srcIntent.getStringExtra(EXTRA_ACTION_ID)); + } + + /** + * Check whether an intent is intended to be used within the setup wizard flow. + * + * @param intent The intent to be checked, usually from + * {@link android.app.Activity#getIntent()}. + * @return true if the intent passed in was intended to be used with setup wizard. + */ + public static boolean isSetupWizardIntent(Intent intent) { + return intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false); + } + + /** + * Checks whether the current user has completed Setup Wizard. This is true if the current user + * has gone through Setup Wizard. The current user may or may not be the device owner and the + * device owner may have already completed setup wizard. + * + * @param context The context to retrieve the settings. + * @return true if the current user has completed Setup Wizard. + * @see #isDeviceProvisioned(android.content.Context) + */ + public static boolean isUserSetupComplete(Context context) { + if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) { + return Settings.Secure.getInt(context.getContentResolver(), + SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + } else { + // For versions below JB MR1, there are no user profiles. Just return the global device + // provisioned state. + return Settings.Secure.getInt(context.getContentResolver(), + SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1; + } + } + + /** + * Checks whether the device is provisioned. This means that the device has gone through Setup + * Wizard at least once. Note that the user can still be in Setup Wizard even if this is true, + * for a secondary user profile triggered through Settings > Add account. + * + * @param context The context to retrieve the settings. + * @return true if the device is provisioned. + * @see #isUserSetupComplete(android.content.Context) + */ + public static boolean isDeviceProvisioned(Context context) { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + return Settings.Global.getInt(context.getContentResolver(), + SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1; + } else { + return Settings.Secure.getInt(context.getContentResolver(), + SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1; + } + } + + /** + * Checks the intent whether the extra indicates that the light theme should be used or not. If + * the theme is not specified in the intent, or the theme specified is unknown, the value def + * will be returned. + * + * @param intent The intent used to start the activity, which the theme extra will be read from. + * @param def The default value if the theme is not specified. + * @return True if the activity started by the given intent should use light theme. + */ + public static boolean isLightTheme(Intent intent, boolean def) { + final String theme = intent.getStringExtra(EXTRA_THEME); + return isLightTheme(theme, def); + } + + /** + * Checks whether {@code theme} represents a light or dark theme. If the theme specified is + * unknown, the value def will be returned. + * + * @param theme The theme as specified from an intent sent from setup wizard. + * @param def The default value if the theme is not known. + * @return True if {@code theme} represents a light theme. + */ + public static boolean isLightTheme(String theme, boolean def) { + if (THEME_HOLO_LIGHT.equals(theme) || THEME_MATERIAL_LIGHT.equals(theme) + || THEME_MATERIAL_BLUE_LIGHT.equals(theme) || THEME_GLIF_LIGHT.equals(theme)) { + return true; + } else if (THEME_HOLO.equals(theme) || THEME_MATERIAL.equals(theme) + || THEME_MATERIAL_BLUE.equals(theme) || THEME_GLIF.equals(theme)) { + return false; + } else { + return def; + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/view/BottomScrollView.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/BottomScrollView.java new file mode 100644 index 0000000..b08cfc8 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/BottomScrollView.java @@ -0,0 +1,104 @@ +/* + * 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 com.android.setupwizardlib.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ScrollView; + +import com.android.setupwizardlib.annotations.VisibleForTesting; + +/** + * An extension of ScrollView that will invoke a listener callback when the ScrollView needs + * scrolling, and when the ScrollView is being scrolled to the bottom. This is often used in Setup + * Wizard as a way to ensure that users see all the content before proceeding. + */ +public class BottomScrollView extends ScrollView { + + public interface BottomScrollListener { + void onScrolledToBottom(); + void onRequiresScroll(); + } + + private BottomScrollListener mListener; + private int mScrollThreshold; + private boolean mRequiringScroll = false; + + private final Runnable mCheckScrollRunnable = new Runnable() { + @Override + public void run() { + checkScroll(); + } + }; + + public BottomScrollView(Context context) { + super(context); + } + + public BottomScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BottomScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setBottomScrollListener(BottomScrollListener l) { + mListener = l; + } + + @VisibleForTesting + public int getScrollThreshold() { + return mScrollThreshold; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + final View child = getChildAt(0); + if (child != null) { + mScrollThreshold = Math.max(0, child.getMeasuredHeight() - b + t - getPaddingBottom()); + } + if (b - t > 0) { + // Post check scroll in the next run loop, so that the callback methods will be invoked + // after the layout pass. This way a new layout pass will be scheduled if view + // properties are changed in the callbacks. + post(mCheckScrollRunnable); + } + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + if (oldt != t) { + checkScroll(); + } + } + + private void checkScroll() { + if (mListener != null) { + if (getScrollY() >= mScrollThreshold) { + mListener.onScrolledToBottom(); + } else if (!mRequiringScroll) { + mRequiringScroll = true; + mListener.onRequiresScroll(); + } + } + } + +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/view/Illustration.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/Illustration.java new file mode 100644 index 0000000..b576174 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/Illustration.java @@ -0,0 +1,221 @@ +/* + * 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 com.android.setupwizardlib.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.util.LayoutDirection; +import android.view.Gravity; +import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; + +import com.android.setupwizardlib.R; + +/** + * Class to draw the illustration of setup wizard. The {@code aspectRatio} attribute determines the + * aspect ratio of the top padding, which leaves space for the illustration. Draws the illustration + * drawable to fit the width of the view and fills the rest with the background. + * + *

If an aspect ratio is set, then the aspect ratio of the source drawable is maintained. + * Otherwise the the aspect ratio will be ignored, only increasing the width of the illustration. + */ +public class Illustration extends FrameLayout { + + // Size of the baseline grid in pixels + private float mBaselineGridSize; + private Drawable mBackground; + private Drawable mIllustration; + private final Rect mViewBounds = new Rect(); + private final Rect mIllustrationBounds = new Rect(); + private float mScale = 1.0f; + private float mAspectRatio = 0.0f; + + public Illustration(Context context) { + super(context); + init(null, 0); + } + + public Illustration(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public Illustration(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + // All the constructors delegate to this init method. The 3-argument constructor is not + // available in FrameLayout before v11, so call super with the exact same arguments. + private void init(AttributeSet attrs, int defStyleAttr) { + if (attrs != null) { + TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.SuwIllustration, defStyleAttr, 0); + mAspectRatio = a.getFloat(R.styleable.SuwIllustration_suwAspectRatio, 0.0f); + a.recycle(); + } + // Number of pixels of the 8dp baseline grid as defined in material design specs + mBaselineGridSize = getResources().getDisplayMetrics().density * 8; + setWillNotDraw(false); + } + + /** + * The background will be drawn to fill up the rest of the view. It will also be scaled by the + * same amount as the foreground so their textures look the same. + */ + // Override the deprecated setBackgroundDrawable method to support API < 16. View.setBackground + // forwards to setBackgroundDrawable in the framework implementation. + @SuppressWarnings("deprecation") + @Override + public void setBackgroundDrawable(Drawable background) { + if (background == mBackground) { + return; + } + mBackground = background; + invalidate(); + requestLayout(); + } + + /** + * Sets the drawable used as the illustration. The drawable is expected to have intrinsic + * width and height defined and will be scaled to fit the width of the view. + */ + public void setIllustration(Drawable illustration) { + if (illustration == mIllustration) { + return; + } + mIllustration = illustration; + invalidate(); + requestLayout(); + } + + /** + * Set the aspect ratio reserved for the illustration. This overrides the top padding of the + * view according to the width of this view and the aspect ratio. Children views will start + * being laid out below this aspect ratio. + * + * @param aspectRatio A float value specifying the aspect ratio (= width / height). 0 to not + * override the top padding. + */ + public void setAspectRatio(float aspectRatio) { + mAspectRatio = aspectRatio; + invalidate(); + requestLayout(); + } + + @Override + @Deprecated + public void setForeground(Drawable d) { + setIllustration(d); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mAspectRatio != 0.0f) { + int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + int illustrationHeight = (int) (parentWidth / mAspectRatio); + illustrationHeight -= illustrationHeight % mBaselineGridSize; + setPadding(0, illustrationHeight, 0, 0); + } + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + //noinspection AndroidLintInlinedApi + setOutlineProvider(ViewOutlineProvider.BOUNDS); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int layoutWidth = right - left; + final int layoutHeight = bottom - top; + if (mIllustration != null) { + int intrinsicWidth = mIllustration.getIntrinsicWidth(); + int intrinsicHeight = mIllustration.getIntrinsicHeight(); + + mViewBounds.set(0, 0, layoutWidth, layoutHeight); + if (mAspectRatio != 0f) { + mScale = layoutWidth / (float) intrinsicWidth; + intrinsicWidth = layoutWidth; + intrinsicHeight = (int) (intrinsicHeight * mScale); + } + Gravity.apply(Gravity.FILL_HORIZONTAL | Gravity.TOP, intrinsicWidth, + intrinsicHeight, mViewBounds, mIllustrationBounds); + mIllustration.setBounds(mIllustrationBounds); + } + if (mBackground != null) { + // Scale the background bounds by the same scale to compensate for the scale done to the + // canvas in onDraw. + mBackground.setBounds(0, 0, (int) Math.ceil(layoutWidth / mScale), + (int) Math.ceil((layoutHeight - mIllustrationBounds.height()) / mScale)); + } + super.onLayout(changed, left, top, right, bottom); + } + + @Override + public void onDraw(Canvas canvas) { + if (mBackground != null) { + // Draw the background filling parts not covered by the illustration + canvas.save(); + canvas.translate(0, mIllustrationBounds.height()); + // Scale the background so its size matches the foreground + canvas.scale(mScale, mScale, 0, 0); + if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1 && + shouldMirrorDrawable(mBackground, getLayoutDirection())) { + // Flip the illustration for RTL layouts + canvas.scale(-1, 1); + canvas.translate(-mBackground.getBounds().width(), 0); + } + mBackground.draw(canvas); + canvas.restore(); + } + if (mIllustration != null) { + canvas.save(); + if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1 && + shouldMirrorDrawable(mIllustration, getLayoutDirection())) { + // Flip the illustration for RTL layouts + canvas.scale(-1, 1); + canvas.translate(-mIllustrationBounds.width(), 0); + } + // Draw the illustration + mIllustration.draw(canvas); + canvas.restore(); + } + super.onDraw(canvas); + } + + private boolean shouldMirrorDrawable(Drawable drawable, int layoutDirection) { + if (layoutDirection == LayoutDirection.RTL) { + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + return drawable.isAutoMirrored(); + } else if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + final int flags = getContext().getApplicationInfo().flags; + //noinspection AndroidLintInlinedApi + return (flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0; + } + } + return false; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java new file mode 100644 index 0000000..e9ab1a7 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import com.android.setupwizardlib.R; + +/** + * A FrameLayout subclass that has an "intrinsic size", which is the size it wants to be if that is + * within the constraints given by the parent. The intrinsic size can be set with the + * {@code android:width} and {@code android:height} attributes in XML. + * + * Note that for the intrinsic size to be meaningful, {@code android:layout_width} and/or + * {@code android:layout_height} will need to be {@code wrap_content}. + */ +public class IntrinsicSizeFrameLayout extends FrameLayout { + + private int mIntrinsicHeight = 0; + private int mIntrinsicWidth = 0; + + public IntrinsicSizeFrameLayout(Context context) { + super(context); + init(context, null, 0); + } + + public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SuwMaxSizeFrameLayout, defStyleAttr, 0); + mIntrinsicHeight = + a.getDimensionPixelSize(R.styleable.SuwMaxSizeFrameLayout_android_height, 0); + mIntrinsicWidth = + a.getDimensionPixelSize(R.styleable.SuwMaxSizeFrameLayout_android_width, 0); + a.recycle(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(getIntrinsicMeasureSpec(widthMeasureSpec, mIntrinsicWidth), + getIntrinsicMeasureSpec(heightMeasureSpec, mIntrinsicHeight)); + } + + private int getIntrinsicMeasureSpec(int measureSpec, int intrinsicSize) { + if (intrinsicSize <= 0) { + // Intrinsic size is not set, just return the original spec + return measureSpec; + } + final int mode = MeasureSpec.getMode(measureSpec); + final int size = MeasureSpec.getSize(measureSpec); + if (mode == MeasureSpec.UNSPECIFIED) { + // Parent did not give any constraint, so we'll be the intrinsic size + return MeasureSpec.makeMeasureSpec(mIntrinsicHeight, MeasureSpec.EXACTLY); + } else if (mode == MeasureSpec.AT_MOST) { + // If intrinsic size is within parents constraint, take the intrinsic size. + // Otherwise take the parents size because that's closest to the intrinsic size. + return MeasureSpec.makeMeasureSpec(Math.min(size, mIntrinsicHeight), + MeasureSpec.EXACTLY); + } + // Parent specified EXACTLY, or in all other cases, just return the original spec + return measureSpec; + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/view/NavigationBar.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/NavigationBar.java new file mode 100644 index 0000000..228d875 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/NavigationBar.java @@ -0,0 +1,138 @@ +/* + * 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 com.android.setupwizardlib.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.android.setupwizardlib.R; + +/** + * Custom navigation bar for use with setup wizard. This bar contains a back button, more button and + * next button. By default, the more button is hidden, and typically the next button will be hidden + * if the more button is shown. + * + * @see com.android.setupwizardlib.util.RequireScrollHelper + */ +public class NavigationBar extends LinearLayout implements View.OnClickListener { + + /** + * An interface to listen to events of the navigation bar, namely when the user clicks on the + * back or next button. + */ + public interface NavigationBarListener { + void onNavigateBack(); + void onNavigateNext(); + } + + private static int getNavbarTheme(Context context) { + // Normally we can automatically guess the theme by comparing the foreground color against + // the background color. But we also allow specifying explicitly using suwNavBarTheme. + TypedArray attributes = context.obtainStyledAttributes( + new int[] { + R.attr.suwNavBarTheme, + android.R.attr.colorForeground, + android.R.attr.colorBackground }); + int theme = attributes.getResourceId(0, 0); + if (theme == 0) { + // Compare the value of the foreground against the background color to see if current + // theme is light-on-dark or dark-on-light. + float[] foregroundHsv = new float[3]; + float[] backgroundHsv = new float[3]; + Color.colorToHSV(attributes.getColor(1, 0), foregroundHsv); + Color.colorToHSV(attributes.getColor(2, 0), backgroundHsv); + boolean isDarkBg = foregroundHsv[2] > backgroundHsv[2]; + theme = isDarkBg ? R.style.SuwNavBarThemeDark : R.style.SuwNavBarThemeLight; + } + attributes.recycle(); + return theme; + } + + private static Context getThemedContext(Context context) { + final int theme = getNavbarTheme(context); + return new ContextThemeWrapper(context, theme); + } + + private Button mNextButton; + private Button mBackButton; + private Button mMoreButton; + private NavigationBarListener mListener; + + public NavigationBar(Context context) { + super(getThemedContext(context)); + init(); + } + + public NavigationBar(Context context, AttributeSet attrs) { + super(getThemedContext(context), attrs); + init(); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public NavigationBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(getThemedContext(context), attrs, defStyleAttr); + init(); + } + + // All the constructors delegate to this init method. The 3-argument constructor is not + // available in LinearLayout before v11, so call super with the exact same arguments. + private void init() { + View.inflate(getContext(), R.layout.suw_navbar_view, this); + mNextButton = (Button) findViewById(R.id.suw_navbar_next); + mBackButton = (Button) findViewById(R.id.suw_navbar_back); + mMoreButton = (Button) findViewById(R.id.suw_navbar_more); + } + + public Button getBackButton() { + return mBackButton; + } + + public Button getNextButton() { + return mNextButton; + } + + public Button getMoreButton() { + return mMoreButton; + } + + public void setNavigationBarListener(NavigationBarListener listener) { + mListener = listener; + if (mListener != null) { + getBackButton().setOnClickListener(this); + getNextButton().setOnClickListener(this); + } + } + + @Override + public void onClick(View view) { + if (mListener != null) { + if (view == getBackButton()) { + mListener.onNavigateBack(); + } else if (view == getNextButton()) { + mListener.onNavigateNext(); + } + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/view/NavigationBarButton.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/NavigationBarButton.java new file mode 100644 index 0000000..45d3737 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/NavigationBarButton.java @@ -0,0 +1,32 @@ +/* + * 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 com.android.setupwizardlib.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Button; + +public class NavigationBarButton extends Button { + + public NavigationBarButton(Context context) { + super(context); + } + + public NavigationBarButton(Context context, AttributeSet attrs) { + super(context, attrs); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java new file mode 100644 index 0000000..fe0bc8f --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java @@ -0,0 +1,113 @@ +/* + * 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 com.android.setupwizardlib.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.view.WindowInsets; +import android.widget.FrameLayout; + +import com.android.setupwizardlib.R; + +/** + * A FrameLayout subclass that will responds to onApplyWindowInsets to draw a drawable in the top + * inset area, making a background effect for the navigation bar. To make use of this layout, + * specify the system UI visibility {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} and + * set specify fitsSystemWindows. + * + *

This view is a normal FrameLayout if either of those are not set, or if the platform version + * is lower than Lollipop. + */ +public class StatusBarBackgroundLayout extends FrameLayout { + + private Drawable mStatusBarBackground; + private Object mLastInsets; // Use generic Object type for compatibility + + public StatusBarBackgroundLayout(Context context) { + super(context); + init(context, null, 0); + } + + public StatusBarBackgroundLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public StatusBarBackgroundLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SuwStatusBarBackgroundLayout, defStyleAttr, 0); + final Drawable statusBarBackground = + a.getDrawable(R.styleable.SuwStatusBarBackgroundLayout_suwStatusBarBackground); + setStatusBarBackground(statusBarBackground); + a.recycle(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (mLastInsets == null) { + requestApplyInsets(); + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (mLastInsets != null) { + final int insetTop = ((WindowInsets) mLastInsets).getSystemWindowInsetTop(); + if (insetTop > 0) { + mStatusBarBackground.setBounds(0, 0, getWidth(), insetTop); + mStatusBarBackground.draw(canvas); + } + } + } + } + + public void setStatusBarBackground(Drawable background) { + mStatusBarBackground = background; + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + setWillNotDraw(background == null); + setFitsSystemWindows(background != null); + invalidate(); + } + } + + public Drawable getStatusBarBackground() { + return mStatusBarBackground; + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mLastInsets = insets; + return super.onApplyWindowInsets(insets); + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StickyHeaderListView.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StickyHeaderListView.java new file mode 100644 index 0000000..58274f6 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StickyHeaderListView.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 com.android.setupwizardlib.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.RectF; +import android.os.Build; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowInsets; +import android.view.accessibility.AccessibilityEvent; +import android.widget.ListView; + +import com.android.setupwizardlib.R; + +/** + * This class provides sticky header functionality in a list view, to use with + * SetupWizardIllustration. To use this, add a header tagged with "sticky", or a header tagged with + * "stickyContainer" and one of its child tagged as "sticky". The sticky container will be drawn + * when the sticky element hits the top of the view. + * + *

There are a few things to note: + *

    + *
  1. The two supported scenarios are StickyHeaderListView -> Header (stickyContainer) -> sticky, + * and StickyHeaderListView -> Header (sticky). The arrow (->) represents parent/child + * relationship and must be immediate child. + *
  2. The view does not work well with padding. b/16190933 + *
  3. If fitsSystemWindows is true, then this will offset the sticking position by the height of + * the system decorations at the top of the screen. + *
+ * + * @see StickyHeaderScrollView + */ +public class StickyHeaderListView extends ListView { + + private View mSticky; + private View mStickyContainer; + private int mStatusBarInset = 0; + private RectF mStickyRect = new RectF(); + + public StickyHeaderListView(Context context) { + super(context); + init(null, android.R.attr.listViewStyle); + } + + public StickyHeaderListView(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, android.R.attr.listViewStyle); + } + + public StickyHeaderListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + final TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.SuwStickyHeaderListView, defStyleAttr, 0); + int headerResId = a.getResourceId(R.styleable.SuwStickyHeaderListView_suwHeader, 0); + if (headerResId != 0) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + View header = inflater.inflate(headerResId, this, false); + addHeaderView(header, null, false); + } + a.recycle(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (mSticky == null) { + updateStickyView(); + } + } + + public void updateStickyView() { + mSticky = findViewWithTag("sticky"); + mStickyContainer = findViewWithTag("stickyContainer"); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (mStickyRect.contains(ev.getX(), ev.getY())) { + ev.offsetLocation(-mStickyRect.left, -mStickyRect.top); + return mStickyContainer.dispatchTouchEvent(ev); + } else { + return super.dispatchTouchEvent(ev); + } + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (mSticky != null) { + final int saveCount = canvas.save(); + // The view to draw when sticking to the top + final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky; + // The offset to draw the view at when sticky + final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0; + // Position of the draw target, relative to the outside of the scrollView + final int drawTop = drawTarget.getTop(); + if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) { + // ListView does not translate the canvas, so we can simply draw at the top + mStickyRect.set(0, -drawOffset + mStatusBarInset, drawTarget.getWidth(), + drawTarget.getHeight() - drawOffset + mStatusBarInset); + canvas.translate(0, mStickyRect.top); + canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight()); + drawTarget.draw(canvas); + } else { + mStickyRect.setEmpty(); + } + canvas.restoreToCount(saveCount); + } + } + + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if (getFitsSystemWindows()) { + mStatusBarInset = insets.getSystemWindowInsetTop(); + insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + 0, /* top */ + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom() + ); + } + return insets; + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + + // Decoration-only headers should not count as an item for accessibility, adjust the + // accessibility event to account for that. + final int numberOfHeaders = mSticky != null ? 1 : 0; + event.setItemCount(event.getItemCount() - numberOfHeaders); + event.setFromIndex(Math.max(event.getFromIndex() - numberOfHeaders, 0)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + event.setToIndex(Math.max(event.getToIndex() - numberOfHeaders, 0)); + } + } +} diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StickyHeaderScrollView.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StickyHeaderScrollView.java new file mode 100644 index 0000000..ca47446 --- /dev/null +++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/view/StickyHeaderScrollView.java @@ -0,0 +1,117 @@ +/* + * 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 com.android.setupwizardlib.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.view.WindowInsets; + +/** + * This class provides sticky header functionality in a scroll view, to use with + * SetupWizardIllustration. To use this, add a subview tagged with "sticky", or a subview tagged + * with "stickyContainer" and one of its child tagged as "sticky". The sticky container will be + * drawn when the sticky element hits the top of the view. + * + *

There are a few things to note: + *

    + *
  1. The two supported scenarios are StickyHeaderScrollView -> subview (stickyContainer) -> + * sticky, and StickyHeaderScrollView -> container -> subview (sticky). + * The arrow (->) represents parent/child relationship and must be immediate child. + *
  2. If fitsSystemWindows is true, then this will offset the sticking position by the height of + * the system decorations at the top of the screen. + *
  3. For versions before Honeycomb, this will behave like a regular ScrollView. + *
+ * + * @see StickyHeaderListView + */ +public class StickyHeaderScrollView extends BottomScrollView { + + private View mSticky; + private View mStickyContainer; + private int mStatusBarInset = 0; + + public StickyHeaderScrollView(Context context) { + super(context); + } + + public StickyHeaderScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public StickyHeaderScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (mSticky == null) { + updateStickyView(); + } + updateStickyHeaderPosition(); + } + + public void updateStickyView() { + mSticky = findViewWithTag("sticky"); + mStickyContainer = findViewWithTag("stickyContainer"); + } + + private void updateStickyHeaderPosition() { + // Note: for pre-Honeycomb the header will not be moved, so this ScrollView essentially + // behaves like a normal BottomScrollView. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + if (mSticky != null) { + // The view to draw when sticking to the top + final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky; + // The offset to draw the view at when sticky + final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0; + // Position of the draw target, relative to the outside of the scrollView + final int drawTop = drawTarget.getTop() - getScrollY(); + if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) { + // ScrollView translates the whole canvas so we have to compensate for that + drawTarget.setTranslationY(getScrollY() - drawOffset); + } else { + drawTarget.setTranslationY(0); + } + } + } + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + updateStickyHeaderPosition(); + } + + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if (getFitsSystemWindows()) { + mStatusBarInset = insets.getSystemWindowInsetTop(); + insets = insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + 0, /* top */ + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom() + ); + } + return insets; + } +} diff --git a/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_back_in.xml b/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_back_in.xml new file mode 100644 index 0000000..18e6e80 --- /dev/null +++ b/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_back_in.xml @@ -0,0 +1,22 @@ + + + + diff --git a/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_back_out.xml b/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_back_out.xml new file mode 100644 index 0000000..a8d1ebf --- /dev/null +++ b/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_back_out.xml @@ -0,0 +1,22 @@ + + + + diff --git a/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_next_in.xml b/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_next_in.xml new file mode 100644 index 0000000..f0144c5 --- /dev/null +++ b/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_next_in.xml @@ -0,0 +1,22 @@ + + + + diff --git a/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_next_out.xml b/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_next_out.xml new file mode 100644 index 0000000..3b68945 --- /dev/null +++ b/setupwizardlib/src/main/res/anim-ldrtl/suw_slide_next_out.xml @@ -0,0 +1,22 @@ + + + + diff --git a/setupwizardlib/src/main/res/anim/suw_slide_back_in.xml b/setupwizardlib/src/main/res/anim/suw_slide_back_in.xml new file mode 100644 index 0000000..f0144c5 --- /dev/null +++ b/setupwizardlib/src/main/res/anim/suw_slide_back_in.xml @@ -0,0 +1,22 @@ + + + + diff --git a/setupwizardlib/src/main/res/anim/suw_slide_back_out.xml b/setupwizardlib/src/main/res/anim/suw_slide_back_out.xml new file mode 100644 index 0000000..3b68945 --- /dev/null +++ b/setupwizardlib/src/main/res/anim/suw_slide_back_out.xml @@ -0,0 +1,22 @@ + + + + diff --git a/setupwizardlib/src/main/res/anim/suw_slide_next_in.xml b/setupwizardlib/src/main/res/anim/suw_slide_next_in.xml new file mode 100644 index 0000000..18e6e80 --- /dev/null +++ b/setupwizardlib/src/main/res/anim/suw_slide_next_in.xml @@ -0,0 +1,22 @@ + + + + diff --git a/setupwizardlib/src/main/res/anim/suw_slide_next_out.xml b/setupwizardlib/src/main/res/anim/suw_slide_next_out.xml new file mode 100644 index 0000000..a8d1ebf --- /dev/null +++ b/setupwizardlib/src/main/res/anim/suw_slide_next_out.xml @@ -0,0 +1,22 @@ + + + + diff --git a/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_back_in.xml b/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_back_in.xml new file mode 100644 index 0000000..195c8b1 --- /dev/null +++ b/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_back_in.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_back_out.xml b/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_back_out.xml new file mode 100644 index 0000000..a910233 --- /dev/null +++ b/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_back_out.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_next_in.xml b/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_next_in.xml new file mode 100644 index 0000000..1b8c68e --- /dev/null +++ b/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_next_in.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_next_out.xml b/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_next_out.xml new file mode 100644 index 0000000..9442096 --- /dev/null +++ b/setupwizardlib/src/main/res/animator-ldrtl-v11/suw_slide_next_out.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/setupwizardlib/src/main/res/animator-v11/suw_slide_back_in.xml b/setupwizardlib/src/main/res/animator-v11/suw_slide_back_in.xml new file mode 100644 index 0000000..1b8c68e --- /dev/null +++ b/setupwizardlib/src/main/res/animator-v11/suw_slide_back_in.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/setupwizardlib/src/main/res/animator-v11/suw_slide_back_out.xml b/setupwizardlib/src/main/res/animator-v11/suw_slide_back_out.xml new file mode 100644 index 0000000..9442096 --- /dev/null +++ b/setupwizardlib/src/main/res/animator-v11/suw_slide_back_out.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/setupwizardlib/src/main/res/animator-v11/suw_slide_next_in.xml b/setupwizardlib/src/main/res/animator-v11/suw_slide_next_in.xml new file mode 100644 index 0000000..195c8b1 --- /dev/null +++ b/setupwizardlib/src/main/res/animator-v11/suw_slide_next_in.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/setupwizardlib/src/main/res/animator-v11/suw_slide_next_out.xml b/setupwizardlib/src/main/res/animator-v11/suw_slide_next_out.xml new file mode 100644 index 0000000..a910233 --- /dev/null +++ b/setupwizardlib/src/main/res/animator-v11/suw_slide_next_out.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_back.xml b/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_back.xml new file mode 100644 index 0000000..d0bff09 --- /dev/null +++ b/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_back.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_more.xml b/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_more.xml new file mode 100644 index 0000000..f21205d --- /dev/null +++ b/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_more.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_next.xml b/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_next.xml new file mode 100644 index 0000000..714731d --- /dev/null +++ b/setupwizardlib/src/main/res/drawable-anydpi-v21/suw_navbar_ic_next.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/setupwizardlib/src/main/res/drawable-v21/suw_card_bg.xml b/setupwizardlib/src/main/res/drawable-v21/suw_card_bg.xml new file mode 100644 index 0000000..13d1d4e --- /dev/null +++ b/setupwizardlib/src/main/res/drawable-v21/suw_card_bg.xml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/drawable-v21/suw_navbar_btn_bg.xml b/setupwizardlib/src/main/res/drawable-v21/suw_navbar_btn_bg.xml new file mode 100644 index 0000000..198f7f0 --- /dev/null +++ b/setupwizardlib/src/main/res/drawable-v21/suw_navbar_btn_bg.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/setupwizardlib/src/main/res/drawable/suw_layout_background.xml b/setupwizardlib/src/main/res/drawable/suw_layout_background.xml new file mode 100644 index 0000000..affe95f --- /dev/null +++ b/setupwizardlib/src/main/res/drawable/suw_layout_background.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout-v21/suw_progress_bar.xml b/setupwizardlib/src/main/res/layout-v21/suw_progress_bar.xml new file mode 100644 index 0000000..cbb69ea --- /dev/null +++ b/setupwizardlib/src/main/res/layout-v21/suw_progress_bar.xml @@ -0,0 +1,25 @@ + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_blank_template_card.xml b/setupwizardlib/src/main/res/layout/suw_glif_blank_template_card.xml new file mode 100644 index 0000000..5e3da11 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_blank_template_card.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_blank_template_compact.xml b/setupwizardlib/src/main/res/layout/suw_glif_blank_template_compact.xml new file mode 100644 index 0000000..0111682 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_blank_template_compact.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_header.xml b/setupwizardlib/src/main/res/layout/suw_glif_header.xml new file mode 100644 index 0000000..549426d --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_header.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_list_template_card.xml b/setupwizardlib/src/main/res/layout/suw_glif_list_template_card.xml new file mode 100644 index 0000000..ebbcde0 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_list_template_card.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_list_template_compact.xml b/setupwizardlib/src/main/res/layout/suw_glif_list_template_compact.xml new file mode 100644 index 0000000..3c5a8f5 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_list_template_compact.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_list_template_content.xml b/setupwizardlib/src/main/res/layout/suw_glif_list_template_content.xml new file mode 100644 index 0000000..b0eec9a --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_list_template_content.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_template_card.xml b/setupwizardlib/src/main/res/layout/suw_glif_template_card.xml new file mode 100644 index 0000000..1f9c0d7 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_template_card.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_template_compact.xml b/setupwizardlib/src/main/res/layout/suw_glif_template_compact.xml new file mode 100644 index 0000000..d3dcad8 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_template_compact.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_glif_template_content.xml b/setupwizardlib/src/main/res/layout/suw_glif_template_content.xml new file mode 100644 index 0000000..c25e444 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_glif_template_content.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_items_button_bar.xml b/setupwizardlib/src/main/res/layout/suw_items_button_bar.xml new file mode 100644 index 0000000..9704311 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_items_button_bar.xml @@ -0,0 +1,23 @@ + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_items_default.xml b/setupwizardlib/src/main/res/layout/suw_items_default.xml new file mode 100644 index 0000000..0799a41 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_items_default.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_items_description.xml b/setupwizardlib/src/main/res/layout/suw_items_description.xml new file mode 100644 index 0000000..3fa1766 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_items_description.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_items_verbose.xml b/setupwizardlib/src/main/res/layout/suw_items_verbose.xml new file mode 100644 index 0000000..f1c135d --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_items_verbose.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_list_header.xml b/setupwizardlib/src/main/res/layout/suw_list_header.xml new file mode 100644 index 0000000..3b21082 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_list_header.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_list_template_card.xml b/setupwizardlib/src/main/res/layout/suw_list_template_card.xml new file mode 100644 index 0000000..1d491ae --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_list_template_card.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_list_template_card_wide.xml b/setupwizardlib/src/main/res/layout/suw_list_template_card_wide.xml new file mode 100644 index 0000000..203d55a --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_list_template_card_wide.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_list_template_header.xml b/setupwizardlib/src/main/res/layout/suw_list_template_header.xml new file mode 100644 index 0000000..8e10cfa --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_list_template_header.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_list_template_header_collapsed.xml b/setupwizardlib/src/main/res/layout/suw_list_template_header_collapsed.xml new file mode 100644 index 0000000..a85417b --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_list_template_header_collapsed.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_navbar_view.xml b/setupwizardlib/src/main/res/layout/suw_navbar_view.xml new file mode 100644 index 0000000..38d0ce6 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_navbar_view.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_no_scroll_template_card.xml b/setupwizardlib/src/main/res/layout/suw_no_scroll_template_card.xml new file mode 100644 index 0000000..f6c2cab --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_no_scroll_template_card.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_no_scroll_template_card_wide.xml b/setupwizardlib/src/main/res/layout/suw_no_scroll_template_card_wide.xml new file mode 100644 index 0000000..a09b9b6 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_no_scroll_template_card_wide.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_no_scroll_template_header.xml b/setupwizardlib/src/main/res/layout/suw_no_scroll_template_header.xml new file mode 100644 index 0000000..413b329 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_no_scroll_template_header.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_no_scroll_template_header_collapsed.xml b/setupwizardlib/src/main/res/layout/suw_no_scroll_template_header_collapsed.xml new file mode 100644 index 0000000..f8af7a7 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_no_scroll_template_header_collapsed.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_progress_bar_stub.xml b/setupwizardlib/src/main/res/layout/suw_progress_bar_stub.xml new file mode 100644 index 0000000..3a03375 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_progress_bar_stub.xml @@ -0,0 +1,25 @@ + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_template_card.xml b/setupwizardlib/src/main/res/layout/suw_template_card.xml new file mode 100644 index 0000000..d79035b --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_template_card.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_template_card_wide.xml b/setupwizardlib/src/main/res/layout/suw_template_card_wide.xml new file mode 100644 index 0000000..d4d0f1f --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_template_card_wide.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_template_header.xml b/setupwizardlib/src/main/res/layout/suw_template_header.xml new file mode 100644 index 0000000..7945dcf --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_template_header.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/layout/suw_template_header_collapsed.xml b/setupwizardlib/src/main/res/layout/suw_template_header_collapsed.xml new file mode 100644 index 0000000..dab4df4 --- /dev/null +++ b/setupwizardlib/src/main/res/layout/suw_template_header_collapsed.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/values-af/strings.xml b/setupwizardlib/src/main/res/values-af/strings.xml new file mode 100644 index 0000000..a049c89 --- /dev/null +++ b/setupwizardlib/src/main/res/values-af/strings.xml @@ -0,0 +1,23 @@ + + + + + "Volgende" + "Terug" + "Meer" + diff --git a/setupwizardlib/src/main/res/values-am/strings.xml b/setupwizardlib/src/main/res/values-am/strings.xml new file mode 100644 index 0000000..7409b64 --- /dev/null +++ b/setupwizardlib/src/main/res/values-am/strings.xml @@ -0,0 +1,23 @@ + + + + + "ቀጣይ" + "ተመለስ" + "ተጨማሪ" + diff --git a/setupwizardlib/src/main/res/values-ar/strings.xml b/setupwizardlib/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..b1c2d5e --- /dev/null +++ b/setupwizardlib/src/main/res/values-ar/strings.xml @@ -0,0 +1,23 @@ + + + + + "التالي" + "رجوع" + "المزيد" + diff --git a/setupwizardlib/src/main/res/values-az-rAZ/strings.xml b/setupwizardlib/src/main/res/values-az-rAZ/strings.xml new file mode 100644 index 0000000..f361ed7 --- /dev/null +++ b/setupwizardlib/src/main/res/values-az-rAZ/strings.xml @@ -0,0 +1,23 @@ + + + + + "Növbəti" + "Geri" + "Daha çox" + diff --git a/setupwizardlib/src/main/res/values-b+sr+Latn/strings.xml b/setupwizardlib/src/main/res/values-b+sr+Latn/strings.xml new file mode 100644 index 0000000..3f0b0c3 --- /dev/null +++ b/setupwizardlib/src/main/res/values-b+sr+Latn/strings.xml @@ -0,0 +1,23 @@ + + + + + "Dalje" + "Nazad" + "Još" + diff --git a/setupwizardlib/src/main/res/values-be-rBY/strings.xml b/setupwizardlib/src/main/res/values-be-rBY/strings.xml new file mode 100644 index 0000000..dd2d5d8 --- /dev/null +++ b/setupwizardlib/src/main/res/values-be-rBY/strings.xml @@ -0,0 +1,23 @@ + + + + + "Далей" + "Назад" + "Яшчэ" + diff --git a/setupwizardlib/src/main/res/values-bg/strings.xml b/setupwizardlib/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000..a88ed6c --- /dev/null +++ b/setupwizardlib/src/main/res/values-bg/strings.xml @@ -0,0 +1,23 @@ + + + + + "Напред" + "Назад" + "Още" + diff --git a/setupwizardlib/src/main/res/values-bn-rBD/strings.xml b/setupwizardlib/src/main/res/values-bn-rBD/strings.xml new file mode 100644 index 0000000..64182c6 --- /dev/null +++ b/setupwizardlib/src/main/res/values-bn-rBD/strings.xml @@ -0,0 +1,23 @@ + + + + + "পরবর্তী" + "ফিরুন" + "আরো" + diff --git a/setupwizardlib/src/main/res/values-bs-rBA/strings.xml b/setupwizardlib/src/main/res/values-bs-rBA/strings.xml new file mode 100644 index 0000000..571828a --- /dev/null +++ b/setupwizardlib/src/main/res/values-bs-rBA/strings.xml @@ -0,0 +1,23 @@ + + + + + "Naprijed" + "Nazad" + "Još" + diff --git a/setupwizardlib/src/main/res/values-ca/strings.xml b/setupwizardlib/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000..32c7241 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ca/strings.xml @@ -0,0 +1,23 @@ + + + + + "Següent" + "Enrere" + "Més" + diff --git a/setupwizardlib/src/main/res/values-cs/strings.xml b/setupwizardlib/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000..2ce9b34 --- /dev/null +++ b/setupwizardlib/src/main/res/values-cs/strings.xml @@ -0,0 +1,23 @@ + + + + + "Další" + "Zpět" + "Další" + diff --git a/setupwizardlib/src/main/res/values-da/strings.xml b/setupwizardlib/src/main/res/values-da/strings.xml new file mode 100644 index 0000000..b2a9cd9 --- /dev/null +++ b/setupwizardlib/src/main/res/values-da/strings.xml @@ -0,0 +1,23 @@ + + + + + "Næste" + "Tilbage" + "Mere" + diff --git a/setupwizardlib/src/main/res/values-de/strings.xml b/setupwizardlib/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..86dce12 --- /dev/null +++ b/setupwizardlib/src/main/res/values-de/strings.xml @@ -0,0 +1,23 @@ + + + + + "Weiter" + "Zurück" + "Mehr" + diff --git a/setupwizardlib/src/main/res/values-el/strings.xml b/setupwizardlib/src/main/res/values-el/strings.xml new file mode 100644 index 0000000..4911da3 --- /dev/null +++ b/setupwizardlib/src/main/res/values-el/strings.xml @@ -0,0 +1,23 @@ + + + + + "Επόμενο" + "Πίσω" + "Περισσότερα" + diff --git a/setupwizardlib/src/main/res/values-en-rAU/strings.xml b/setupwizardlib/src/main/res/values-en-rAU/strings.xml new file mode 100644 index 0000000..ce96d65 --- /dev/null +++ b/setupwizardlib/src/main/res/values-en-rAU/strings.xml @@ -0,0 +1,23 @@ + + + + + "Next" + "Back" + "More" + diff --git a/setupwizardlib/src/main/res/values-en-rGB/strings.xml b/setupwizardlib/src/main/res/values-en-rGB/strings.xml new file mode 100644 index 0000000..ce96d65 --- /dev/null +++ b/setupwizardlib/src/main/res/values-en-rGB/strings.xml @@ -0,0 +1,23 @@ + + + + + "Next" + "Back" + "More" + diff --git a/setupwizardlib/src/main/res/values-en-rIN/strings.xml b/setupwizardlib/src/main/res/values-en-rIN/strings.xml new file mode 100644 index 0000000..ce96d65 --- /dev/null +++ b/setupwizardlib/src/main/res/values-en-rIN/strings.xml @@ -0,0 +1,23 @@ + + + + + "Next" + "Back" + "More" + diff --git a/setupwizardlib/src/main/res/values-es-rUS/strings.xml b/setupwizardlib/src/main/res/values-es-rUS/strings.xml new file mode 100644 index 0000000..5410b53 --- /dev/null +++ b/setupwizardlib/src/main/res/values-es-rUS/strings.xml @@ -0,0 +1,23 @@ + + + + + "Siguiente" + "Atrás" + "Más" + diff --git a/setupwizardlib/src/main/res/values-es/strings.xml b/setupwizardlib/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..5410b53 --- /dev/null +++ b/setupwizardlib/src/main/res/values-es/strings.xml @@ -0,0 +1,23 @@ + + + + + "Siguiente" + "Atrás" + "Más" + diff --git a/setupwizardlib/src/main/res/values-et-rEE/strings.xml b/setupwizardlib/src/main/res/values-et-rEE/strings.xml new file mode 100644 index 0000000..56414cd --- /dev/null +++ b/setupwizardlib/src/main/res/values-et-rEE/strings.xml @@ -0,0 +1,23 @@ + + + + + "Edasi" + "Tagasi" + "Rohkem" + diff --git a/setupwizardlib/src/main/res/values-eu-rES/strings.xml b/setupwizardlib/src/main/res/values-eu-rES/strings.xml new file mode 100644 index 0000000..b7a2b88 --- /dev/null +++ b/setupwizardlib/src/main/res/values-eu-rES/strings.xml @@ -0,0 +1,23 @@ + + + + + "Hurrengoa" + "Atzera" + "Gehiago" + diff --git a/setupwizardlib/src/main/res/values-fa/strings.xml b/setupwizardlib/src/main/res/values-fa/strings.xml new file mode 100644 index 0000000..339daaf --- /dev/null +++ b/setupwizardlib/src/main/res/values-fa/strings.xml @@ -0,0 +1,23 @@ + + + + + "بعدی" + "برگشت" + "بیشتر" + diff --git a/setupwizardlib/src/main/res/values-fi/strings.xml b/setupwizardlib/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000..9a12462 --- /dev/null +++ b/setupwizardlib/src/main/res/values-fi/strings.xml @@ -0,0 +1,23 @@ + + + + + "Seuraava" + "Takaisin" + "Lisää" + diff --git a/setupwizardlib/src/main/res/values-fr-rCA/strings.xml b/setupwizardlib/src/main/res/values-fr-rCA/strings.xml new file mode 100644 index 0000000..bab6a0f --- /dev/null +++ b/setupwizardlib/src/main/res/values-fr-rCA/strings.xml @@ -0,0 +1,23 @@ + + + + + "Suivant" + "Précédent" + "Plus" + diff --git a/setupwizardlib/src/main/res/values-fr/strings.xml b/setupwizardlib/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..ec7bc75 --- /dev/null +++ b/setupwizardlib/src/main/res/values-fr/strings.xml @@ -0,0 +1,23 @@ + + + + + "Suivant" + "Retour" + "Plus" + diff --git a/setupwizardlib/src/main/res/values-gl-rES/strings.xml b/setupwizardlib/src/main/res/values-gl-rES/strings.xml new file mode 100644 index 0000000..e188873 --- /dev/null +++ b/setupwizardlib/src/main/res/values-gl-rES/strings.xml @@ -0,0 +1,23 @@ + + + + + "Seguinte" + "Atrás" + "Máis" + diff --git a/setupwizardlib/src/main/res/values-gu-rIN/strings.xml b/setupwizardlib/src/main/res/values-gu-rIN/strings.xml new file mode 100644 index 0000000..d273881 --- /dev/null +++ b/setupwizardlib/src/main/res/values-gu-rIN/strings.xml @@ -0,0 +1,23 @@ + + + + + "આગલું" + "પાછળ" + "વધુ" + diff --git a/setupwizardlib/src/main/res/values-hi/strings.xml b/setupwizardlib/src/main/res/values-hi/strings.xml new file mode 100644 index 0000000..e55df0f --- /dev/null +++ b/setupwizardlib/src/main/res/values-hi/strings.xml @@ -0,0 +1,23 @@ + + + + + "आगे" + "पीछे" + "अधिक" + diff --git a/setupwizardlib/src/main/res/values-hr/strings.xml b/setupwizardlib/src/main/res/values-hr/strings.xml new file mode 100644 index 0000000..faacd43 --- /dev/null +++ b/setupwizardlib/src/main/res/values-hr/strings.xml @@ -0,0 +1,23 @@ + + + + + "Dalje" + "Natrag" + "Više" + diff --git a/setupwizardlib/src/main/res/values-hu/strings.xml b/setupwizardlib/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000..e50f1dc --- /dev/null +++ b/setupwizardlib/src/main/res/values-hu/strings.xml @@ -0,0 +1,23 @@ + + + + + "Következő" + "Vissza" + "Továbbiak" + diff --git a/setupwizardlib/src/main/res/values-hy-rAM/strings.xml b/setupwizardlib/src/main/res/values-hy-rAM/strings.xml new file mode 100644 index 0000000..7236789 --- /dev/null +++ b/setupwizardlib/src/main/res/values-hy-rAM/strings.xml @@ -0,0 +1,23 @@ + + + + + "Առաջ" + "Հետ" + "Ավելին" + diff --git a/setupwizardlib/src/main/res/values-in/strings.xml b/setupwizardlib/src/main/res/values-in/strings.xml new file mode 100644 index 0000000..708addf --- /dev/null +++ b/setupwizardlib/src/main/res/values-in/strings.xml @@ -0,0 +1,23 @@ + + + + + "Berikutnya" + "Kembali" + "Lainnya" + diff --git a/setupwizardlib/src/main/res/values-is-rIS/strings.xml b/setupwizardlib/src/main/res/values-is-rIS/strings.xml new file mode 100644 index 0000000..a509880 --- /dev/null +++ b/setupwizardlib/src/main/res/values-is-rIS/strings.xml @@ -0,0 +1,23 @@ + + + + + "Áfram" + "Til baka" + "Meira" + diff --git a/setupwizardlib/src/main/res/values-it/strings.xml b/setupwizardlib/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..5995eda --- /dev/null +++ b/setupwizardlib/src/main/res/values-it/strings.xml @@ -0,0 +1,23 @@ + + + + + "Avanti" + "Indietro" + "Altro" + diff --git a/setupwizardlib/src/main/res/values-iw/strings.xml b/setupwizardlib/src/main/res/values-iw/strings.xml new file mode 100644 index 0000000..cb3769d --- /dev/null +++ b/setupwizardlib/src/main/res/values-iw/strings.xml @@ -0,0 +1,23 @@ + + + + + "הבא" + "חזרה" + "עוד" + diff --git a/setupwizardlib/src/main/res/values-ja/strings.xml b/setupwizardlib/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..263b0f1 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ja/strings.xml @@ -0,0 +1,23 @@ + + + + + "次へ" + "戻る" + "もっと見る" + diff --git a/setupwizardlib/src/main/res/values-ka-rGE/strings.xml b/setupwizardlib/src/main/res/values-ka-rGE/strings.xml new file mode 100644 index 0000000..55dfabb --- /dev/null +++ b/setupwizardlib/src/main/res/values-ka-rGE/strings.xml @@ -0,0 +1,23 @@ + + + + + "შემდეგი" + "უკან" + "დამატებით" + diff --git a/setupwizardlib/src/main/res/values-kk-rKZ/strings.xml b/setupwizardlib/src/main/res/values-kk-rKZ/strings.xml new file mode 100644 index 0000000..c1cbf2c --- /dev/null +++ b/setupwizardlib/src/main/res/values-kk-rKZ/strings.xml @@ -0,0 +1,23 @@ + + + + + "Келесі" + "Артқа" + "Тағы" + diff --git a/setupwizardlib/src/main/res/values-km-rKH/strings.xml b/setupwizardlib/src/main/res/values-km-rKH/strings.xml new file mode 100644 index 0000000..b4f7a9f --- /dev/null +++ b/setupwizardlib/src/main/res/values-km-rKH/strings.xml @@ -0,0 +1,23 @@ + + + + + "បន្ទាប់" + "ថយក្រោយ" + "ច្រើនទៀត" + diff --git a/setupwizardlib/src/main/res/values-kn-rIN/strings.xml b/setupwizardlib/src/main/res/values-kn-rIN/strings.xml new file mode 100644 index 0000000..38dc0ad --- /dev/null +++ b/setupwizardlib/src/main/res/values-kn-rIN/strings.xml @@ -0,0 +1,23 @@ + + + + + "ಮುಂದೆ" + "ಹಿಂದೆ" + "ಇನ್ನಷ್ಟು" + diff --git a/setupwizardlib/src/main/res/values-ko/strings.xml b/setupwizardlib/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..536fd84 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ko/strings.xml @@ -0,0 +1,23 @@ + + + + + "다음" + "뒤로" + "더보기" + diff --git a/setupwizardlib/src/main/res/values-ky-rKG/strings.xml b/setupwizardlib/src/main/res/values-ky-rKG/strings.xml new file mode 100644 index 0000000..ea133c4 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ky-rKG/strings.xml @@ -0,0 +1,23 @@ + + + + + "Кийинки" + "Артка" + "Дагы" + diff --git a/setupwizardlib/src/main/res/values-land/dimens.xml b/setupwizardlib/src/main/res/values-land/dimens.xml new file mode 100644 index 0000000..7b4b1c6 --- /dev/null +++ b/setupwizardlib/src/main/res/values-land/dimens.xml @@ -0,0 +1,36 @@ + + + + + + + 32dp + 56dp + 24dp + 0dp + + + 0dp + + + 672dp + 560dp + + + 0 + + diff --git a/setupwizardlib/src/main/res/values-land/layouts.xml b/setupwizardlib/src/main/res/values-land/layouts.xml new file mode 100644 index 0000000..7d75af0 --- /dev/null +++ b/setupwizardlib/src/main/res/values-land/layouts.xml @@ -0,0 +1,28 @@ + + + + + + @layout/suw_template_header_collapsed + @layout/suw_template_header_collapsed + @layout/suw_list_template_header_collapsed + @layout/suw_list_template_header_collapsed + @layout/suw_no_scroll_template_header_collapsed + @layout/suw_no_scroll_template_header_collapsed + + + diff --git a/setupwizardlib/src/main/res/values-land/styles.xml b/setupwizardlib/src/main/res/values-land/styles.xml new file mode 100644 index 0000000..536a1dc --- /dev/null +++ b/setupwizardlib/src/main/res/values-land/styles.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/setupwizardlib/src/main/res/values-lo-rLA/strings.xml b/setupwizardlib/src/main/res/values-lo-rLA/strings.xml new file mode 100644 index 0000000..b26e410 --- /dev/null +++ b/setupwizardlib/src/main/res/values-lo-rLA/strings.xml @@ -0,0 +1,23 @@ + + + + + "ຕໍ່​ໄປ" + "ກັບຄືນ" + "ເພີ່ມເຕີມ" + diff --git a/setupwizardlib/src/main/res/values-lt/strings.xml b/setupwizardlib/src/main/res/values-lt/strings.xml new file mode 100644 index 0000000..fb8343f --- /dev/null +++ b/setupwizardlib/src/main/res/values-lt/strings.xml @@ -0,0 +1,23 @@ + + + + + "Kitas" + "Atgal" + "Daugiau" + diff --git a/setupwizardlib/src/main/res/values-lv/strings.xml b/setupwizardlib/src/main/res/values-lv/strings.xml new file mode 100644 index 0000000..5ba37e1 --- /dev/null +++ b/setupwizardlib/src/main/res/values-lv/strings.xml @@ -0,0 +1,23 @@ + + + + + "Tālāk" + "Atpakaļ" + "Vairāk" + diff --git a/setupwizardlib/src/main/res/values-mk-rMK/strings.xml b/setupwizardlib/src/main/res/values-mk-rMK/strings.xml new file mode 100644 index 0000000..0cb6856 --- /dev/null +++ b/setupwizardlib/src/main/res/values-mk-rMK/strings.xml @@ -0,0 +1,23 @@ + + + + + "Следно" + "Назад" + "Повеќе" + diff --git a/setupwizardlib/src/main/res/values-ml-rIN/strings.xml b/setupwizardlib/src/main/res/values-ml-rIN/strings.xml new file mode 100644 index 0000000..29a6e02 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ml-rIN/strings.xml @@ -0,0 +1,23 @@ + + + + + "അടുത്തത്" + "മടങ്ങുക" + "കൂടുതൽ" + diff --git a/setupwizardlib/src/main/res/values-mn-rMN/strings.xml b/setupwizardlib/src/main/res/values-mn-rMN/strings.xml new file mode 100644 index 0000000..7420ea4 --- /dev/null +++ b/setupwizardlib/src/main/res/values-mn-rMN/strings.xml @@ -0,0 +1,23 @@ + + + + + "Дараах" + "Буцах" + "Илүү" + diff --git a/setupwizardlib/src/main/res/values-mr-rIN/strings.xml b/setupwizardlib/src/main/res/values-mr-rIN/strings.xml new file mode 100644 index 0000000..d2d784b --- /dev/null +++ b/setupwizardlib/src/main/res/values-mr-rIN/strings.xml @@ -0,0 +1,23 @@ + + + + + "पुढील" + "परत" + "अधिक" + diff --git a/setupwizardlib/src/main/res/values-ms-rMY/strings.xml b/setupwizardlib/src/main/res/values-ms-rMY/strings.xml new file mode 100644 index 0000000..5bb8351 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ms-rMY/strings.xml @@ -0,0 +1,23 @@ + + + + + "Seterusnya" + "Kembali" + "Lagi" + diff --git a/setupwizardlib/src/main/res/values-my-rMM/strings.xml b/setupwizardlib/src/main/res/values-my-rMM/strings.xml new file mode 100644 index 0000000..0451227 --- /dev/null +++ b/setupwizardlib/src/main/res/values-my-rMM/strings.xml @@ -0,0 +1,23 @@ + + + + + "ရှေ့သို့" + "နောက်သို့" + "နောက်ထပ်" + diff --git a/setupwizardlib/src/main/res/values-nb/strings.xml b/setupwizardlib/src/main/res/values-nb/strings.xml new file mode 100644 index 0000000..e755f77 --- /dev/null +++ b/setupwizardlib/src/main/res/values-nb/strings.xml @@ -0,0 +1,23 @@ + + + + + "Neste" + "Tilbake" + "Mer" + diff --git a/setupwizardlib/src/main/res/values-ne-rNP/strings.xml b/setupwizardlib/src/main/res/values-ne-rNP/strings.xml new file mode 100644 index 0000000..214fb82 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ne-rNP/strings.xml @@ -0,0 +1,23 @@ + + + + + "अर्को" + "पछाडि जानुहोस्" + "थप" + diff --git a/setupwizardlib/src/main/res/values-nl/strings.xml b/setupwizardlib/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000..a049c89 --- /dev/null +++ b/setupwizardlib/src/main/res/values-nl/strings.xml @@ -0,0 +1,23 @@ + + + + + "Volgende" + "Terug" + "Meer" + diff --git a/setupwizardlib/src/main/res/values-pa-rIN/strings.xml b/setupwizardlib/src/main/res/values-pa-rIN/strings.xml new file mode 100644 index 0000000..7d4f0d0 --- /dev/null +++ b/setupwizardlib/src/main/res/values-pa-rIN/strings.xml @@ -0,0 +1,23 @@ + + + + + "ਅੱਗੇ" + "ਪਿੱਛੇ" + "ਹੋਰ" + diff --git a/setupwizardlib/src/main/res/values-pl/strings.xml b/setupwizardlib/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000..e717cda --- /dev/null +++ b/setupwizardlib/src/main/res/values-pl/strings.xml @@ -0,0 +1,23 @@ + + + + + "Dalej" + "Wstecz" + "Więcej" + diff --git a/setupwizardlib/src/main/res/values-pt-rBR/strings.xml b/setupwizardlib/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..a405ebc --- /dev/null +++ b/setupwizardlib/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,23 @@ + + + + + "Próximo" + "Voltar" + "Mais" + diff --git a/setupwizardlib/src/main/res/values-pt-rPT/strings.xml b/setupwizardlib/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000..29c1978 --- /dev/null +++ b/setupwizardlib/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1,23 @@ + + + + + "Seguinte" + "Anterior" + "Mais" + diff --git a/setupwizardlib/src/main/res/values-pt/strings.xml b/setupwizardlib/src/main/res/values-pt/strings.xml new file mode 100644 index 0000000..a405ebc --- /dev/null +++ b/setupwizardlib/src/main/res/values-pt/strings.xml @@ -0,0 +1,23 @@ + + + + + "Próximo" + "Voltar" + "Mais" + diff --git a/setupwizardlib/src/main/res/values-ro/strings.xml b/setupwizardlib/src/main/res/values-ro/strings.xml new file mode 100644 index 0000000..98d56c3 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ro/strings.xml @@ -0,0 +1,23 @@ + + + + + "Înainte" + "Înapoi" + "Mai mult" + diff --git a/setupwizardlib/src/main/res/values-ru/strings.xml b/setupwizardlib/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..3e8f832 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ru/strings.xml @@ -0,0 +1,23 @@ + + + + + "Далее" + "Назад" + "Ещё" + diff --git a/setupwizardlib/src/main/res/values-si-rLK/strings.xml b/setupwizardlib/src/main/res/values-si-rLK/strings.xml new file mode 100644 index 0000000..2e9ae7a --- /dev/null +++ b/setupwizardlib/src/main/res/values-si-rLK/strings.xml @@ -0,0 +1,23 @@ + + + + + "ඊළඟ" + "ආපසු" + "තවත්" + diff --git a/setupwizardlib/src/main/res/values-sk/strings.xml b/setupwizardlib/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000..5ff5052 --- /dev/null +++ b/setupwizardlib/src/main/res/values-sk/strings.xml @@ -0,0 +1,23 @@ + + + + + "Ďalej" + "Späť" + "Viac" + diff --git a/setupwizardlib/src/main/res/values-sl/strings.xml b/setupwizardlib/src/main/res/values-sl/strings.xml new file mode 100644 index 0000000..c2af32f --- /dev/null +++ b/setupwizardlib/src/main/res/values-sl/strings.xml @@ -0,0 +1,23 @@ + + + + + "Naprej" + "Nazaj" + "Več" + diff --git a/setupwizardlib/src/main/res/values-sq-rAL/strings.xml b/setupwizardlib/src/main/res/values-sq-rAL/strings.xml new file mode 100644 index 0000000..20fdff1 --- /dev/null +++ b/setupwizardlib/src/main/res/values-sq-rAL/strings.xml @@ -0,0 +1,23 @@ + + + + + "Përpara" + "Prapa" + "Më shumë" + diff --git a/setupwizardlib/src/main/res/values-sr/strings.xml b/setupwizardlib/src/main/res/values-sr/strings.xml new file mode 100644 index 0000000..971b138 --- /dev/null +++ b/setupwizardlib/src/main/res/values-sr/strings.xml @@ -0,0 +1,23 @@ + + + + + "Даље" + "Назад" + "Још" + diff --git a/setupwizardlib/src/main/res/values-sv/strings.xml b/setupwizardlib/src/main/res/values-sv/strings.xml new file mode 100644 index 0000000..f5083db --- /dev/null +++ b/setupwizardlib/src/main/res/values-sv/strings.xml @@ -0,0 +1,23 @@ + + + + + "Nästa" + "Tillbaka" + "Mer" + diff --git a/setupwizardlib/src/main/res/values-sw/strings.xml b/setupwizardlib/src/main/res/values-sw/strings.xml new file mode 100644 index 0000000..e08f0fe --- /dev/null +++ b/setupwizardlib/src/main/res/values-sw/strings.xml @@ -0,0 +1,23 @@ + + + + + "Endelea" + "Rudi nyuma" + "Zaidi" + diff --git a/setupwizardlib/src/main/res/values-sw360dp/dimens.xml b/setupwizardlib/src/main/res/values-sw360dp/dimens.xml new file mode 100644 index 0000000..9ed9a82 --- /dev/null +++ b/setupwizardlib/src/main/res/values-sw360dp/dimens.xml @@ -0,0 +1,20 @@ + + + + + 16sp + diff --git a/setupwizardlib/src/main/res/values-sw600dp-land/dimens.xml b/setupwizardlib/src/main/res/values-sw600dp-land/dimens.xml new file mode 100644 index 0000000..57b7e3e --- /dev/null +++ b/setupwizardlib/src/main/res/values-sw600dp-land/dimens.xml @@ -0,0 +1,26 @@ + + + + + + + 128dp + + + 0.0 + + diff --git a/setupwizardlib/src/main/res/values-sw600dp-land/layouts.xml b/setupwizardlib/src/main/res/values-sw600dp-land/layouts.xml new file mode 100644 index 0000000..6fcb97c --- /dev/null +++ b/setupwizardlib/src/main/res/values-sw600dp-land/layouts.xml @@ -0,0 +1,28 @@ + + + + + + @layout/suw_template_card_wide + @layout/suw_template_card_wide + @layout/suw_list_template_card_wide + @layout/suw_list_template_card_wide + @layout/suw_no_scroll_template_card_wide + @layout/suw_no_scroll_template_card_wide + + + diff --git a/setupwizardlib/src/main/res/values-sw600dp/config.xml b/setupwizardlib/src/main/res/values-sw600dp/config.xml new file mode 100644 index 0000000..0cbd5a8 --- /dev/null +++ b/setupwizardlib/src/main/res/values-sw600dp/config.xml @@ -0,0 +1,23 @@ + + + + + + + true + + diff --git a/setupwizardlib/src/main/res/values-sw600dp/dimens.xml b/setupwizardlib/src/main/res/values-sw600dp/dimens.xml new file mode 100644 index 0000000..98c3dd7 --- /dev/null +++ b/setupwizardlib/src/main/res/values-sw600dp/dimens.xml @@ -0,0 +1,28 @@ + + + + + + + 256dp + + + 0 + 34sp + 40dp + + diff --git a/setupwizardlib/src/main/res/values-sw600dp/layouts.xml b/setupwizardlib/src/main/res/values-sw600dp/layouts.xml new file mode 100644 index 0000000..9db88c7 --- /dev/null +++ b/setupwizardlib/src/main/res/values-sw600dp/layouts.xml @@ -0,0 +1,32 @@ + + + + + + @layout/suw_template_card + @layout/suw_template_card + @layout/suw_list_template_card + @layout/suw_list_template_card + @layout/suw_no_scroll_template_card + @layout/suw_no_scroll_template_card + + @layout/suw_glif_template_card + @layout/suw_glif_list_template_card + @layout/suw_glif_blank_template_card + + + diff --git a/setupwizardlib/src/main/res/values-ta-rIN/strings.xml b/setupwizardlib/src/main/res/values-ta-rIN/strings.xml new file mode 100644 index 0000000..9bd2113 --- /dev/null +++ b/setupwizardlib/src/main/res/values-ta-rIN/strings.xml @@ -0,0 +1,23 @@ + + + + + "அடுத்து" + "முந்தையது" + "மேலும்" + diff --git a/setupwizardlib/src/main/res/values-te-rIN/strings.xml b/setupwizardlib/src/main/res/values-te-rIN/strings.xml new file mode 100644 index 0000000..440a2fa --- /dev/null +++ b/setupwizardlib/src/main/res/values-te-rIN/strings.xml @@ -0,0 +1,23 @@ + + + + + "తదుపరి" + "వెనుకకు" + "మరింత" + diff --git a/setupwizardlib/src/main/res/values-th/strings.xml b/setupwizardlib/src/main/res/values-th/strings.xml new file mode 100644 index 0000000..c316e74 --- /dev/null +++ b/setupwizardlib/src/main/res/values-th/strings.xml @@ -0,0 +1,23 @@ + + + + + "ถัดไป" + "กลับ" + "เพิ่มเติม" + diff --git a/setupwizardlib/src/main/res/values-tl/strings.xml b/setupwizardlib/src/main/res/values-tl/strings.xml new file mode 100644 index 0000000..5d23fd9 --- /dev/null +++ b/setupwizardlib/src/main/res/values-tl/strings.xml @@ -0,0 +1,23 @@ + + + + + "Susunod" + "Bumalik" + "Higit pa" + diff --git a/setupwizardlib/src/main/res/values-tr/strings.xml b/setupwizardlib/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000..0840982 --- /dev/null +++ b/setupwizardlib/src/main/res/values-tr/strings.xml @@ -0,0 +1,23 @@ + + + + + "İleri" + "Geri" + "Diğer" + diff --git a/setupwizardlib/src/main/res/values-uk/strings.xml b/setupwizardlib/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000..2c87ee0 --- /dev/null +++ b/setupwizardlib/src/main/res/values-uk/strings.xml @@ -0,0 +1,23 @@ + + + + + "Далі" + "Назад" + "Більше" + diff --git a/setupwizardlib/src/main/res/values-ur-rPK/strings.xml b/setupwizardlib/src/main/res/values-ur-rPK/strings.xml new file mode 100644 index 0000000..740598c --- /dev/null +++ b/setupwizardlib/src/main/res/values-ur-rPK/strings.xml @@ -0,0 +1,23 @@ + + + + + "آگے" + "پیچھے" + "مزید" + diff --git a/setupwizardlib/src/main/res/values-uz-rUZ/strings.xml b/setupwizardlib/src/main/res/values-uz-rUZ/strings.xml new file mode 100644 index 0000000..9ff351a --- /dev/null +++ b/setupwizardlib/src/main/res/values-uz-rUZ/strings.xml @@ -0,0 +1,23 @@ + + + + + "Keyingisi" + "Orqaga" + "Yana" + diff --git a/setupwizardlib/src/main/res/values-v21/styles.xml b/setupwizardlib/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000..88c39d4 --- /dev/null +++ b/setupwizardlib/src/main/res/values-v21/styles.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/values-v21/themes.xml b/setupwizardlib/src/main/res/values-v21/themes.xml new file mode 100644 index 0000000..6ecab7c --- /dev/null +++ b/setupwizardlib/src/main/res/values-v21/themes.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setupwizardlib/src/main/res/values-vi/strings.xml b/setupwizardlib/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000..180d292 --- /dev/null +++ b/setupwizardlib/src/main/res/values-vi/strings.xml @@ -0,0 +1,23 @@ + + + + + "Tiếp theo" + "Quay lại" + "Khác" + diff --git a/setupwizardlib/src/main/res/values-zh-rCN/strings.xml b/setupwizardlib/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..5a8d8a1 --- /dev/null +++ b/setupwizardlib/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,23 @@ + + + + + "下一步" + "返回" + "更多" + diff --git a/setupwizardlib/src/main/res/values-zh-rHK/strings.xml b/setupwizardlib/src/main/res/values-zh-rHK/strings.xml new file mode 100644 index 0000000..5a8d8a1 --- /dev/null +++ b/setupwizardlib/src/main/res/values-zh-rHK/strings.xml @@ -0,0 +1,23 @@ + + + + + "下一步" + "返回" + "更多" + diff --git a/setupwizardlib/src/main/res/values-zh-rTW/strings.xml b/setupwizardlib/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..f61cc20 --- /dev/null +++ b/setupwizardlib/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,23 @@ + + + + + "繼續" + "返回" + "更多" + diff --git a/setupwizardlib/src/main/res/values-zu/strings.xml b/setupwizardlib/src/main/res/values-zu/strings.xml new file mode 100644 index 0000000..52ecbda --- /dev/null +++ b/setupwizardlib/src/main/res/values-zu/strings.xml @@ -0,0 +1,23 @@ + + + + + "Okulandelayo" + "Emuva" + "Okuningi" + diff --git a/setupwizardlib/src/main/res/values/attrs.xml b/setupwizardlib/src/main/res/values/attrs.xml new file mode 100644 index 0000000..4bdc05a --- /dev/null +++ b/setupwizardlib/src/main/res/values/attrs.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/values/colors.xml b/setupwizardlib/src/main/res/values/colors.xml new file mode 100644 index 0000000..e6ee76b --- /dev/null +++ b/setupwizardlib/src/main/res/values/colors.xml @@ -0,0 +1,40 @@ + + + + + + + + #ff448aff + #ff3367d6 + #ff448aff + #ff3367d6 + #b3ffffff + #89000000 + #ffffcd40 + #fff4b400 + + + + #ff21272b + #ffe4e7e9 + + + #ff4285f4 + #ff4285f4 + + diff --git a/setupwizardlib/src/main/res/values/config.xml b/setupwizardlib/src/main/res/values/config.xml new file mode 100644 index 0000000..74e30d3 --- /dev/null +++ b/setupwizardlib/src/main/res/values/config.xml @@ -0,0 +1,26 @@ + + + + + + + 300 + + + false + + diff --git a/setupwizardlib/src/main/res/values/dimens.xml b/setupwizardlib/src/main/res/values/dimens.xml new file mode 100644 index 0000000..4a6266e --- /dev/null +++ b/setupwizardlib/src/main/res/values/dimens.xml @@ -0,0 +1,122 @@ + + + + + + + 40dp + 24dp + 48dp + + + 4sp + 12dp + 1dp + 24dp + 15dp + 16sp + + 0dp + 16dp + + 24dp + 12dp + 24dp + 4sp + 16sp + + 3dp + 24dp + + + -6dp + 0dp + 12dp + 18dp + 5sp + + + 2dp + 5dp + 128dp + 56dp + + 0dp + 0dp + 16dp + 28dp + + 256dp + + + 0dp + + + 5dp + 560dp + 672dp + + + 3dp + + 1dp + + 24sp + 16dp + 16dp + 2dp + + 3.67sp + + 15dp + 2dp + + 32dp + + + 2.22 + + + 48dp + 15dp + 20dp + + 88dp + 40dp + + 72dp + 24dp + + + 1dp + 0dp + 56dp + + + 6dp + 10dp + 56dp + 24dp + 24dp + 16sp + + + + -7dp + 7dp + + diff --git a/setupwizardlib/src/main/res/values/layouts.xml b/setupwizardlib/src/main/res/values/layouts.xml new file mode 100644 index 0000000..7a5d435 --- /dev/null +++ b/setupwizardlib/src/main/res/values/layouts.xml @@ -0,0 +1,32 @@ + + + + + + @layout/suw_template_header + @layout/suw_template_header_collapsed + @layout/suw_list_template_header + @layout/suw_list_template_header_collapsed + @layout/suw_no_scroll_template_header + @layout/suw_no_scroll_template_header_collapsed + + @layout/suw_glif_template_compact + @layout/suw_glif_list_template_compact + @layout/suw_glif_blank_template_compact + + + diff --git a/setupwizardlib/src/main/res/values/strings.xml b/setupwizardlib/src/main/res/values/strings.xml new file mode 100644 index 0000000..9436690 --- /dev/null +++ b/setupwizardlib/src/main/res/values/strings.xml @@ -0,0 +1,27 @@ + + + + + + Next + + + Back + + + More + diff --git a/setupwizardlib/src/main/res/values/styles.xml b/setupwizardlib/src/main/res/values/styles.xml new file mode 100644 index 0000000..f2bae05 --- /dev/null +++ b/setupwizardlib/src/main/res/values/styles.xml @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setupwizardlib/src/main/res/values/themes.xml b/setupwizardlib/src/main/res/values/themes.xml new file mode 100644 index 0000000..119086c --- /dev/null +++ b/setupwizardlib/src/main/res/values/themes.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3