diff options
author | Xin Li <delphij@google.com> | 2019-07-01 20:59:51 +0000 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2019-07-01 20:59:51 +0000 |
commit | aa06a98b9387bf7cda7948de3f3949e82c151856 (patch) | |
tree | d4d0f1ccfa3d6b82ef0a0be0a353328a3001714d | |
parent | f26a9e8e2e422b20c721e199961f11bc82dd250a (diff) | |
parent | 93dacd74efc365bc889773a7cca03adad1ded25d (diff) | |
download | setupwizard-aa06a98b9387bf7cda7948de3f3949e82c151856.tar.gz |
DO NOT MERGE - Merge qt-dev-plus-aosp-without-vendor (5699924) into stage-aosp-mastertemp_140451723
Bug: 134405016
Change-Id: I5e74b54945db4e896b45312ec51d2a4facc01c55
260 files changed, 14767 insertions, 15072 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index b45eaff..e6fe0c4 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,7 +1,4 @@ [Hook Scripts] -checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py - --sha ${PREUPLOAD_COMMIT} - --config_xml tools/checkstyle/checkstyle.xml [Builtin Hooks] commit_msg_test_field = true diff --git a/library/LICENSE b/library/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/library/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.
\ No newline at end of file diff --git a/navigationbar/res/drawable/setup_wizard_navbar_ic_next.xml b/library/gingerbread/AndroidManifest.xml index 1b40776..bf7d42f 100644 --- a/navigationbar/res/drawable/setup_wizard_navbar_ic_next.xml +++ b/library/gingerbread/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + 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. @@ -14,15 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:autoMirrored="true" - android:width="@dimen/setup_wizard_navbar_ic_intrinsic_size" - android:height="@dimen/setup_wizard_navbar_ic_intrinsic_size" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="?attr/setup_wizard_navbar_text_color" - android:pathData="M10,6 l-1.4,1.4 4.6,4.6 -4.6,4.6 1.4,1.4 6,-6z" /> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.setupwizardlib"> -</vector> + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="28" /> + +</manifest> diff --git a/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_down_arrow.png b/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_down_arrow.png Binary files differindex 94016f4..a7084c5 100644 --- a/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_down_arrow.png +++ b/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_down_arrow.png diff --git a/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_down_arrow.png b/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_down_arrow.png Binary files differindex 17811ae..ed3c3b0 100644 --- a/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_down_arrow.png +++ b/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_down_arrow.png diff --git a/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_left_arrow.png b/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_left_arrow.png Binary files differindex 97fed92..be42712 100644 --- a/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_left_arrow.png +++ b/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_left_arrow.png diff --git a/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_right_arrow.png b/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_right_arrow.png Binary files differindex f874955..d7bc4e3 100644 --- a/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_right_arrow.png +++ b/library/gingerbread/res/drawable-xxhdpi/suw_navbar_ic_right_arrow.png diff --git a/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_down_arrow.png b/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_down_arrow.png Binary files differindex cb6a422..dcc1f3c 100644 --- a/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_down_arrow.png +++ b/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_down_arrow.png diff --git a/library/gingerbread/res/layout/suw_items_switch.xml b/library/gingerbread/res/layout/suw_items_switch.xml index 5296a62..5614044 100644 --- a/library/gingerbread/res/layout/suw_items_switch.xml +++ b/library/gingerbread/res/layout/suw_items_switch.xml @@ -17,17 +17,17 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - style="@style/SuwItemContainer.Verbose" + style="@style/SuwItemContainer" android:layout_width="match_parent" android:layout_height="wrap_content" - android:clipToPadding="false" + android:baselineAligned="false" android:orientation="horizontal"> <FrameLayout android:id="@+id/suw_items_icon_container" android:layout_width="@dimen/suw_items_icon_container_width" android:layout_height="wrap_content" - android:layout_gravity="top" + android:layout_gravity="center_vertical" android:gravity="start"> <ImageView @@ -41,13 +41,13 @@ <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/suw_items_verbose_padding_bottom_extra" + android:layout_marginBottom="@dimen/suw_items_padding_bottom_extra" android:layout_weight="1" android:orientation="vertical"> <com.android.setupwizardlib.view.RichTextView android:id="@+id/suw_items_title" - style="@style/SuwItemTitle.Verbose" + style="@style/SuwItemTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="start" diff --git a/library/gingerbread/res/layout/suw_items_switch_verbose.xml b/library/gingerbread/res/layout/suw_items_switch_verbose.xml new file mode 100644 index 0000000..8911acc --- /dev/null +++ b/library/gingerbread/res/layout/suw_items_switch_verbose.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + style="@style/SuwItemContainer.Verbose" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:baselineAligned="false" + android:clipToPadding="false" + android:orientation="horizontal" + tools:ignore="UnusedResources"> + <!-- Ignore UnusedResources: can be used by clients --> + + <FrameLayout + android:id="@+id/suw_items_icon_container" + android:layout_width="@dimen/suw_items_icon_container_width" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:gravity="start"> + + <ImageView + android:id="@+id/suw_items_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:ignore="ContentDescription" /> + + </FrameLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/suw_items_verbose_padding_bottom_extra" + android:layout_weight="1" + android:orientation="vertical"> + + <com.android.setupwizardlib.view.RichTextView + android:id="@+id/suw_items_title" + style="@style/SuwItemTitle.Verbose" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + android:labelFor="@+id/suw_items_switch" + android:textAlignment="viewStart" + tools:ignore="UnusedAttribute" /> + + <com.android.setupwizardlib.view.RichTextView + android:id="@+id/suw_items_summary" + style="@style/SuwItemSummary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + android:textAlignment="viewStart" + android:visibility="gone" + tools:ignore="UnusedAttribute" /> + + </LinearLayout> + + <androidx.appcompat.widget.SwitchCompat + android:id="@+id/suw_items_switch" + style="@style/SuwSwitchStyle" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center_vertical" /> + +</LinearLayout> diff --git a/library/gingerbread/res/values-v21/styles.xml b/library/gingerbread/res/values-v21/styles.xml new file mode 100644 index 0000000..3c0c254 --- /dev/null +++ b/library/gingerbread/res/values-v21/styles.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 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. +--> + +<resources xmlns:tools="http://schemas.android.com/tools"> + + <!-- Button styles --> + + <style name="SuwGlifButton.Tertiary" parent="SuwGlifButton.BaseTertiary"> + <item name="android:fontFamily">sans-serif-medium</item> + <item name="textAllCaps" tools:targetApi="ice_cream_sandwich">false</item> + </style> + +</resources> diff --git a/library/gingerbread/res/values/styles.xml b/library/gingerbread/res/values/styles.xml index 241f037..b008e1e 100644 --- a/library/gingerbread/res/values/styles.xml +++ b/library/gingerbread/res/values/styles.xml @@ -41,6 +41,7 @@ <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> <item name="suwButtonAllCaps">true</item> <item name="suwButtonFontFamily">sans-serif</item> + <item name="suwButtonHighlightAlpha">0.24</item> <item name="suwCardBackground">@drawable/suw_card_bg_dark</item> <item name="suwDividerInsetEnd">0dp</item> <item name="suwDividerInsetStart">@dimen/suw_items_icon_divider_inset</item> @@ -75,6 +76,7 @@ <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> <item name="suwButtonAllCaps">true</item> <item name="suwButtonFontFamily">sans-serif</item> + <item name="suwButtonHighlightAlpha">0.24</item> <item name="suwCardBackground">@drawable/suw_card_bg_light</item> <item name="suwDividerInsetEnd">0dp</item> <item name="suwDividerInsetStart">@dimen/suw_items_icon_divider_inset</item> @@ -109,7 +111,8 @@ <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> <item name="suwButtonAllCaps">true</item> <item name="suwButtonCornerRadius">@dimen/suw_glif_button_corner_radius</item> - <item name="suwButtonFontFamily">sans-serif</item> + <item name="suwButtonFontFamily">sans-serif-medium</item> + <item name="suwButtonHighlightAlpha">0.24</item> <item name="suwColorPrimary">?attr/colorPrimary</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> @@ -148,7 +151,8 @@ <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> <item name="suwButtonAllCaps">true</item> <item name="suwButtonCornerRadius">@dimen/suw_glif_button_corner_radius</item> - <item name="suwButtonFontFamily">sans-serif</item> + <item name="suwButtonFontFamily">sans-serif-medium</item> + <item name="suwButtonHighlightAlpha">0.12</item> <item name="suwColorPrimary">?attr/colorPrimary</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> @@ -262,6 +266,13 @@ <item name="colorControlHighlight">@color/suw_flat_button_highlight</item> </style> + <!-- Ignore UnusedResources: used by clients --> + <style name="SuwGlifButton.Tertiary" + parent="SuwGlifButton.BaseTertiary" + tools:ignore="UnusedResources"> + <item name="textAllCaps" tools:targetApi="ice_cream_sandwich">false</item> + </style> + <!-- Card layout (for tablets) --> <style name="TextAppearance.SuwCardTitle" parent="@style/TextAppearance.AppCompat.Display1"> @@ -279,6 +290,10 @@ <style name="SuwFourColorIndeterminateProgressBar" parent="SuwBase.ProgressBarLarge"> <item name="android:layout_gravity">center</item> <item name="android:indeterminate">true</item> + <item name="android:paddingEnd" tools:targetApi="17" >@dimen/suw_glif_progress_bar_padding</item> + <item name="android:paddingLeft">@dimen/suw_glif_progress_bar_padding</item> + <item name="android:paddingRight">@dimen/suw_glif_progress_bar_padding</item> + <item name="android:paddingStart" tools:targetApi="17" >@dimen/suw_glif_progress_bar_padding</item> </style> <!-- Navigation bar styles --> diff --git a/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java b/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java index 71d1bb6..4b9347f 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java +++ b/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java @@ -28,7 +28,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.view.CheckableLinearLayout; @@ -37,140 +36,130 @@ import com.android.setupwizardlib.view.CheckableLinearLayout; * summary, and when that is clicked, will expand to show a longer summary. The end (right for LTR) * side is a switch which can be toggled by the user. * - * Note: It is highly recommended to use this item with recycler view rather than list view, because - * list view draws the touch ripple effect on top of the item, rather than letting the item handle - * it. Therefore you might see a double-ripple, one for the expandable area and one for the entire - * list item, when using this in list view. + * <p>Note: It is highly recommended to use this item with recycler view rather than list view, + * because list view draws the touch ripple effect on top of the item, rather than letting the item + * handle it. Therefore you might see a double-ripple, one for the expandable area and one for the + * entire list item, when using this in list view. */ public class ExpandableSwitchItem extends SwitchItem - implements OnCheckedChangeListener, OnClickListener { - - private CharSequence mCollapsedSummary; - private CharSequence mExpandedSummary; - private boolean mIsExpanded = false; - - public ExpandableSwitchItem() { - super(); - } - - public ExpandableSwitchItem(Context context, AttributeSet attrs) { - super(context, attrs); - final TypedArray a = - context.obtainStyledAttributes(attrs, R.styleable.SuwExpandableSwitchItem); - mCollapsedSummary = a.getText(R.styleable.SuwExpandableSwitchItem_suwCollapsedSummary); - mExpandedSummary = a.getText(R.styleable.SuwExpandableSwitchItem_suwExpandedSummary); - a.recycle(); - } - - @Override - protected int getDefaultLayoutResource() { - return R.layout.suw_items_expandable_switch; + implements OnCheckedChangeListener, OnClickListener { + + private CharSequence collapsedSummary; + private CharSequence expandedSummary; + private boolean isExpanded = false; + + public ExpandableSwitchItem() { + super(); + } + + public ExpandableSwitchItem(Context context, AttributeSet attrs) { + super(context, attrs); + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwExpandableSwitchItem); + collapsedSummary = a.getText(R.styleable.SuwExpandableSwitchItem_suwCollapsedSummary); + expandedSummary = a.getText(R.styleable.SuwExpandableSwitchItem_suwExpandedSummary); + a.recycle(); + } + + @Override + protected int getDefaultLayoutResource() { + return R.layout.suw_items_expandable_switch; + } + + @Override + public CharSequence getSummary() { + return isExpanded ? getExpandedSummary() : getCollapsedSummary(); + } + + /** @return True if the item is currently expanded. */ + public boolean isExpanded() { + return isExpanded; + } + + /** Sets whether the item should be expanded. */ + public void setExpanded(boolean expanded) { + if (isExpanded == expanded) { + return; } - - @Override - public CharSequence getSummary() { - return mIsExpanded ? getExpandedSummary() : getCollapsedSummary(); + isExpanded = expanded; + notifyItemChanged(); + } + + /** @return The summary shown when in collapsed state. */ + public CharSequence getCollapsedSummary() { + return collapsedSummary; + } + + /** + * Sets the summary text shown when the item is collapsed. Corresponds to the {@code + * app:suwCollapsedSummary} XML attribute. + */ + public void setCollapsedSummary(CharSequence collapsedSummary) { + this.collapsedSummary = collapsedSummary; + if (!isExpanded()) { + notifyChanged(); } - - /** - * @return True if the item is currently expanded. - */ - public boolean isExpanded() { - return mIsExpanded; + } + + /** @return The summary shown when in expanded state. */ + public CharSequence getExpandedSummary() { + return expandedSummary; + } + + /** + * Sets the summary text shown when the item is expanded. Corresponds to the {@code + * app:suwExpandedSummary} XML attribute. + */ + public void setExpandedSummary(CharSequence expandedSummary) { + this.expandedSummary = expandedSummary; + if (isExpanded()) { + notifyChanged(); } + } - /** - * Sets whether the item should be expanded. - */ - public void setExpanded(boolean expanded) { - if (mIsExpanded == expanded) { - return; - } - mIsExpanded = expanded; - notifyItemChanged(); - } + @Override + public void onBindView(View view) { + // TODO: If it is possible to detect, log a warning if this is being used with ListView. + super.onBindView(view); + View content = view.findViewById(R.id.suw_items_expandable_switch_content); + content.setOnClickListener(this); - /** - * @return The summary shown when in collapsed state. - */ - public CharSequence getCollapsedSummary() { - return mCollapsedSummary; + if (content instanceof CheckableLinearLayout) { + ((CheckableLinearLayout) content).setChecked(isExpanded()); } - /** - * Sets the summary text shown when the item is collapsed. Corresponds to the - * {@code app:suwCollapsedSummary} XML attribute. - */ - public void setCollapsedSummary(CharSequence collapsedSummary) { - mCollapsedSummary = collapsedSummary; - if (!isExpanded()) { - notifyChanged(); + tintCompoundDrawables(view); + + // Expandable switch item has focusability on the expandable layout on the left, and the + // switch on the right, but not the item itself. + view.setFocusable(false); + } + + @Override + public void onClick(View v) { + setExpanded(!isExpanded()); + } + + // Tint the expand arrow with the text color + private void tintCompoundDrawables(View view) { + final TypedArray a = + view.getContext().obtainStyledAttributes(new int[] {android.R.attr.textColorPrimary}); + final ColorStateList tintColor = a.getColorStateList(0); + a.recycle(); + + if (tintColor != null) { + TextView titleView = (TextView) view.findViewById(R.id.suw_items_title); + for (Drawable drawable : titleView.getCompoundDrawables()) { + if (drawable != null) { + drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); } - } - - /** - * @return The summary shown when in expanded state. - */ - public CharSequence getExpandedSummary() { - return mExpandedSummary; - } - - /** - * Sets the summary text shown when the item is expanded. Corresponds to the - * {@code app:suwExpandedSummary} XML attribute. - */ - public void setExpandedSummary(CharSequence expandedSummary) { - mExpandedSummary = expandedSummary; - if (isExpanded()) { - notifyChanged(); - } - } - - @Override - public void onBindView(View view) { - // TODO: If it is possible to detect, log a warning if this is being used with ListView. - super.onBindView(view); - View content = view.findViewById(R.id.suw_items_expandable_switch_content); - content.setOnClickListener(this); - - if (content instanceof CheckableLinearLayout) { - ((CheckableLinearLayout) content).setChecked(isExpanded()); - } - - tintCompoundDrawables(view); - - // Expandable switch item has focusability on the expandable layout on the left, and the - // switch on the right, but not the item itself. - view.setFocusable(false); - } - - @Override - public void onClick(View v) { - setExpanded(!isExpanded()); - } - - // Tint the expand arrow with the text color - private void tintCompoundDrawables(View view) { - final TypedArray a = view.getContext() - .obtainStyledAttributes(new int[] {android.R.attr.textColorPrimary}); - final ColorStateList tintColor = a.getColorStateList(0); - a.recycle(); - - if (tintColor != null) { - TextView titleView = (TextView) view.findViewById(R.id.suw_items_title); - for (Drawable drawable : titleView.getCompoundDrawables()) { - if (drawable != null) { - drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); - } - } - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - for (Drawable drawable : titleView.getCompoundDrawablesRelative()) { - if (drawable != null) { - drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); - } - } - } - + } + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + for (Drawable drawable : titleView.getCompoundDrawablesRelative()) { + if (drawable != null) { + drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); + } } + } } + } } diff --git a/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java b/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java index 8d828ac..56fa742 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java +++ b/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java @@ -18,12 +18,10 @@ package com.android.setupwizardlib.items; import android.content.Context; import android.content.res.TypedArray; +import androidx.appcompat.widget.SwitchCompat; import android.util.AttributeSet; import android.view.View; import android.widget.CompoundButton; - -import androidx.appcompat.widget.SwitchCompat; - import com.android.setupwizardlib.R; /** @@ -34,101 +32,93 @@ import com.android.setupwizardlib.R; */ public class SwitchItem extends Item implements CompoundButton.OnCheckedChangeListener { - /** - * Listener for check state changes of this switch item. - */ - public interface OnCheckedChangeListener { - - /** - * Callback when checked state of a {@link SwitchItem} is changed. - * - * @see #setOnCheckedChangeListener(OnCheckedChangeListener) - */ - void onCheckedChange(SwitchItem item, boolean isChecked); - } - - private boolean mChecked = false; - private OnCheckedChangeListener mListener; + /** Listener for check state changes of this switch item. */ + public interface OnCheckedChangeListener { /** - * Creates a default switch item. - */ - public SwitchItem() { - super(); - } - - /** - * Creates a switch item. This constructor is used for inflation from XML. + * Callback when checked state of a {@link SwitchItem} is changed. * - * @param context The context which this item is inflated in. - * @param attrs The XML attributes defined on the item. + * @see #setOnCheckedChangeListener(OnCheckedChangeListener) */ - public SwitchItem(Context context, AttributeSet attrs) { - super(context, attrs); - final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwSwitchItem); - mChecked = a.getBoolean(R.styleable.SuwSwitchItem_android_checked, false); - a.recycle(); + void onCheckedChange(SwitchItem item, boolean isChecked); + } + + private boolean checked = false; + private OnCheckedChangeListener listener; + + /** Creates a default switch item. */ + public SwitchItem() { + super(); + } + + /** + * Creates a switch item. This constructor is used for inflation from XML. + * + * @param context The context which this item is inflated in. + * @param attrs The XML attributes defined on the item. + */ + public SwitchItem(Context context, AttributeSet attrs) { + super(context, attrs); + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwSwitchItem); + checked = a.getBoolean(R.styleable.SuwSwitchItem_android_checked, false); + a.recycle(); + } + + /** Sets whether this item should be checked. */ + public void setChecked(boolean checked) { + if (this.checked != checked) { + this.checked = checked; + notifyItemChanged(); + if (listener != null) { + listener.onCheckedChange(this, checked); + } } - - /** - * Sets whether this item should be checked. - */ - public void setChecked(boolean checked) { - if (mChecked != checked) { - mChecked = checked; - notifyItemChanged(); - if (mListener != null) { - mListener.onCheckedChange(this, checked); - } - } - } - - /** - * @return True if this switch item is currently checked. - */ - public boolean isChecked() { - return mChecked; - } - - @Override - protected int getDefaultLayoutResource() { - return R.layout.suw_items_switch; - } - - /** - * Toggle the checked state of the switch, without invalidating the entire item. - * - * @param view The root view of this item, typically from the argument of onItemClick. - */ - public void toggle(View view) { - mChecked = !mChecked; - final SwitchCompat switchView = (SwitchCompat) view.findViewById(R.id.suw_items_switch); - switchView.setChecked(mChecked); - } - - @Override - public void onBindView(View view) { - super.onBindView(view); - final SwitchCompat switchView = (SwitchCompat) view.findViewById(R.id.suw_items_switch); - switchView.setOnCheckedChangeListener(null); - switchView.setChecked(mChecked); - switchView.setOnCheckedChangeListener(this); - switchView.setEnabled(isEnabled()); - } - - /** - * Sets a listener to listen for changes in checked state. This listener is invoked in both - * user toggling the switch and calls to {@link #setChecked(boolean)}. - */ - public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { - mListener = listener; - } - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mChecked = isChecked; - if (mListener != null) { - mListener.onCheckedChange(this, isChecked); - } + } + + /** @return True if this switch item is currently checked. */ + public boolean isChecked() { + return checked; + } + + @Override + protected int getDefaultLayoutResource() { + return R.layout.suw_items_switch; + } + + /** + * Toggle the checked state of the switch, without invalidating the entire item. + * + * @param view The root view of this item, typically from the argument of onItemClick. + */ + public void toggle(View view) { + checked = !checked; + final SwitchCompat switchView = (SwitchCompat) view.findViewById(R.id.suw_items_switch); + switchView.setChecked(checked); + } + + @Override + public void onBindView(View view) { + super.onBindView(view); + final SwitchCompat switchView = (SwitchCompat) view.findViewById(R.id.suw_items_switch); + switchView.setOnCheckedChangeListener(null); + switchView.setChecked(checked); + switchView.setOnCheckedChangeListener(this); + switchView.setEnabled(isEnabled()); + } + + /** + * Sets a listener to listen for changes in checked state. This listener is invoked in both user + * toggling the switch and calls to {@link #setChecked(boolean)}. + */ + public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { + this.listener = listener; + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + checked = isChecked; + if (listener != null) { + listener.onCheckedChange(this, isChecked); } + } } diff --git a/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java index 2a378e8..c416a9e 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java +++ b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java @@ -19,6 +19,12 @@ package com.android.setupwizardlib.util; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.core.view.accessibility.AccessibilityNodeProviderCompat; +import androidx.customview.widget.ExploreByTouchHelper; import android.text.Layout; import android.text.Spanned; import android.text.style.ClickableSpan; @@ -28,14 +34,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.core.view.AccessibilityDelegateCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.view.accessibility.AccessibilityNodeProviderCompat; -import androidx.customview.widget.ExploreByTouchHelper; - import java.util.List; /** @@ -46,6 +44,7 @@ import java.util.List; * support for ClickableSpan accessibility. * * <p>Sample usage: + * * <pre> * LinkAccessibilityHelper mAccessibilityHelper; * @@ -68,262 +67,260 @@ import java.util.List; */ public class LinkAccessibilityHelper extends AccessibilityDelegateCompat { - private static final String TAG = "LinkAccessibilityHelper"; - - private final AccessibilityDelegateCompat mDelegate; - - public LinkAccessibilityHelper(TextView view) { - this(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - // Platform support was added in O. This helper will be no-op - ? new AccessibilityDelegateCompat() - // Pre-O, we extend ExploreByTouchHelper to expose a virtual view hierarchy - : new PreOLinkAccessibilityHelper(view)); - } - - @VisibleForTesting - LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) { - mDelegate = delegate; - } - - @Override - public void sendAccessibilityEvent(View host, int eventType) { - mDelegate.sendAccessibilityEvent(host, eventType); + private static final String TAG = "LinkAccessibilityHelper"; + + private final AccessibilityDelegateCompat delegate; + + public LinkAccessibilityHelper(TextView view) { + this( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + // Platform support was added in O. This helper will be no-op + ? new AccessibilityDelegateCompat() + // Pre-O, we extend ExploreByTouchHelper to expose a virtual view hierarchy + : new PreOLinkAccessibilityHelper(view)); + } + + @VisibleForTesting + LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) { + this.delegate = delegate; + } + + @Override + public void sendAccessibilityEvent(View host, int eventType) { + delegate.sendAccessibilityEvent(host, eventType); + } + + @Override + public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { + delegate.sendAccessibilityEventUnchecked(host, event); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + return delegate.dispatchPopulateAccessibilityEvent(host, event); + } + + @Override + public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + delegate.onPopulateAccessibilityEvent(host, event); + } + + @Override + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { + delegate.onInitializeAccessibilityEvent(host, event); + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + delegate.onInitializeAccessibilityNodeInfo(host, info); + } + + @Override + public boolean onRequestSendAccessibilityEvent( + ViewGroup host, View child, AccessibilityEvent event) { + return delegate.onRequestSendAccessibilityEvent(host, child, event); + } + + @Override + public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) { + return delegate.getAccessibilityNodeProvider(host); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + return delegate.performAccessibilityAction(host, action, args); + } + + /** + * Dispatches hover event to the virtual view hierarchy. This method should be called in {@link + * View#dispatchHoverEvent(MotionEvent)}. + * + * @see ExploreByTouchHelper#dispatchHoverEvent(MotionEvent) + */ + public boolean dispatchHoverEvent(MotionEvent event) { + return delegate instanceof ExploreByTouchHelper + && ((ExploreByTouchHelper) delegate).dispatchHoverEvent(event); + } + + @VisibleForTesting + static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper { + + private final Rect tempRect = new Rect(); + private final TextView view; + + PreOLinkAccessibilityHelper(TextView view) { + super(view); + this.view = view; } @Override - public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { - mDelegate.sendAccessibilityEventUnchecked(host, event); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { - return mDelegate.dispatchPopulateAccessibilityEvent(host, event); - } - - @Override - public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { - mDelegate.onPopulateAccessibilityEvent(host, event); + protected int getVirtualViewAt(float x, float y) { + final CharSequence text = view.getText(); + if (text instanceof Spanned) { + final Spanned spannedText = (Spanned) text; + final int offset = getOffsetForPosition(view, x, y); + ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class); + if (linkSpans.length == 1) { + ClickableSpan linkSpan = linkSpans[0]; + return spannedText.getSpanStart(linkSpan); + } + } + return ExploreByTouchHelper.INVALID_ID; } @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - mDelegate.onInitializeAccessibilityEvent(host, event); + protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { + final CharSequence text = view.getText(); + if (text instanceof Spanned) { + final Spanned spannedText = (Spanned) text; + ClickableSpan[] linkSpans = + spannedText.getSpans(0, spannedText.length(), ClickableSpan.class); + for (ClickableSpan span : linkSpans) { + virtualViewIds.add(spannedText.getSpanStart(span)); + } + } } @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - mDelegate.onInitializeAccessibilityNodeInfo(host, info); + protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { + final ClickableSpan span = getSpanForOffset(virtualViewId); + if (span != null) { + event.setContentDescription(getTextForSpan(span)); + } else { + Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); + event.setContentDescription(view.getText()); + } } @Override - public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, - AccessibilityEvent event) { - return mDelegate.onRequestSendAccessibilityEvent(host, child, event); + protected void onPopulateNodeForVirtualView( + int virtualViewId, AccessibilityNodeInfoCompat info) { + final ClickableSpan span = getSpanForOffset(virtualViewId); + if (span != null) { + info.setContentDescription(getTextForSpan(span)); + } else { + Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); + info.setContentDescription(view.getText()); + } + info.setFocusable(true); + info.setClickable(true); + getBoundsForSpan(span, tempRect); + if (tempRect.isEmpty()) { + Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId); + tempRect.set(0, 0, 1, 1); + } + info.setBoundsInParent(tempRect); + info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); } @Override - public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) { - return mDelegate.getAccessibilityNodeProvider(host); + protected boolean onPerformActionForVirtualView( + int virtualViewId, int action, Bundle arguments) { + if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { + ClickableSpan span = getSpanForOffset(virtualViewId); + if (span != null) { + span.onClick(view); + return true; + } else { + Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); + } + } + return false; } - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - return mDelegate.performAccessibilityAction(host, action, args); + private ClickableSpan getSpanForOffset(int offset) { + CharSequence text = view.getText(); + if (text instanceof Spanned) { + Spanned spannedText = (Spanned) text; + ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class); + if (spans.length == 1) { + return spans[0]; + } + } + return null; } - /** - * Dispatches hover event to the virtual view hierarchy. This method should be called in - * {@link View#dispatchHoverEvent(MotionEvent)}. - * - * @see ExploreByTouchHelper#dispatchHoverEvent(MotionEvent) - */ - public final boolean dispatchHoverEvent(MotionEvent event) { - return mDelegate instanceof ExploreByTouchHelper - && ((ExploreByTouchHelper) mDelegate).dispatchHoverEvent(event); + private CharSequence getTextForSpan(ClickableSpan span) { + CharSequence text = view.getText(); + if (text instanceof Spanned) { + Spanned spannedText = (Spanned) text; + return spannedText.subSequence( + spannedText.getSpanStart(span), spannedText.getSpanEnd(span)); + } + return text; } - @VisibleForTesting - static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper { - - private final Rect mTempRect = new Rect(); - private final TextView mView; - - PreOLinkAccessibilityHelper(TextView view) { - super(view); - mView = view; - } - - @Override - protected int getVirtualViewAt(float x, float y) { - final CharSequence text = mView.getText(); - if (text instanceof Spanned) { - final Spanned spannedText = (Spanned) text; - final int offset = getOffsetForPosition(mView, x, y); - ClickableSpan[] linkSpans = - spannedText.getSpans(offset, offset, ClickableSpan.class); - if (linkSpans.length == 1) { - ClickableSpan linkSpan = linkSpans[0]; - return spannedText.getSpanStart(linkSpan); - } - } - return ExploreByTouchHelper.INVALID_ID; - } - - @Override - protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { - final CharSequence text = mView.getText(); - if (text instanceof Spanned) { - final Spanned spannedText = (Spanned) text; - ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(), - ClickableSpan.class); - for (ClickableSpan span : linkSpans) { - virtualViewIds.add(spannedText.getSpanStart(span)); - } - } - } - - @Override - protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { - final ClickableSpan span = getSpanForOffset(virtualViewId); - if (span != null) { - event.setContentDescription(getTextForSpan(span)); + // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for + // the section on the first line. + private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) { + CharSequence text = view.getText(); + outRect.setEmpty(); + if (text instanceof Spanned) { + final Layout layout = view.getLayout(); + if (layout != null) { + Spanned spannedText = (Spanned) text; + final int spanStart = spannedText.getSpanStart(span); + final int spanEnd = spannedText.getSpanEnd(span); + final float xStart = layout.getPrimaryHorizontal(spanStart); + final float xEnd = layout.getPrimaryHorizontal(spanEnd); + final int lineStart = layout.getLineForOffset(spanStart); + final int lineEnd = layout.getLineForOffset(spanEnd); + layout.getLineBounds(lineStart, outRect); + if (lineEnd == lineStart) { + // If the span is on a single line, adjust both the left and right bounds + // so outrect is exactly bounding the span. + outRect.left = (int) Math.min(xStart, xEnd); + outRect.right = (int) Math.max(xStart, xEnd); + } else { + // If the span wraps across multiple lines, only use the first line (as + // returned by layout.getLineBounds above), and adjust the "start" of + // outrect to where the span starts, leaving the "end" of outrect at the end + // of the line. ("start" being left for LTR, and right for RTL) + if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) { + outRect.right = (int) xStart; } else { - Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); - event.setContentDescription(mView.getText()); + outRect.left = (int) xStart; } - } - - @Override - protected void onPopulateNodeForVirtualView( - int virtualViewId, - AccessibilityNodeInfoCompat info) { - final ClickableSpan span = getSpanForOffset(virtualViewId); - if (span != null) { - info.setContentDescription(getTextForSpan(span)); - } else { - Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); - info.setContentDescription(mView.getText()); - } - info.setFocusable(true); - info.setClickable(true); - getBoundsForSpan(span, mTempRect); - if (mTempRect.isEmpty()) { - Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId); - mTempRect.set(0, 0, 1, 1); - } - info.setBoundsInParent(mTempRect); - info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); - } + } - @Override - protected boolean onPerformActionForVirtualView( - int virtualViewId, - int action, - Bundle arguments) { - if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { - ClickableSpan span = getSpanForOffset(virtualViewId); - if (span != null) { - span.onClick(mView); - return true; - } else { - Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); - } - } - return false; - } - - private ClickableSpan getSpanForOffset(int offset) { - CharSequence text = mView.getText(); - if (text instanceof Spanned) { - Spanned spannedText = (Spanned) text; - ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class); - if (spans.length == 1) { - return spans[0]; - } - } - return null; - } - - private CharSequence getTextForSpan(ClickableSpan span) { - CharSequence text = mView.getText(); - if (text instanceof Spanned) { - Spanned spannedText = (Spanned) text; - return spannedText.subSequence( - spannedText.getSpanStart(span), - spannedText.getSpanEnd(span)); - } - return text; - } - - // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for - // the section on the first line. - private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) { - CharSequence text = mView.getText(); - outRect.setEmpty(); - if (text instanceof Spanned) { - final Layout layout = mView.getLayout(); - if (layout != null) { - Spanned spannedText = (Spanned) text; - final int spanStart = spannedText.getSpanStart(span); - final int spanEnd = spannedText.getSpanEnd(span); - final float xStart = layout.getPrimaryHorizontal(spanStart); - final float xEnd = layout.getPrimaryHorizontal(spanEnd); - final int lineStart = layout.getLineForOffset(spanStart); - final int lineEnd = layout.getLineForOffset(spanEnd); - layout.getLineBounds(lineStart, outRect); - if (lineEnd == lineStart) { - // If the span is on a single line, adjust both the left and right bounds - // so outrect is exactly bounding the span. - outRect.left = (int) Math.min(xStart, xEnd); - outRect.right = (int) Math.max(xStart, xEnd); - } else { - // If the span wraps across multiple lines, only use the first line (as - // returned by layout.getLineBounds above), and adjust the "start" of - // outrect to where the span starts, leaving the "end" of outrect at the end - // of the line. ("start" being left for LTR, and right for RTL) - if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) { - outRect.right = (int) xStart; - } else { - outRect.left = (int) xStart; - } - } - - // Offset for padding - outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop()); - } - } - return outRect; + // Offset for padding + outRect.offset(view.getTotalPaddingLeft(), view.getTotalPaddingTop()); } + } + return outRect; + } - // Compat implementation of TextView#getOffsetForPosition(). + // Compat implementation of TextView#getOffsetForPosition(). - private static int getOffsetForPosition(TextView view, float x, float y) { - if (view.getLayout() == null) return -1; - final int line = getLineAtCoordinate(view, y); - return getOffsetAtCoordinate(view, line, x); - } + private static int getOffsetForPosition(TextView view, float x, float y) { + if (view.getLayout() == null) { + return -1; + } + final int line = getLineAtCoordinate(view, y); + return getOffsetAtCoordinate(view, line, x); + } - private static float convertToLocalHorizontalCoordinate(TextView view, float x) { - x -= view.getTotalPaddingLeft(); - // Clamp the position to inside of the view. - x = Math.max(0.0f, x); - x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x); - x += view.getScrollX(); - return x; - } + private static float convertToLocalHorizontalCoordinate(TextView view, float x) { + x -= view.getTotalPaddingLeft(); + // Clamp the position to inside of the view. + x = Math.max(0.0f, x); + x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x); + x += view.getScrollX(); + return x; + } - private static int getLineAtCoordinate(TextView view, float y) { - y -= view.getTotalPaddingTop(); - // Clamp the position to inside of the view. - y = Math.max(0.0f, y); - y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y); - y += view.getScrollY(); - return view.getLayout().getLineForVertical((int) y); - } + private static int getLineAtCoordinate(TextView view, float y) { + y -= view.getTotalPaddingTop(); + // Clamp the position to inside of the view. + y = Math.max(0.0f, y); + y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y); + y += view.getScrollY(); + return view.getLayout().getLineForVertical((int) y); + } - private static int getOffsetAtCoordinate(TextView view, int line, float x) { - x = convertToLocalHorizontalCoordinate(view, x); - return view.getLayout().getOffsetForHorizontal(line, x); - } + private static int getOffsetAtCoordinate(TextView view, int line, float x) { + x = convertToLocalHorizontalCoordinate(view, x); + return view.getLayout().getOffsetForHorizontal(line, x); } + } } diff --git a/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java b/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java index 872cc9f..b2d9d79 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java +++ b/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java @@ -23,11 +23,10 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Build; +import androidx.annotation.NonNull; import android.util.AttributeSet; import android.widget.Button; -import androidx.annotation.NonNull; - /** * Button for navigation bar, which includes tinting of its compound drawables to be used for dark * and light themes. @@ -35,128 +34,144 @@ import androidx.annotation.NonNull; @SuppressLint("AppCompatCustomView") public class NavigationBarButton extends Button { - public NavigationBarButton(Context context) { - super(context); - init(); + public NavigationBarButton(Context context) { + super(context); + init(); + } + + public NavigationBarButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter, + // so manually getting it and wrapping it in the compat drawable. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Drawable[] drawables = getCompoundDrawablesRelative(); + for (int i = 0; i < drawables.length; i++) { + if (drawables[i] != null) { + drawables[i] = TintedDrawable.wrap(drawables[i]); + } + } + setCompoundDrawablesRelativeWithIntrinsicBounds( + drawables[0], drawables[1], drawables[2], drawables[3]); } + } - public NavigationBarButton(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + @Override + public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { + if (left != null) { + left = TintedDrawable.wrap(left); } - - private void init() { - // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter, - // so manually getting it and wrapping it in the compat drawable. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Drawable[] drawables = getCompoundDrawablesRelative(); - for (int i = 0; i < drawables.length; i++) { - if (drawables[i] != null) { - drawables[i] = TintedDrawable.wrap(drawables[i]); - } - } - setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1], - drawables[2], drawables[3]); + if (top != null) { + top = TintedDrawable.wrap(top); + } + if (right != null) { + right = TintedDrawable.wrap(right); + } + if (bottom != null) { + bottom = TintedDrawable.wrap(bottom); + } + super.setCompoundDrawables(left, top, right, bottom); + tintDrawables(); + } + + @Override + public void setCompoundDrawablesRelative( + Drawable start, Drawable top, Drawable end, Drawable bottom) { + if (start != null) { + start = TintedDrawable.wrap(start); + } + if (top != null) { + top = TintedDrawable.wrap(top); + } + if (end != null) { + end = TintedDrawable.wrap(end); + } + if (bottom != null) { + bottom = TintedDrawable.wrap(bottom); + } + super.setCompoundDrawablesRelative(start, top, end, bottom); + tintDrawables(); + } + + @Override + public void setTextColor(ColorStateList colors) { + super.setTextColor(colors); + tintDrawables(); + } + + private void tintDrawables() { + final ColorStateList textColors = getTextColors(); + if (textColors != null) { + for (Drawable drawable : getAllCompoundDrawables()) { + if (drawable instanceof TintedDrawable) { + ((TintedDrawable) drawable).setTintListCompat(textColors); } + } + invalidate(); + } + } + + private Drawable[] getAllCompoundDrawables() { + Drawable[] drawables = new Drawable[6]; + Drawable[] compoundDrawables = getCompoundDrawables(); + drawables[0] = compoundDrawables[0]; // left + drawables[1] = compoundDrawables[1]; // top + drawables[2] = compoundDrawables[2]; // right + drawables[3] = compoundDrawables[3]; // bottom + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative(); + drawables[4] = compoundDrawablesRelative[0]; // start + drawables[5] = compoundDrawablesRelative[2]; // end + } + return drawables; + } + + // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0 + // or above + private static class TintedDrawable extends LayerDrawable { + + public static TintedDrawable wrap(Drawable drawable) { + if (drawable instanceof TintedDrawable) { + return (TintedDrawable) drawable; + } + return new TintedDrawable(drawable.mutate()); } - @Override - public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { - if (left != null) left = TintedDrawable.wrap(left); - if (top != null) top = TintedDrawable.wrap(top); - if (right != null) right = TintedDrawable.wrap(right); - if (bottom != null) bottom = TintedDrawable.wrap(bottom); - super.setCompoundDrawables(left, top, right, bottom); - tintDrawables(); + private ColorStateList tintList = null; + + TintedDrawable(Drawable wrapped) { + super(new Drawable[] {wrapped}); } @Override - public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end, - Drawable bottom) { - if (start != null) start = TintedDrawable.wrap(start); - if (top != null) top = TintedDrawable.wrap(top); - if (end != null) end = TintedDrawable.wrap(end); - if (bottom != null) bottom = TintedDrawable.wrap(bottom); - super.setCompoundDrawablesRelative(start, top, end, bottom); - tintDrawables(); + public boolean isStateful() { + return true; } @Override - public void setTextColor(ColorStateList colors) { - super.setTextColor(colors); - tintDrawables(); - } - - private void tintDrawables() { - final ColorStateList textColors = getTextColors(); - if (textColors != null) { - for (Drawable drawable : getAllCompoundDrawables()) { - if (drawable instanceof TintedDrawable) { - ((TintedDrawable) drawable).setTintListCompat(textColors); - } - } - invalidate(); - } + public boolean setState(@NonNull int[] stateSet) { + boolean needsInvalidate = super.setState(stateSet); + boolean needsInvalidateForState = updateState(); + return needsInvalidate || needsInvalidateForState; } - private Drawable[] getAllCompoundDrawables() { - Drawable[] drawables = new Drawable[6]; - Drawable[] compoundDrawables = getCompoundDrawables(); - drawables[0] = compoundDrawables[0]; // left - drawables[1] = compoundDrawables[1]; // top - drawables[2] = compoundDrawables[2]; // right - drawables[3] = compoundDrawables[3]; // bottom - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative(); - drawables[4] = compoundDrawablesRelative[0]; // start - drawables[5] = compoundDrawablesRelative[2]; // end - } - return drawables; + public void setTintListCompat(ColorStateList colors) { + tintList = colors; + if (updateState()) { + invalidateSelf(); + } } - // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0 - // or above - private static class TintedDrawable extends LayerDrawable { - - public static TintedDrawable wrap(Drawable drawable) { - if (drawable instanceof TintedDrawable) { - return (TintedDrawable) drawable; - } - return new TintedDrawable(drawable.mutate()); - } - - private ColorStateList mTintList = null; - - TintedDrawable(Drawable wrapped) { - super(new Drawable[] { wrapped }); - } - - @Override - public boolean isStateful() { - return true; - } - - @Override - public boolean setState(@NonNull int[] stateSet) { - boolean needsInvalidate = super.setState(stateSet); - boolean needsInvalidateForState = updateState(); - return needsInvalidate || needsInvalidateForState; - } - - public void setTintListCompat(ColorStateList colors) { - mTintList = colors; - if (updateState()) { - invalidateSelf(); - } - } - - private boolean updateState() { - if (mTintList != null) { - final int color = mTintList.getColorForState(getState(), 0); - setColorFilter(color, PorterDuff.Mode.SRC_IN); - return true; // Needs invalidate - } - return false; - } + private boolean updateState() { + if (tintList != null) { + final int color = tintList.getColorForState(getState(), 0); + setColorFilter(color, PorterDuff.Mode.SRC_IN); + return true; // Needs invalidate + } + return false; } + } } diff --git a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java index 1ee3219..5e50e7a 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java +++ b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java @@ -20,6 +20,8 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import androidx.core.view.ViewCompat; +import androidx.appcompat.widget.AppCompatTextView; import android.text.Annotation; import android.text.SpannableString; import android.text.Spanned; @@ -29,10 +31,6 @@ import android.text.style.TextAppearanceSpan; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; - -import androidx.appcompat.widget.AppCompatTextView; -import androidx.core.view.ViewCompat; - import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; import com.android.setupwizardlib.span.SpanHelper; @@ -40,178 +38,178 @@ import com.android.setupwizardlib.util.LinkAccessibilityHelper; import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod; /** - * An extension of TextView that automatically replaces the annotation tags as specified in - * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)} + * An extension of TextView that automatically replaces the annotation tags as specified in {@link + * SpanHelper#replaceSpan(android.text.Spannable, Object, Object)} */ public class RichTextView extends AppCompatTextView implements OnLinkClickListener { - /* static section */ - - private static final String TAG = "RichTextView"; - - private static final String ANNOTATION_LINK = "link"; - private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance"; - - /** - * Replace <annotation> tags in strings to become their respective types. Currently 2 - * types are supported: - * <ol> - * <li><annotation link="foobar"> will create a - * {@link com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key - * "foobar"</li> - * <li><annotation textAppearance="TextAppearance.FooBar"> will create a - * {@link android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar</li> - * </ol> - */ - public static CharSequence getRichText(Context context, CharSequence text) { - if (text instanceof Spanned) { - final SpannableString spannable = new SpannableString(text); - final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class); - for (Annotation span : spans) { - final String key = span.getKey(); - if (ANNOTATION_TEXT_APPEARANCE.equals(key)) { - String textAppearance = span.getValue(); - final int style = context.getResources() - .getIdentifier(textAppearance, "style", context.getPackageName()); - if (style == 0) { - Log.w(TAG, "Cannot find resource: " + style); - } - final TextAppearanceSpan textAppearanceSpan = - new TextAppearanceSpan(context, style); - SpanHelper.replaceSpan(spannable, span, textAppearanceSpan); - } else if (ANNOTATION_LINK.equals(key)) { - LinkSpan link = new LinkSpan(span.getValue()); - SpanHelper.replaceSpan(spannable, span, link); - } - } - return spannable; + /* static section */ + + private static final String TAG = "RichTextView"; + + private static final String ANNOTATION_LINK = "link"; + private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance"; + + /** + * Replace <annotation> tags in strings to become their respective types. Currently 2 types + * are supported: + * + * <ol> + * <li><annotation link="foobar"> will create a {@link + * com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key "foobar" + * <li><annotation textAppearance="TextAppearance.FooBar"> will create a {@link + * android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar + * </ol> + */ + public static CharSequence getRichText(Context context, CharSequence text) { + if (text instanceof Spanned) { + final SpannableString spannable = new SpannableString(text); + final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class); + for (Annotation span : spans) { + final String key = span.getKey(); + if (ANNOTATION_TEXT_APPEARANCE.equals(key)) { + String textAppearance = span.getValue(); + final int style = + context + .getResources() + .getIdentifier(textAppearance, "style", context.getPackageName()); + if (style == 0) { + Log.w(TAG, "Cannot find resource: " + style); + } + final TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(context, style); + SpanHelper.replaceSpan(spannable, span, textAppearanceSpan); + } else if (ANNOTATION_LINK.equals(key)) { + LinkSpan link = new LinkSpan(span.getValue()); + SpanHelper.replaceSpan(spannable, span, link); } - return text; - } - - /* non-static section */ - - private LinkAccessibilityHelper mAccessibilityHelper; - private OnLinkClickListener mOnLinkClickListener; - - public RichTextView(Context context) { - super(context); - init(); - } - - public RichTextView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + } + return spannable; } - - private void init() { - mAccessibilityHelper = new LinkAccessibilityHelper(this); - ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper); + return text; + } + + /* non-static section */ + + private LinkAccessibilityHelper accessibilityHelper; + private OnLinkClickListener onLinkClickListener; + + public RichTextView(Context context) { + super(context); + init(); + } + + public RichTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + accessibilityHelper = new LinkAccessibilityHelper(this); + ViewCompat.setAccessibilityDelegate(this, accessibilityHelper); + } + + @Override + public void setText(CharSequence text, BufferType type) { + text = getRichText(getContext(), text); + // Set text first before doing anything else because setMovementMethod internally calls + // setText. This in turn ends up calling this method with mText as the first parameter + super.setText(text, type); + boolean hasLinks = hasLinks(text); + + if (hasLinks) { + // When a TextView has a movement method, it will set the view to clickable. This makes + // View.onTouchEvent always return true and consumes the touch event, essentially + // nullifying any return values of MovementMethod.onTouchEvent. + // To still allow propagating touch events to the parent when this view doesn't have + // links, we only set the movement method here if the text contains links. + setMovementMethod(TouchableLinkMovementMethod.getInstance()); + } else { + setMovementMethod(null); } - - @Override - public void setText(CharSequence text, BufferType type) { - text = getRichText(getContext(), text); - // Set text first before doing anything else because setMovementMethod internally calls - // setText. This in turn ends up calling this method with mText as the first parameter - super.setText(text, type); - boolean hasLinks = hasLinks(text); - - if (hasLinks) { - // When a TextView has a movement method, it will set the view to clickable. This makes - // View.onTouchEvent always return true and consumes the touch event, essentially - // nullifying any return values of MovementMethod.onTouchEvent. - // To still allow propagating touch events to the parent when this view doesn't have - // links, we only set the movement method here if the text contains links. - setMovementMethod(TouchableLinkMovementMethod.getInstance()); - } else { - setMovementMethod(null); - } - // ExploreByTouchHelper automatically enables focus for RichTextView - // even though it may not have any links. Causes problems during talkback - // as individual TextViews consume touch events and thereby reducing the focus window - // shown by Talkback. Disable focus if there are no links - setFocusable(hasLinks); - // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is - // focusable in touch mode, we may be focused when the screen is first shown, and starting - // a screen halfway scrolled down is confusing to the user. - if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { - setRevealOnFocusHint(false); - // setRevealOnFocusHint is a new API added in SDK 25. For lower SDK versions, do not - // call setFocusableInTouchMode. We won't get touch effect on those earlier versions, - // but the link will still work, and will prevent the scroll view from starting halfway - // down the page. - setFocusableInTouchMode(hasLinks); - } + // ExploreByTouchHelper automatically enables focus for RichTextView + // even though it may not have any links. Causes problems during talkback + // as individual TextViews consume touch events and thereby reducing the focus window + // shown by Talkback. Disable focus if there are no links + setFocusable(hasLinks); + // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is + // focusable in touch mode, we may be focused when the screen is first shown, and starting + // a screen halfway scrolled down is confusing to the user. + if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { + setRevealOnFocusHint(false); + // setRevealOnFocusHint is a new API added in SDK 25. For lower SDK versions, do not + // call setFocusableInTouchMode. We won't get touch effect on those earlier versions, + // but the link will still work, and will prevent the scroll view from starting halfway + // down the page. + setFocusableInTouchMode(hasLinks); } + } - private boolean hasLinks(CharSequence text) { - if (text instanceof Spanned) { - final ClickableSpan[] spans = - ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); - return spans.length > 0; - } - return false; + private boolean hasLinks(CharSequence text) { + if (text instanceof Spanned) { + final ClickableSpan[] spans = + ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); + return spans.length > 0; } - - @Override - @SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called - public boolean onTouchEvent(MotionEvent event) { - // Since View#onTouchEvent always return true if the view is clickable (which is the case - // when a TextView has a movement method), override the implementation to allow the movement - // method, if it implements TouchableMovementMethod, to say that the touch is not handled, - // allowing the event to bubble up to the parent view. - boolean superResult = super.onTouchEvent(event); - MovementMethod movementMethod = getMovementMethod(); - if (movementMethod instanceof TouchableMovementMethod) { - TouchableMovementMethod touchableMovementMethod = - (TouchableMovementMethod) movementMethod; - if (touchableMovementMethod.getLastTouchEvent() == event) { - return touchableMovementMethod.isLastTouchEventHandled(); - } - } - return superResult; + return false; + } + + @Override + @SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called + public boolean onTouchEvent(MotionEvent event) { + // Since View#onTouchEvent always return true if the view is clickable (which is the case + // when a TextView has a movement method), override the implementation to allow the movement + // method, if it implements TouchableMovementMethod, to say that the touch is not handled, + // allowing the event to bubble up to the parent view. + boolean superResult = super.onTouchEvent(event); + MovementMethod movementMethod = getMovementMethod(); + if (movementMethod instanceof TouchableMovementMethod) { + TouchableMovementMethod touchableMovementMethod = (TouchableMovementMethod) movementMethod; + if (touchableMovementMethod.getLastTouchEvent() == event) { + return touchableMovementMethod.isLastTouchEventHandled(); + } } + return superResult; + } - @Override - protected boolean dispatchHoverEvent(MotionEvent event) { - if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) { - return true; - } - return super.dispatchHoverEvent(event); + @Override + protected boolean dispatchHoverEvent(MotionEvent event) { + if (accessibilityHelper != null && accessibilityHelper.dispatchHoverEvent(event)) { + return true; } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a - // workaround, set the state on those drawables directly. - final int[] state = getDrawableState(); - for (Drawable drawable : getCompoundDrawablesRelative()) { - if (drawable != null) { - if (drawable.setState(state)) { - invalidateDrawable(drawable); - } - } - } + return super.dispatchHoverEvent(event); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a + // workaround, set the state on those drawables directly. + final int[] state = getDrawableState(); + for (Drawable drawable : getCompoundDrawablesRelative()) { + if (drawable != null) { + if (drawable.setState(state)) { + invalidateDrawable(drawable); + } } + } } + } - public void setOnLinkClickListener(OnLinkClickListener listener) { - mOnLinkClickListener = listener; - } + public void setOnLinkClickListener(OnLinkClickListener listener) { + onLinkClickListener = listener; + } - public OnLinkClickListener getOnLinkClickListener() { - return mOnLinkClickListener; - } + public OnLinkClickListener getOnLinkClickListener() { + return onLinkClickListener; + } - @Override - public boolean onLinkClick(LinkSpan span) { - if (mOnLinkClickListener != null) { - return mOnLinkClickListener.onLinkClick(span); - } - return false; + @Override + public boolean onLinkClick(LinkSpan span) { + if (onLinkClickListener != null) { + return onLinkClickListener.onLinkClick(span); } + return false; + } } diff --git a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java index f5b8253..7f51fa7 100644 --- a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java @@ -18,19 +18,16 @@ package com.android.setupwizardlib.items; import static org.junit.Assert.assertTrue; +import androidx.annotation.StyleRes; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.LinearLayout; - -import androidx.annotation.StyleRes; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.test.util.DrawingTestHelper; - import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,69 +36,65 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ButtonItemDrawingTest { - private static final int GLIF_ACCENT_COLOR = 0xff4285f4; - private static final int GLIF_V3_ACCENT_COLOR = 0xff1a73e8; - - // These tests need to be run on UI thread because button uses ValueAnimator - @Rule - public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); - - @Test - @UiThreadTest - public void drawButton_glif_shouldHaveAccentColoredButton() - throws InstantiationException, IllegalAccessException { - Button button = createButton(R.style.SuwThemeGlif_Light); - - DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); - drawingTestHelper.drawView(button); - - int accentPixelCount = - countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_ACCENT_COLOR); - assertTrue("> 10 pixels should be #4285f4. Found " + accentPixelCount, - accentPixelCount > 10); - } - - @Test - @UiThreadTest - public void drawButton_glifV3_shouldHaveAccentColoredButton() - throws InstantiationException, IllegalAccessException { - Button button = createButton(R.style.SuwThemeGlifV3_Light); - - DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); - drawingTestHelper.drawView(button); - - int accentPixelCount = - countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_V3_ACCENT_COLOR); - assertTrue("> 10 pixels should be #1a73e8. Found " + accentPixelCount, - accentPixelCount > 10); - } - - private Button createButton(@StyleRes int theme) - throws InstantiationException, IllegalAccessException { - final ViewGroup parent = new LinearLayout(DrawingTestHelper.createCanvasActivity(theme)); - TestButtonItem item = new TestButtonItem(); - item.setTheme(R.style.SuwButtonItem_Colored); - item.setText("foobar"); - - return item.createButton(parent); - } - - private int countPixelsWithColor(int[] pixels, int color) { - int count = 0; - for (int pixel : pixels) { - if (pixel == color) { - count++; - } - } - return count; + private static final int GLIF_ACCENT_COLOR = 0xff4285f4; + private static final int GLIF_V3_ACCENT_COLOR = 0xff1a73e8; + + // These tests need to be run on UI thread because button uses ValueAnimator + @Rule public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); + + @Test + @UiThreadTest + public void drawButton_glif_shouldHaveAccentColoredButton() + throws InstantiationException, IllegalAccessException { + Button button = createButton(R.style.SuwThemeGlif_Light); + + DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); + drawingTestHelper.drawView(button); + + int accentPixelCount = countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_ACCENT_COLOR); + assertTrue("> 10 pixels should be #4285f4. Found " + accentPixelCount, accentPixelCount > 10); + } + + @Test + @UiThreadTest + public void drawButton_glifV3_shouldHaveAccentColoredButton() + throws InstantiationException, IllegalAccessException { + Button button = createButton(R.style.SuwThemeGlifV3_Light); + + DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); + drawingTestHelper.drawView(button); + + int accentPixelCount = + countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_V3_ACCENT_COLOR); + assertTrue("> 10 pixels should be #1a73e8. Found " + accentPixelCount, accentPixelCount > 10); + } + + private Button createButton(@StyleRes int theme) + throws InstantiationException, IllegalAccessException { + final ViewGroup parent = new LinearLayout(DrawingTestHelper.createCanvasActivity(theme)); + TestButtonItem item = new TestButtonItem(); + item.setTheme(R.style.SuwButtonItem_Colored); + item.setText("foobar"); + + return item.createButton(parent); + } + + private int countPixelsWithColor(int[] pixels, int color) { + int count = 0; + for (int pixel : pixels) { + if (pixel == color) { + count++; + } } + return count; + } - private static class TestButtonItem extends ButtonItem { + private static class TestButtonItem extends ButtonItem { - @Override - public Button createButton(ViewGroup parent) { - // Make this method public for testing - return super.createButton(parent); - } + @Override + public Button createButton(ViewGroup parent) { + // Make this method public for testing + return super.createButton(parent); } + } } diff --git a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java index d3518f4..80e23e9 100644 --- a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java @@ -26,5 +26,4 @@ import androidx.appcompat.app.AppCompatActivity; * * @see DrawingTestHelper */ -public class DrawingTestActivity extends AppCompatActivity { -} +public class DrawingTestActivity extends AppCompatActivity {} diff --git a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java index 7b4ad4d..da5e16c 100644 --- a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java @@ -25,342 +25,334 @@ import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.os.Bundle; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; +import androidx.customview.widget.ExploreByTouchHelper; import android.text.SpannableStringBuilder; import android.util.DisplayMetrics; import android.util.TypedValue; -import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.TextView; - import androidx.core.text.BidiFormatter; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; -import androidx.customview.widget.ExploreByTouchHelper; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.util.LinkAccessibilityHelper.PreOLinkAccessibilityHelper; - -import org.junit.Test; -import org.junit.runner.RunWith; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class LinkAccessibilityHelperTest { - private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); - - private TextView mTextView; - private TestPreOLinkAccessibilityHelper mHelper; - - private DisplayMetrics mDisplayMetrics; - - @Test - public void testGetVirtualViewAt() { - initTextView(); - final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10)); - assertEquals("Virtual view ID should be 1", 1, virtualViewId); - } - - @Test - public void testGetVirtualViewAtHost() { - initTextView(); - final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100)); - assertEquals("Virtual view ID should be INVALID_ID", - ExploreByTouchHelper.INVALID_ID, virtualViewId); - } - - @Test - public void testGetVisibleVirtualViews() { - initTextView(); - List<Integer> virtualViewIds = new ArrayList<>(); - mHelper.getVisibleVirtualViews(virtualViewIds); - - assertEquals("VisibleVirtualViews should be [1]", - Collections.singletonList(1), virtualViewIds); - } - - @Test - public void testOnPopulateEventForVirtualView() { - initTextView(); - AccessibilityEvent event = AccessibilityEvent.obtain(); - mHelper.onPopulateEventForVirtualView(1, event); - - // LinkSpan is set on substring(1, 2) of "Hello world" --> "e" - assertEquals("LinkSpan description should be \"e\"", - "e", event.getContentDescription().toString()); - - event.recycle(); - } - - @Test - public void testOnPopulateEventForVirtualViewHost() { - initTextView(); - AccessibilityEvent event = AccessibilityEvent.obtain(); - mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event); - - assertEquals("Host view description should be \"Hello world\"", "Hello world", - event.getContentDescription().toString()); - - event.recycle(); - } - - @Test - public void testOnPopulateNodeForVirtualView() { - initTextView(); - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(1, info); - - assertEquals("LinkSpan description should be \"e\"", - "e", info.getContentDescription().toString()); - assertTrue("LinkSpan should be focusable", info.isFocusable()); - assertTrue("LinkSpan should be clickable", info.isClickable()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should be (10.5dp, 0dp, 18.5dp, 20.5dp)", - new Rect(dp2Px(10.5f), dp2Px(0f), dp2Px(18.5f), dp2Px(20.5f)), bounds); - - info.recycle(); - } - - @Test - public void testNullLayout() { - initTextView(); - // Setting the padding will cause the layout to be null-ed out. - mTextView.setPadding(1, 1, 1, 1); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(0, info); - - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should be (0, 0, 1, 1)", - new Rect(0, 0, 1, 1), bounds); - - info.recycle(); - } - - @Test - public void testRtlLayout() { - SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום"); - ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); - initTextView(ssb); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(1, info); - - assertEquals("LinkSpan description should be \"כ\"", - "כ", info.getContentDescription().toString()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should be (481.5dp, 0dp, 489.5dp, 20.5dp)", - new Rect(dp2Px(481.5f), dp2Px(0f), dp2Px(489.5f), dp2Px(20.5f)), bounds); - - info.recycle(); - } - - @Test - public void testMultilineLink() { - SpannableStringBuilder ssb = new SpannableStringBuilder( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); + + private TextView mTextView; + private TestPreOLinkAccessibilityHelper mHelper; + + private DisplayMetrics mDisplayMetrics; + + @Test + public void testGetVirtualViewAt() { + initTextView(); + final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10)); + assertEquals("Virtual view ID should be 1", 1, virtualViewId); + } + + @Test + public void testGetVirtualViewAtHost() { + initTextView(); + final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100)); + assertEquals( + "Virtual view ID should be INVALID_ID", ExploreByTouchHelper.INVALID_ID, virtualViewId); + } + + @Test + public void testGetVisibleVirtualViews() { + initTextView(); + List<Integer> virtualViewIds = new ArrayList<>(); + mHelper.getVisibleVirtualViews(virtualViewIds); + + assertEquals("VisibleVirtualViews should be [1]", Collections.singletonList(1), virtualViewIds); + } + + @Test + public void testOnPopulateEventForVirtualView() { + initTextView(); + AccessibilityEvent event = AccessibilityEvent.obtain(); + mHelper.onPopulateEventForVirtualView(1, event); + + // LinkSpan is set on substring(1, 2) of "Hello world" --> "e" + assertEquals( + "LinkSpan description should be \"e\"", "e", event.getContentDescription().toString()); + + event.recycle(); + } + + @Test + public void testOnPopulateEventForVirtualViewHost() { + initTextView(); + AccessibilityEvent event = AccessibilityEvent.obtain(); + mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event); + + assertEquals( + "Host view description should be \"Hello world\"", + "Hello world", + event.getContentDescription().toString()); + + event.recycle(); + } + + @Test + public void testOnPopulateNodeForVirtualView() { + initTextView(); + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(1, info); + + assertEquals( + "LinkSpan description should be \"e\"", "e", info.getContentDescription().toString()); + assertTrue("LinkSpan should be focusable", info.isFocusable()); + assertTrue("LinkSpan should be clickable", info.isClickable()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should be (10.5dp, 0dp, 18.5dp, 20.5dp)", + new Rect(dp2Px(10.5f), dp2Px(0f), dp2Px(18.5f), dp2Px(20.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testNullLayout() { + initTextView(); + // Setting the padding will cause the layout to be null-ed out. + mTextView.setPadding(1, 1, 1, 1); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(0, info); + + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals("LinkSpan bounds should be (0, 0, 1, 1)", new Rect(0, 0, 1, 1), bounds); + + info.recycle(); + } + + @Test + public void testRtlLayout() { + SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום"); + ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); + initTextView(ssb); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(1, info); + + assertEquals( + "LinkSpan description should be \"כ\"", "כ", info.getContentDescription().toString()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should be (481.5dp, 0dp, 489.5dp, 20.5dp)", + new Rect(dp2Px(481.5f), dp2Px(0f), dp2Px(489.5f), dp2Px(20.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testMultilineLink() { + SpannableStringBuilder ssb = + new SpannableStringBuilder( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Praesent accumsan efficitur eros eu porttitor."); - ssb.setSpan(LINK_SPAN, 51, 74, 0 /* flags */); - initTextView(ssb); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(51, info); - - assertEquals("LinkSpan description should match the span", - "elit. Praesent accumsan", info.getContentDescription().toString()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should match first line of the span", - new Rect(dp2Px(343f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), bounds); - - info.recycle(); + ssb.setSpan(LINK_SPAN, 51, 74, 0 /* flags */); + initTextView(ssb); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(51, info); + + assertEquals( + "LinkSpan description should match the span", + "elit. Praesent accumsan", + info.getContentDescription().toString()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should match first line of the span", + new Rect(dp2Px(343f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testRtlMultilineLink() { + String iwLoremIpsum = + "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " + + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + + "דפים המחשב מיזמים ב."; + SpannableStringBuilder ssb = new SpannableStringBuilder(iwLoremIpsum); + ssb.setSpan(LINK_SPAN, 50, 100, 0 /* flags */); + initTextView(ssb); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(50, info); + + assertEquals( + "LinkSpan description should match the span", + iwLoremIpsum.substring(50, 100), + info.getContentDescription().toString()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should match the first line of the span", + new Rect(dp2Px(0f), dp2Px(0f), dp2Px(150f), dp2Px(19.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testBidiMultilineLink() { + String iwLoremIpsum = + "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " + + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + + "דפים המחשב מיזמים ב."; + BidiFormatter formatter = BidiFormatter.getInstance(false /* rtlContext */); + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append("hello ").append(formatter.unicodeWrap(iwLoremIpsum)).append(" world"); + ssb.setSpan( + LINK_SPAN, + "hello ".length() + 2, // Add two for the characters added by BidiFormatter + "hello ".length() + 2 + iwLoremIpsum.length(), + 0 /* flags */); + initTextView(ssb); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView("hello ".length() + 2, info); + + assertEquals( + "LinkSpan description should match the span", + iwLoremIpsum, + info.getContentDescription().toString()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should match the first line of the span", + new Rect(dp2Px(491.5f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testMethodDelegation() { + initTextView(); + AccessibilityDelegateCompat delegate = mock(AccessibilityDelegateCompat.class); + LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate); + + AccessibilityEvent accessibilityEvent = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED); + + helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED); + verify(delegate) + .sendAccessibilityEvent(same(mTextView), eq(AccessibilityEvent.TYPE_VIEW_CLICKED)); + + helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent); + verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent)); + + helper.performAccessibilityAction( + mTextView, AccessibilityActionCompat.ACTION_CLICK.getId(), Bundle.EMPTY); + verify(delegate) + .performAccessibilityAction( + same(mTextView), eq(AccessibilityActionCompat.ACTION_CLICK.getId()), eq(Bundle.EMPTY)); + + helper.dispatchPopulateAccessibilityEvent(mTextView, accessibilityEvent); + verify(delegate).dispatchPopulateAccessibilityEvent(same(mTextView), same(accessibilityEvent)); + + helper.getAccessibilityNodeProvider(mTextView); + verify(delegate).getAccessibilityNodeProvider(same(mTextView)); + + helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent); + verify(delegate).onInitializeAccessibilityEvent(same(mTextView), eq(accessibilityEvent)); + + AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain(); + helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo); + verify(delegate) + .onInitializeAccessibilityNodeInfo(same(mTextView), same(accessibilityNodeInfo)); + + helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent); + verify(delegate).onPopulateAccessibilityEvent(same(mTextView), same(accessibilityEvent)); + + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext()); + helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent); + verify(delegate) + .onRequestSendAccessibilityEvent(same(parent), same(mTextView), same(accessibilityEvent)); + } + + private void initTextView() { + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); + initTextView(ssb); + } + + private void initTextView(CharSequence text) { + mTextView = new TextView(InstrumentationRegistry.getContext()); + mTextView.setSingleLine(false); + mTextView.setText(text); + mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + mHelper = new TestPreOLinkAccessibilityHelper(mTextView); + + int measureExactly500dp = + View.MeasureSpec.makeMeasureSpec(dp2Px(500), View.MeasureSpec.EXACTLY); + mTextView.measure(measureExactly500dp, measureExactly500dp); + mTextView.layout(dp2Px(0), dp2Px(0), dp2Px(500), dp2Px(500)); + } + + private int dp2Px(float dp) { + if (mDisplayMetrics == null) { + mDisplayMetrics = InstrumentationRegistry.getContext().getResources().getDisplayMetrics(); } + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics); + } - @Test - public void testRtlMultilineLink() { - String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " - + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " - + "דפים המחשב מיזמים ב."; - SpannableStringBuilder ssb = new SpannableStringBuilder(iwLoremIpsum); - ssb.setSpan(LINK_SPAN, 50, 100, 0 /* flags */); - initTextView(ssb); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(50, info); - - assertEquals("LinkSpan description should match the span", - iwLoremIpsum.substring(50, 100), - info.getContentDescription().toString()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should match the first line of the span", - new Rect(dp2Px(0f), dp2Px(0f), dp2Px(150f), dp2Px(19.5f)), bounds); - - info.recycle(); - } + public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper { - @Test - public void testBidiMultilineLink() { - String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " - + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " - + "דפים המחשב מיזמים ב."; - BidiFormatter formatter = BidiFormatter.getInstance(false /* rtlContext */); - SpannableStringBuilder ssb = new SpannableStringBuilder(); - ssb.append("hello ").append(formatter.unicodeWrap(iwLoremIpsum)).append(" world"); - ssb.setSpan(LINK_SPAN, - "hello ".length() + 2, // Add two for the characters added by BidiFormatter - "hello ".length() + 2 + iwLoremIpsum.length(), - 0 /* flags */); - initTextView(ssb); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView("hello ".length() + 2, info); - - assertEquals("LinkSpan description should match the span", - iwLoremIpsum, - info.getContentDescription().toString()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should match the first line of the span", - new Rect(dp2Px(491.5f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), bounds); - - info.recycle(); + TestPreOLinkAccessibilityHelper(TextView view) { + super(view); } - @Test - public void testMethodDelegation() { - initTextView(); - ExploreByTouchHelper delegate = mock(TestPreOLinkAccessibilityHelper.class); - LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate); - - AccessibilityEvent accessibilityEvent = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED); - - helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED); - verify(delegate).sendAccessibilityEvent( - same(mTextView), - eq(AccessibilityEvent.TYPE_VIEW_CLICKED)); - - helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent); - verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent)); - - helper.performAccessibilityAction( - mTextView, - AccessibilityActionCompat.ACTION_CLICK.getId(), - Bundle.EMPTY); - verify(delegate).performAccessibilityAction( - same(mTextView), - eq(AccessibilityActionCompat.ACTION_CLICK.getId()), - eq(Bundle.EMPTY)); - - helper.dispatchPopulateAccessibilityEvent( - mTextView, - accessibilityEvent); - verify(delegate).dispatchPopulateAccessibilityEvent( - same(mTextView), - same(accessibilityEvent)); - - MotionEvent motionEvent = MotionEvent.obtain(0, 0, 0, 0, 0, 0); - helper.dispatchHoverEvent(motionEvent); - verify(delegate).dispatchHoverEvent(eq(motionEvent)); - - helper.getAccessibilityNodeProvider(mTextView); - verify(delegate).getAccessibilityNodeProvider(same(mTextView)); - - helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent); - verify(delegate).onInitializeAccessibilityEvent( - same(mTextView), - eq(accessibilityEvent)); - - AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain(); - helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo); - verify(delegate).onInitializeAccessibilityNodeInfo( - same(mTextView), - same(accessibilityNodeInfo)); - - helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent); - verify(delegate).onPopulateAccessibilityEvent( - same(mTextView), - same(accessibilityEvent)); - - FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext()); - helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent); - verify(delegate).onRequestSendAccessibilityEvent( - same(parent), - same(mTextView), - same(accessibilityEvent)); + @Override + public int getVirtualViewAt(float x, float y) { + return super.getVirtualViewAt(x, y); } - private void initTextView() { - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); - initTextView(ssb); + @Override + public void getVisibleVirtualViews(List<Integer> virtualViewIds) { + super.getVisibleVirtualViews(virtualViewIds); } - private void initTextView(CharSequence text) { - mTextView = new TextView(InstrumentationRegistry.getContext()); - mTextView.setSingleLine(false); - mTextView.setText(text); - mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - mHelper = new TestPreOLinkAccessibilityHelper(mTextView); - - int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500), - View.MeasureSpec.EXACTLY); - mTextView.measure(measureExactly500dp, measureExactly500dp); - mTextView.layout(dp2Px(0), dp2Px(0), dp2Px(500), dp2Px(500)); + @Override + public void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { + super.onPopulateEventForVirtualView(virtualViewId, event); } - private int dp2Px(float dp) { - if (mDisplayMetrics == null) { - mDisplayMetrics = - InstrumentationRegistry.getContext().getResources().getDisplayMetrics(); - } - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics); + @Override + public void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat info) { + super.onPopulateNodeForVirtualView(virtualViewId, info); } - public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper { - - TestPreOLinkAccessibilityHelper(TextView view) { - super(view); - } - - @Override - public int getVirtualViewAt(float x, float y) { - return super.getVirtualViewAt(x, y); - } - - @Override - public void getVisibleVirtualViews(List<Integer> virtualViewIds) { - super.getVisibleVirtualViews(virtualViewIds); - } - - @Override - public void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { - super.onPopulateEventForVirtualView(virtualViewId, event); - } - - @Override - public void onPopulateNodeForVirtualView(int virtualViewId, - AccessibilityNodeInfoCompat info) { - super.onPopulateNodeForVirtualView(virtualViewId, info); - } - - @Override - public boolean onPerformActionForVirtualView(int virtualViewId, int action, - Bundle arguments) { - return super.onPerformActionForVirtualView(virtualViewId, action, arguments); - } + @Override + public boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) { + return super.onPerformActionForVirtualView(virtualViewId, action, arguments); } + } } diff --git a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java index 6192061..c43b4ab 100644 --- a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java +++ b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.items; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -27,133 +26,139 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.view.CheckableLinearLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class ExpandableSwitchItemTest { - private TextView mSummaryView; - private ExpandableSwitchItem mItem; - - @Before - public void setUp() { - mItem = new ExpandableSwitchItem(); - mItem.setTitle("TestTitle"); - mItem.setCollapsedSummary("TestSummary"); - mItem.setExpandedSummary("TestSummaryExpanded"); - } - - @Test - public void testInitialState() { - View view = createLayout(); - mItem.onBindView(view); - - assertEquals("Collapsed summary should be TestSummary", - "TestSummary", mItem.getCollapsedSummary()); - assertEquals("Expanded summary should be TestSummaryExpanded", - "TestSummaryExpanded", mItem.getExpandedSummary()); - - assertEquals("Should be collapsed initially", "TestSummary", mItem.getSummary()); - assertEquals("Summary view should display collapsed summary", - "TestSummary", mSummaryView.getText()); - - assertFalse("Expandable switch item itself should not be focusable", view.isFocusable()); - - View switchContent = view.findViewById(R.id.suw_items_expandable_switch_content); - assertThat(switchContent).isInstanceOf(CheckableLinearLayout.class); - assertThat(switchContent.isFocusable()) - .named("expandable content focusable") - .isTrue(); - } - - @Test - public void testExpanded() { - View view = createLayout(); - mItem.onBindView(view); - - mItem.setExpanded(true); - - assertEquals("Collapsed summary should be TestSummary", - "TestSummary", mItem.getCollapsedSummary()); - assertEquals("Expanded summary should be TestSummaryExpanded", - "TestSummaryExpanded", mItem.getExpandedSummary()); - - assertTrue("Should be expanded", mItem.isExpanded()); - assertEquals("getSummary should be expanded summary", - "TestSummaryExpanded", mItem.getSummary()); - } - - @Test - public void testCollapsed() { - View view = createLayout(); - mItem.onBindView(view); - - mItem.setExpanded(true); - assertTrue("Should be expanded", mItem.isExpanded()); - - mItem.setExpanded(false); - - assertEquals("Collapsed summary should be TestSummary", - "TestSummary", mItem.getCollapsedSummary()); - assertEquals("Expanded summary should be TestSummaryExpanded", - "TestSummaryExpanded", mItem.getExpandedSummary()); - - assertFalse("Should be expanded", mItem.isExpanded()); - assertEquals("getSummary should be collapsed summary", - "TestSummary", mItem.getSummary()); - } - - @Test - public void testClick() { - View view = createLayout(); - mItem.onBindView(view); - - assertFalse("Should not be expanded initially", mItem.isExpanded()); - - final View content = view.findViewById(R.id.suw_items_expandable_switch_content); - content.performClick(); - assertTrue("Should be expanded after clicking", mItem.isExpanded()); - - content.performClick(); - assertFalse("Should be collapsed again after clicking", mItem.isExpanded()); - } - - @Test - public void testDrawableState() { - final View view = - LayoutInflater.from(application).inflate(mItem.getLayoutResource(), null); - mItem.onBindView(view); - - final View titleView = view.findViewById(R.id.suw_items_title); - assertThat(titleView.getDrawableState()).asList().named("Drawable state") - .doesNotContain(android.R.attr.state_checked); - - mItem.setExpanded(true); - mItem.onBindView(view); - assertThat(titleView.getDrawableState()).asList().named("Drawable state") - .contains(android.R.attr.state_checked); - - mItem.setExpanded(false); - mItem.onBindView(view); - assertThat(titleView.getDrawableState()).asList().named("Drawable state") - .doesNotContain(android.R.attr.state_checked); - } - - private ViewGroup createLayout() { - ViewGroup root = - (ViewGroup) LayoutInflater.from(application) - .inflate(R.layout.suw_items_expandable_switch, null); - mSummaryView = root.findViewById(R.id.suw_items_summary); - - return root; - } + private TextView mSummaryView; + private ExpandableSwitchItem mItem; + + @Before + public void setUp() { + mItem = new ExpandableSwitchItem(); + mItem.setTitle("TestTitle"); + mItem.setCollapsedSummary("TestSummary"); + mItem.setExpandedSummary("TestSummaryExpanded"); + } + + @Test + public void testInitialState() { + View view = createLayout(); + mItem.onBindView(view); + + assertEquals( + "Collapsed summary should be TestSummary", "TestSummary", mItem.getCollapsedSummary()); + assertEquals( + "Expanded summary should be TestSummaryExpanded", + "TestSummaryExpanded", + mItem.getExpandedSummary()); + + assertEquals("Should be collapsed initially", "TestSummary", mItem.getSummary()); + assertEquals( + "Summary view should display collapsed summary", "TestSummary", mSummaryView.getText()); + + assertFalse("Expandable switch item itself should not be focusable", view.isFocusable()); + + View switchContent = view.findViewById(R.id.suw_items_expandable_switch_content); + assertThat(switchContent).isInstanceOf(CheckableLinearLayout.class); + assertThat(switchContent.isFocusable()).named("expandable content focusable").isTrue(); + } + + @Test + public void testExpanded() { + View view = createLayout(); + mItem.onBindView(view); + + mItem.setExpanded(true); + + assertEquals( + "Collapsed summary should be TestSummary", "TestSummary", mItem.getCollapsedSummary()); + assertEquals( + "Expanded summary should be TestSummaryExpanded", + "TestSummaryExpanded", + mItem.getExpandedSummary()); + + assertTrue("Should be expanded", mItem.isExpanded()); + assertEquals( + "getSummary should be expanded summary", "TestSummaryExpanded", mItem.getSummary()); + } + + @Test + public void testCollapsed() { + View view = createLayout(); + mItem.onBindView(view); + + mItem.setExpanded(true); + assertTrue("Should be expanded", mItem.isExpanded()); + + mItem.setExpanded(false); + + assertEquals( + "Collapsed summary should be TestSummary", "TestSummary", mItem.getCollapsedSummary()); + assertEquals( + "Expanded summary should be TestSummaryExpanded", + "TestSummaryExpanded", + mItem.getExpandedSummary()); + + assertFalse("Should be expanded", mItem.isExpanded()); + assertEquals("getSummary should be collapsed summary", "TestSummary", mItem.getSummary()); + } + + @Test + public void testClick() { + View view = createLayout(); + mItem.onBindView(view); + + assertFalse("Should not be expanded initially", mItem.isExpanded()); + + final View content = view.findViewById(R.id.suw_items_expandable_switch_content); + content.performClick(); + assertTrue("Should be expanded after clicking", mItem.isExpanded()); + + content.performClick(); + assertFalse("Should be collapsed again after clicking", mItem.isExpanded()); + } + + @Test + public void testDrawableState() { + final View view = LayoutInflater.from(application).inflate(mItem.getLayoutResource(), null); + mItem.onBindView(view); + + final View titleView = view.findViewById(R.id.suw_items_title); + assertThat(titleView.getDrawableState()) + .asList() + .named("Drawable state") + .doesNotContain(android.R.attr.state_checked); + + mItem.setExpanded(true); + mItem.onBindView(view); + assertThat(titleView.getDrawableState()) + .asList() + .named("Drawable state") + .contains(android.R.attr.state_checked); + + mItem.setExpanded(false); + mItem.onBindView(view); + assertThat(titleView.getDrawableState()) + .asList() + .named("Drawable state") + .doesNotContain(android.R.attr.state_checked); + } + + private ViewGroup createLayout() { + ViewGroup root = + (ViewGroup) + LayoutInflater.from(application).inflate(R.layout.suw_items_expandable_switch, null); + mSummaryView = root.findViewById(R.id.suw_items_summary); + + return root; + } } diff --git a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java index 05d6e5b..422d979 100644 --- a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java +++ b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.items; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -26,219 +25,232 @@ import static org.robolectric.RuntimeEnvironment.application; import android.annotation.TargetApi; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import androidx.appcompat.widget.SwitchCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; - -import androidx.appcompat.widget.SwitchCompat; - import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class SwitchItemTest { - private SwitchCompat mSwitch; + private SwitchCompat mSwitch; - @Test - public void testLayout() { - Assume.assumeTrue(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP); - SwitchItem item = new SwitchItem(); - LayoutInflater inflater = LayoutInflater.from(application); - ViewGroup layout = (ViewGroup) inflater.inflate(item.getDefaultLayoutResource(), null); - assertThat(layout.getClipToPadding()).isFalse(); - } + @Test + public void defaultLayout_baselineAligned_shouldBeFalse() { + Assume.assumeTrue(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP); + LayoutInflater inflater = LayoutInflater.from(application); + SwitchItem item = new SwitchItem(); + LinearLayout layout = (LinearLayout) inflater.inflate(item.getDefaultLayoutResource(), null); + assertThat(layout.isBaselineAligned()).isFalse(); + } + + @Test + public void verboseLayout_clipPadding_shouldBeFalse() { + Assume.assumeTrue(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP); + LayoutInflater inflater = LayoutInflater.from(application); + SwitchItem item = + new SwitchItem( + application, + Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.layout, "@layout/suw_items_switch_verbose") + .build()); + ViewGroup layout = (ViewGroup) inflater.inflate(item.getLayoutResource(), null); + assertThat(layout.getClipToPadding()).isFalse(); + } - @Test - public void testChecked() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + @Test + public void testChecked() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - item.setChecked(true); + item.setChecked(true); - item.onBindView(view); + item.onBindView(view); - assertTrue("Switch should be checked", mSwitch.isChecked()); - } + assertTrue("Switch should be checked", mSwitch.isChecked()); + } - @Test - public void testNotChecked() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + @Test + public void testNotChecked() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - item.setChecked(false); + item.setChecked(false); - item.onBindView(view); + item.onBindView(view); - assertFalse("Switch should be unchecked", mSwitch.isChecked()); - } + assertFalse("Switch should be unchecked", mSwitch.isChecked()); + } - @Test - public void testListener() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + @Test + public void testListener() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - item.setChecked(true); + item.setChecked(true); - final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); - item.setOnCheckedChangeListener(listener); + final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); + item.setOnCheckedChangeListener(listener); - item.onBindView(view); + item.onBindView(view); - assertTrue("Switch should be checked", mSwitch.isChecked()); - mSwitch.setChecked(false); + assertTrue("Switch should be checked", mSwitch.isChecked()); + mSwitch.setChecked(false); - assertTrue("Listener should be called", listener.mCalled); - assertFalse("Listener should not be checked", listener.mChecked); + assertTrue("Listener should be called", listener.mCalled); + assertFalse("Listener should not be checked", listener.mChecked); - mSwitch.setChecked(true); + mSwitch.setChecked(true); - assertTrue("Listener should be called", listener.mCalled); - assertTrue("Listener should be checked", listener.mChecked); - } + assertTrue("Listener should be called", listener.mCalled); + assertTrue("Listener should be checked", listener.mChecked); + } - @Test - public void testRebind() { - SwitchItem item1 = new SwitchItem(); - item1.setTitle("TestTitle1"); - item1.setSummary("TestSummary1"); - item1.setChecked(false); + @Test + public void testRebind() { + SwitchItem item1 = new SwitchItem(); + item1.setTitle("TestTitle1"); + item1.setSummary("TestSummary1"); + item1.setChecked(false); - SwitchItem item2 = new SwitchItem(); - item2.setTitle("TestTitle2"); - item2.setSummary("TestSummary2"); - item2.setChecked(true); + SwitchItem item2 = new SwitchItem(); + item2.setTitle("TestTitle2"); + item2.setSummary("TestSummary2"); + item2.setChecked(true); - View view = createLayout(); + View view = createLayout(); - item1.onBindView(view); - item2.onBindView(view); + item1.onBindView(view); + item2.onBindView(view); - // Switch should be bound to item2, and therefore checked - assertTrue("Switch should be checked", mSwitch.isChecked()); + // Switch should be bound to item2, and therefore checked + assertTrue("Switch should be checked", mSwitch.isChecked()); - // Switching the switch to false should change the checked state of item 2 only - mSwitch.setChecked(false); - assertFalse("Item1 should still be unchecked", item1.isChecked()); - assertFalse("Item2 should not be checked", item2.isChecked()); + // Switching the switch to false should change the checked state of item 2 only + mSwitch.setChecked(false); + assertFalse("Item1 should still be unchecked", item1.isChecked()); + assertFalse("Item2 should not be checked", item2.isChecked()); - // Switching the switch to true should change the checked state of item 2 only - mSwitch.setChecked(true); - assertFalse("Item1 should still be unchecked", item1.isChecked()); - assertTrue("Item2 should be checked", item2.isChecked()); - } + // Switching the switch to true should change the checked state of item 2 only + mSwitch.setChecked(true); + assertFalse("Item1 should still be unchecked", item1.isChecked()); + assertTrue("Item2 should be checked", item2.isChecked()); + } - @Test - public void testListenerSetChecked() { - // Check that calling setChecked on the item will also call the listener. + @Test + public void testListenerSetChecked() { + // Check that calling setChecked on the item will also call the listener. - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - item.setChecked(true); + item.setChecked(true); - final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); - item.setOnCheckedChangeListener(listener); + final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); + item.setOnCheckedChangeListener(listener); - item.onBindView(view); + item.onBindView(view); - assertTrue("Switch should be checked", mSwitch.isChecked()); - item.setChecked(false); + assertTrue("Switch should be checked", mSwitch.isChecked()); + item.setChecked(false); - assertTrue("Listener should be called", listener.mCalled); - assertFalse("Listener should not be checked", listener.mChecked); + assertTrue("Listener should be called", listener.mCalled); + assertFalse("Listener should not be checked", listener.mChecked); - item.setChecked(true); + item.setChecked(true); - assertTrue("Listener should be called", listener.mCalled); - assertTrue("Listener should be checked", listener.mChecked); - } + assertTrue("Listener should be called", listener.mCalled); + assertTrue("Listener should be checked", listener.mChecked); + } - @Test - public void testToggle() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + @Test + public void testToggle() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - item.setChecked(true); - item.onBindView(view); + item.setChecked(true); + item.onBindView(view); - assertTrue("Switch should be checked", mSwitch.isChecked()); + assertTrue("Switch should be checked", mSwitch.isChecked()); - item.toggle(view); + item.toggle(view); - assertFalse("Switch should be unchecked", mSwitch.isChecked()); - } + assertFalse("Switch should be unchecked", mSwitch.isChecked()); + } - @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) - @Config(minSdk = VERSION_CODES.JELLY_BEAN_MR1) - @Test - public void testAccessibility() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + @Config(minSdk = VERSION_CODES.JELLY_BEAN_MR1) + @Test + public void testAccessibility() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); - View view = LayoutInflater.from(application).inflate(R.layout.suw_items_switch, null); - item.onBindView(view); + View view = LayoutInflater.from(application).inflate(R.layout.suw_items_switch, null); + item.onBindView(view); - final View titleView = view.findViewById(R.id.suw_items_title); - assertEquals("Title view should label for switch", - R.id.suw_items_switch, titleView.getLabelFor()); - } + final View titleView = view.findViewById(R.id.suw_items_title); + assertEquals( + "Title view should label for switch", R.id.suw_items_switch, titleView.getLabelFor()); + } - private ViewGroup createLayout() { - ViewGroup root = new FrameLayout(application); + private ViewGroup createLayout() { + ViewGroup root = new FrameLayout(application); - TextView titleView = new TextView(application); - titleView.setId(R.id.suw_items_title); - root.addView(titleView); + TextView titleView = new TextView(application); + titleView.setId(R.id.suw_items_title); + root.addView(titleView); - TextView summaryView = new TextView(application); - summaryView.setId(R.id.suw_items_summary); - root.addView(summaryView); + TextView summaryView = new TextView(application); + summaryView.setId(R.id.suw_items_summary); + root.addView(summaryView); - FrameLayout iconContainer = new FrameLayout(application); - iconContainer.setId(R.id.suw_items_icon_container); - root.addView(iconContainer); + FrameLayout iconContainer = new FrameLayout(application); + iconContainer.setId(R.id.suw_items_icon_container); + root.addView(iconContainer); - ImageView iconView = new ImageView(application); - iconView.setId(R.id.suw_items_icon); - iconContainer.addView(iconView); + ImageView iconView = new ImageView(application); + iconView.setId(R.id.suw_items_icon); + iconContainer.addView(iconView); - mSwitch = new SwitchCompat(application); - mSwitch.setId(R.id.suw_items_switch); - root.addView(mSwitch); + mSwitch = new SwitchCompat(application); + mSwitch.setId(R.id.suw_items_switch); + root.addView(mSwitch); - return root; - } + return root; + } - private static class TestOnCheckedChangeListener implements SwitchItem.OnCheckedChangeListener { + private static class TestOnCheckedChangeListener implements SwitchItem.OnCheckedChangeListener { - boolean mCalled = false; - boolean mChecked = false; + boolean mCalled = false; + boolean mChecked = false; - @Override - public void onCheckedChange(SwitchItem item, boolean isChecked) { - mCalled = true; - mChecked = isChecked; - } + @Override + public void onCheckedChange(SwitchItem item, boolean isChecked) { + mCalled = true; + mChecked = isChecked; } + } } diff --git a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/util/DimensionConsistencyTest.java b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/util/DimensionConsistencyTest.java index 7a08235..1f76d35 100644 --- a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/util/DimensionConsistencyTest.java +++ b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/util/DimensionConsistencyTest.java @@ -24,45 +24,42 @@ import android.content.res.Resources; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.ContextThemeWrapper; - import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(sdk = Config.ALL_SDKS) public class DimensionConsistencyTest { - // Visual height of the framework switch widget - private static final int SWTICH_HEIGHT_DP = 26; + // Visual height of the framework switch widget + private static final int SWTICH_HEIGHT_DP = 26; - private Context mContext; + private Context mContext; - @Before - public void setUp() { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - } + @Before + public void setUp() { + mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + } - @Test - public void testSwitchPaddingTop() { - final Resources res = mContext.getResources(); + @Test + public void testSwitchPaddingTop() { + final Resources res = mContext.getResources(); - assertEquals( - "Switch and divider should be aligned at center vertically: " - + "suw_switch_padding_top + SWITCH_HEIGHT / 2 = " - + "suw_switch_divider_padding_top + suw_switch_divider_height / 2", - res.getDimensionPixelSize(R.dimen.suw_switch_divider_padding_top) - + (res.getDimensionPixelSize(R.dimen.suw_switch_divider_height) / 2), - res.getDimensionPixelSize(R.dimen.suw_switch_padding_top) - + (dp2Px(SWTICH_HEIGHT_DP) / 2)); - } + assertEquals( + "Switch and divider should be aligned at center vertically: " + + "suw_switch_padding_top + SWITCH_HEIGHT / 2 = " + + "suw_switch_divider_padding_top + suw_switch_divider_height / 2", + res.getDimensionPixelSize(R.dimen.suw_switch_divider_padding_top) + + (res.getDimensionPixelSize(R.dimen.suw_switch_divider_height) / 2), + res.getDimensionPixelSize(R.dimen.suw_switch_padding_top) + (dp2Px(SWTICH_HEIGHT_DP) / 2)); + } - private int dp2Px(float dp) { - DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); - } + private int dp2Px(float dp) { + DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); + } } diff --git a/library/grandfathered_lint_checks.txt b/library/grandfathered_lint_checks.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/library/grandfathered_lint_checks.txt diff --git a/library/main/res/color-v23/suw_flat_button_highlight.xml b/library/main/res/color-v23/suw_flat_button_highlight.xml index c5be14f..cdb1305 100644 --- a/library/main/res/color-v23/suw_flat_button_highlight.xml +++ b/library/main/res/color-v23/suw_flat_button_highlight.xml @@ -17,5 +17,5 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="?android:attr/colorAccent" - android:alpha="0.24" /> + android:alpha="?attr/suwButtonHighlightAlpha" /> </selector> diff --git a/library/main/res/values-as/strings.xml b/library/main/res/values-as/strings.xml new file mode 100644 index 0000000..be6e06b --- /dev/null +++ b/library/main/res/values-as/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="suw_next_button_label" msgid="7269625133873553978">"পৰৱৰ্তী"</string> + <string name="suw_back_button_label" msgid="1460929053642711025">"উভতি যাওক"</string> + <string name="suw_more_button_label" msgid="7769076059705546563">"অধিক"</string> +</resources> diff --git a/library/main/res/values-hi/strings.xml b/library/main/res/values-hi/strings.xml index 3fb41d3..ec2cd77 100644 --- a/library/main/res/values-hi/strings.xml +++ b/library/main/res/values-hi/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="suw_next_button_label" msgid="7269625133873553978">"आगे बढ़ें"</string> <string name="suw_back_button_label" msgid="1460929053642711025">"पीछे"</string> - <string name="suw_more_button_label" msgid="7769076059705546563">"अधिक"</string> + <string name="suw_more_button_label" msgid="7769076059705546563">"ज़्यादा"</string> </resources> diff --git a/library/main/res/values-mr/strings.xml b/library/main/res/values-mr/strings.xml index a529655..5c5b6c2 100644 --- a/library/main/res/values-mr/strings.xml +++ b/library/main/res/values-mr/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="suw_next_button_label" msgid="7269625133873553978">"पुढील"</string> + <string name="suw_next_button_label" msgid="7269625133873553978">"पुढे जा"</string> <string name="suw_back_button_label" msgid="1460929053642711025">"मागे"</string> <string name="suw_more_button_label" msgid="7769076059705546563">"अधिक"</string> </resources> diff --git a/library/main/res/values-night/styles.xml b/library/main/res/values-night/styles.xml new file mode 100644 index 0000000..912e149 --- /dev/null +++ b/library/main/res/values-night/styles.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2018 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. +--> + +<resources> + + <!-- DayNight themes --> + <style name="SuwThemeMaterial.DayNight" parent="SuwThemeMaterial" /> + <style name="SuwThemeGlif.DayNight" parent="SuwThemeGlif" /> + <style name="SuwThemeGlifV2.DayNight" parent="SuwThemeGlifV2" /> + <style name="SuwThemeGlifV3.DayNight" parent="SuwThemeGlifV3" /> + +</resources> diff --git a/library/main/res/values-or/strings.xml b/library/main/res/values-or/strings.xml new file mode 100644 index 0000000..c4d12ff --- /dev/null +++ b/library/main/res/values-or/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="suw_next_button_label" msgid="7269625133873553978">"ପରବର୍ତ୍ତୀ"</string> + <string name="suw_back_button_label" msgid="1460929053642711025">"ପଛକୁ ଫେରନ୍ତୁ"</string> + <string name="suw_more_button_label" msgid="7769076059705546563">"ଅଧିକ"</string> +</resources> diff --git a/library/main/res/values-v21/styles.xml b/library/main/res/values-v21/styles.xml index d2c27f6..fe71289 100644 --- a/library/main/res/values-v21/styles.xml +++ b/library/main/res/values-v21/styles.xml @@ -38,10 +38,6 @@ <!-- Button styles --> - <style name="SuwGlifButton.Tertiary" parent="SuwGlifButton.BaseTertiary"> - <item name="android:fontFamily">sans-serif-medium</item> - </style> - <style name="SuwBase.ProgressBarLarge" parent="@android:style/Widget.Material.ProgressBar.Large" /> <style name="SuwFourColorIndeterminateProgressBar" parent="SuwBase.ProgressBarLarge"> @@ -50,6 +46,8 @@ <item name="android:indeterminateDrawable">@drawable/suw_fourcolor_progress_bar</item> <item name="android:indeterminateTint">@null</item> <item name="android:indeterminateTintMode">@null</item> + <item name="android:paddingStart">@dimen/suw_glif_progress_bar_padding</item> + <item name="android:paddingEnd">@dimen/suw_glif_progress_bar_padding</item> </style> <!-- Items styles --> diff --git a/library/main/res/values/attrs.xml b/library/main/res/values/attrs.xml index b3fcfe9..86b27fa 100644 --- a/library/main/res/values/attrs.xml +++ b/library/main/res/values/attrs.xml @@ -21,6 +21,7 @@ <attr name="suwLayoutTheme" format="reference" /> <attr name="suwMarginSides" format="dimension|reference" /> <attr name="suwEditTextBackgroundColor" format="color" /> + <attr name="suwButtonHighlightAlpha" format="float" /> <!-- Subset of values in "gravity" in frameworks/base/core/res/res/values/attrs.xml. Only horizontal values are listed here as the header does not support vertical gravity. --> diff --git a/library/main/res/values/dimens.xml b/library/main/res/values/dimens.xml index 1a8b516..63980ab 100644 --- a/library/main/res/values/dimens.xml +++ b/library/main/res/values/dimens.xml @@ -31,7 +31,7 @@ <dimen name="suw_glif_footer_padding_vertical">8dp</dimen> <dimen name="suw_glif_footer_min_height">72dp</dimen> <dimen name="suw_glif_margin_sides">24dp</dimen> - <dimen name="suw_glif_margin_top">48dp</dimen> + <dimen name="suw_glif_margin_top">56dp</dimen> <dimen name="suw_glif_v3_button_corner_radius">4dp</dimen> @@ -98,7 +98,7 @@ <!-- This is the extra spacing required to make the leading exactly 32sp --> <dimen name="suw_header_title_line_spacing_extra">3.67sp</dimen> - <dimen name="suw_glif_header_title_margin_top">15dp</dimen> + <dimen name="suw_glif_header_title_margin_top">16dp</dimen> <dimen name="suw_glif_header_title_margin_bottom">2dp</dimen> <dimen name="suw_glif_icon_max_height">32dp</dimen> @@ -139,6 +139,7 @@ <!-- The margin to compensate for the padding built-in to the widget itself --> <dimen name="suw_progress_bar_margin_vertical">-7dp</dimen> <dimen name="suw_glif_progress_bar_margin_vertical">7dp</dimen> + <dimen name="suw_glif_progress_bar_padding">40dp</dimen> <!-- Edit Text dimensions --> <dimen name="suw_edit_text_min_height">56dp</dimen> diff --git a/library/main/res/values/styles.xml b/library/main/res/values/styles.xml index fa2a080..34a917f 100644 --- a/library/main/res/values/styles.xml +++ b/library/main/res/values/styles.xml @@ -58,6 +58,12 @@ <item name="android:activityCloseExitAnimation">@anim/suw_slide_back_out</item> </style> + <!-- DayNight themes --> + <style name="SuwThemeMaterial.DayNight" parent="SuwThemeMaterial.Light" /> + <style name="SuwThemeGlif.DayNight" parent="SuwThemeGlif.Light" /> + <style name="SuwThemeGlifV2.DayNight" parent="SuwThemeGlifV2.Light" /> + <style name="SuwThemeGlifV3.DayNight" parent="SuwThemeGlifV3.Light" /> + <!-- Content styles --> <!-- Ignore UnusedResources: Used by clients --> @@ -166,7 +172,7 @@ <!-- Before Honeycomb, layout_gravity is needed for FrameLayout to apply the margins --> <item name="android:layout_gravity">top</item> <item name="android:ellipsize">end</item> - <item name="android:maxLines">2</item> + <item name="android:maxLines">3</item> <item name="android:textSize">@dimen/suw_header_title_size</item> </style> @@ -204,11 +210,6 @@ <item name="android:textAllCaps" tools:targetApi="ice_cream_sandwich">false</item> </style> - <!-- Ignore UnusedResources: used by clients --> - <style name="SuwGlifButton.Tertiary" - parent="SuwGlifButton.BaseTertiary" - tools:ignore="UnusedResources" /> - <!-- The start and end paddings are asymmetric because start buttons are borderless buttons which aligns the text label. --> <style name="SuwGlifButtonBar"> diff --git a/library/main/src/com/android/setupwizardlib/GlifLayout.java b/library/main/src/com/android/setupwizardlib/GlifLayout.java index 9b30c2f..20fb6b6 100644 --- a/library/main/src/com/android/setupwizardlib/GlifLayout.java +++ b/library/main/src/com/android/setupwizardlib/GlifLayout.java @@ -24,6 +24,9 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION_CODES; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -32,11 +35,6 @@ import android.view.ViewStub; import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; - -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.setupwizardlib.template.ButtonFooterMixin; import com.android.setupwizardlib.template.ColoredHeaderMixin; import com.android.setupwizardlib.template.HeaderMixin; @@ -50,6 +48,7 @@ import com.android.setupwizardlib.view.StatusBarBackgroundLayout; * Layout for the GLIF theme used in Setup Wizard for N. * * <p>Example usage: + * * <pre>{@code * <com.android.setupwizardlib.GlifLayout * xmlns:android="http://schemas.android.com/apk/res/android" @@ -66,259 +65,254 @@ import com.android.setupwizardlib.view.StatusBarBackgroundLayout; */ public class GlifLayout extends TemplateLayout { - private static final String TAG = "GlifLayout"; - - private ColorStateList mPrimaryColor; - - private boolean mBackgroundPatterned = true; - - /** - * The color of the background. If null, the color will inherit from mPrimaryColor. - */ - @Nullable - private ColorStateList mBackgroundBaseColor; - - private boolean mLayoutFullscreen = true; - - 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); - } + private static final String TAG = "GlifLayout"; - public GlifLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs, R.attr.suwLayoutTheme); - } + private ColorStateList primaryColor; - @TargetApi(VERSION_CODES.HONEYCOMB) - public GlifLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs, defStyleAttr); - } + private boolean backgroundPatterned = true; - // 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) { - registerMixin(HeaderMixin.class, new ColoredHeaderMixin(this, attrs, defStyleAttr)); - registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr)); - registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this)); - registerMixin(ButtonFooterMixin.class, new ButtonFooterMixin(this)); - final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this); - registerMixin(RequireScrollMixin.class, requireScrollMixin); - - final ScrollView scrollView = getScrollView(); - if (scrollView != null) { - requireScrollMixin.setScrollHandlingDelegate( - new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView)); - } - - TypedArray a = getContext().obtainStyledAttributes(attrs, - R.styleable.SuwGlifLayout, defStyleAttr, 0); - - ColorStateList primaryColor = - a.getColorStateList(R.styleable.SuwGlifLayout_suwColorPrimary); - if (primaryColor != null) { - setPrimaryColor(primaryColor); - } - - ColorStateList backgroundColor = - a.getColorStateList(R.styleable.SuwGlifLayout_suwBackgroundBaseColor); - setBackgroundBaseColor(backgroundColor); - - boolean backgroundPatterned = - a.getBoolean(R.styleable.SuwGlifLayout_suwBackgroundPatterned, true); - setBackgroundPatterned(backgroundPatterned); - - final int footer = a.getResourceId(R.styleable.SuwGlifLayout_suwFooter, 0); - if (footer != 0) { - inflateFooter(footer); - } - - final int stickyHeader = a.getResourceId(R.styleable.SuwGlifLayout_suwStickyHeader, 0); - if (stickyHeader != 0) { - inflateStickyHeader(stickyHeader); - } - - mLayoutFullscreen = a.getBoolean(R.styleable.SuwGlifLayout_suwLayoutFullscreen, true); - - a.recycle(); - - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && mLayoutFullscreen) { - setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - } + /** The color of the background. If null, the color will inherit from primaryColor. */ + @Nullable private ColorStateList backgroundBaseColor; - @Override - protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) { - if (template == 0) { - template = R.layout.suw_glif_template; - } - return inflateTemplate(inflater, R.style.SuwThemeGlif_Light, template); - } + private boolean layoutFullscreen = true; - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_layout_content; - } - return super.findContainer(containerId); - } + public GlifLayout(Context context) { + this(context, 0, 0); + } - /** - * Sets the footer of the layout, which is at the bottom of the content area outside the - * scrolling container. The footer can only be inflated once per instance of this layout. - * - * @param footer The layout to be inflated as footer. - * @return The root of the inflated footer view. - */ - public View inflateFooter(@LayoutRes int footer) { - ViewStub footerStub = findManagedViewById(R.id.suw_layout_footer); - footerStub.setLayoutResource(footer); - return footerStub.inflate(); - } + public GlifLayout(Context context, int template) { + this(context, template, 0); + } - /** - * Sets the sticky header (i.e. header that doesn't scroll) of the layout, which is at the top - * of the content area outside of the scrolling container. The header can only be inflated once - * per instance of this layout. - * - * @param header The layout to be inflated as the header. - * @return The root of the inflated header view. - */ - public View inflateStickyHeader(@LayoutRes int header) { - ViewStub stickyHeaderStub = findManagedViewById(R.id.suw_layout_sticky_header); - stickyHeaderStub.setLayoutResource(header); - return stickyHeaderStub.inflate(); - } + public GlifLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, R.attr.suwLayoutTheme); + } - public ScrollView getScrollView() { - final View view = findManagedViewById(R.id.suw_scroll_view); - return view instanceof ScrollView ? (ScrollView) view : null; - } + public GlifLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, R.attr.suwLayoutTheme); + } - public TextView getHeaderTextView() { - return getMixin(HeaderMixin.class).getTextView(); - } + @TargetApi(VERSION_CODES.HONEYCOMB) + public GlifLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } - public void setHeaderText(int title) { - getMixin(HeaderMixin.class).setText(title); - } + // 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) { + registerMixin(HeaderMixin.class, new ColoredHeaderMixin(this, attrs, defStyleAttr)); + registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr)); + registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this)); + registerMixin(ButtonFooterMixin.class, new ButtonFooterMixin(this)); + final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this); + registerMixin(RequireScrollMixin.class, requireScrollMixin); - public void setHeaderText(CharSequence title) { - getMixin(HeaderMixin.class).setText(title); + final ScrollView scrollView = getScrollView(); + if (scrollView != null) { + requireScrollMixin.setScrollHandlingDelegate( + new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView)); } - public CharSequence getHeaderText() { - return getMixin(HeaderMixin.class).getText(); - } + TypedArray a = + getContext().obtainStyledAttributes(attrs, R.styleable.SuwGlifLayout, defStyleAttr, 0); - public void setHeaderColor(ColorStateList color) { - final ColoredHeaderMixin mixin = (ColoredHeaderMixin) getMixin(HeaderMixin.class); - mixin.setColor(color); + ColorStateList primaryColor = a.getColorStateList(R.styleable.SuwGlifLayout_suwColorPrimary); + if (primaryColor != null) { + setPrimaryColor(primaryColor); } - public ColorStateList getHeaderColor() { - final ColoredHeaderMixin mixin = (ColoredHeaderMixin) getMixin(HeaderMixin.class); - return mixin.getColor(); - } + ColorStateList backgroundColor = + a.getColorStateList(R.styleable.SuwGlifLayout_suwBackgroundBaseColor); + setBackgroundBaseColor(backgroundColor); - public void setIcon(Drawable icon) { - getMixin(IconMixin.class).setIcon(icon); - } + boolean backgroundPatterned = + a.getBoolean(R.styleable.SuwGlifLayout_suwBackgroundPatterned, true); + setBackgroundPatterned(backgroundPatterned); - public Drawable getIcon() { - return getMixin(IconMixin.class).getIcon(); + final int footer = a.getResourceId(R.styleable.SuwGlifLayout_suwFooter, 0); + if (footer != 0) { + inflateFooter(footer); } - /** - * Sets the primary color of this layout, which will be used to determine the color of the - * progress bar and the background pattern. - */ - public void setPrimaryColor(@NonNull ColorStateList color) { - mPrimaryColor = color; - updateBackground(); - getMixin(ProgressBarMixin.class).setColor(color); + final int stickyHeader = a.getResourceId(R.styleable.SuwGlifLayout_suwStickyHeader, 0); + if (stickyHeader != 0) { + inflateStickyHeader(stickyHeader); } - public ColorStateList getPrimaryColor() { - return mPrimaryColor; - } + layoutFullscreen = a.getBoolean(R.styleable.SuwGlifLayout_suwLayoutFullscreen, true); - /** - * Sets the base color of the background view, which is the status bar for phones and the full- - * screen background for tablets. If {@link #isBackgroundPatterned()} is true, the pattern will - * be drawn with this color. - * - * @param color The color to use as the base color of the background. If {@code null}, - * {@link #getPrimaryColor()} will be used. - */ - public void setBackgroundBaseColor(@Nullable ColorStateList color) { - mBackgroundBaseColor = color; - updateBackground(); - } + a.recycle(); - /** - * @return The base color of the background. {@code null} indicates the background will be drawn - * with {@link #getPrimaryColor()}. - */ - @Nullable - public ColorStateList getBackgroundBaseColor() { - return mBackgroundBaseColor; + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) { + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } + } - /** - * Sets whether the background should be {@link GlifPatternDrawable}. If {@code false}, the - * background will be a solid color. - */ - public void setBackgroundPatterned(boolean patterned) { - mBackgroundPatterned = patterned; - updateBackground(); + @Override + protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) { + if (template == 0) { + template = R.layout.suw_glif_template; } + return inflateTemplate(inflater, R.style.SuwThemeGlif_Light, template); + } - /** - * @return True if this view uses {@link GlifPatternDrawable} as background. - */ - public boolean isBackgroundPatterned() { - return mBackgroundPatterned; + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_layout_content; } - - private void updateBackground() { - final View patternBg = findManagedViewById(R.id.suw_pattern_bg); - if (patternBg != null) { - int backgroundColor = 0; - if (mBackgroundBaseColor != null) { - backgroundColor = mBackgroundBaseColor.getDefaultColor(); - } else if (mPrimaryColor != null) { - backgroundColor = mPrimaryColor.getDefaultColor(); - } - Drawable background = mBackgroundPatterned - ? new GlifPatternDrawable(backgroundColor) - : new ColorDrawable(backgroundColor); - if (patternBg instanceof StatusBarBackgroundLayout) { - ((StatusBarBackgroundLayout) patternBg).setStatusBarBackground(background); - } else { - patternBg.setBackgroundDrawable(background); - } - } + return super.findContainer(containerId); + } + + /** + * Sets the footer of the layout, which is at the bottom of the content area outside the scrolling + * container. The footer can only be inflated once per instance of this layout. + * + * @param footer The layout to be inflated as footer. + * @return The root of the inflated footer view. + */ + public View inflateFooter(@LayoutRes int footer) { + ViewStub footerStub = findManagedViewById(R.id.suw_layout_footer); + footerStub.setLayoutResource(footer); + return footerStub.inflate(); + } + + /** + * Sets the sticky header (i.e. header that doesn't scroll) of the layout, which is at the top of + * the content area outside of the scrolling container. The header can only be inflated once per + * instance of this layout. + * + * @param header The layout to be inflated as the header. + * @return The root of the inflated header view. + */ + public View inflateStickyHeader(@LayoutRes int header) { + ViewStub stickyHeaderStub = findManagedViewById(R.id.suw_layout_sticky_header); + stickyHeaderStub.setLayoutResource(header); + return stickyHeaderStub.inflate(); + } + + public ScrollView getScrollView() { + final View view = findManagedViewById(R.id.suw_scroll_view); + return view instanceof ScrollView ? (ScrollView) view : null; + } + + public TextView getHeaderTextView() { + return getMixin(HeaderMixin.class).getTextView(); + } + + public void setHeaderText(int title) { + getMixin(HeaderMixin.class).setText(title); + } + + public void setHeaderText(CharSequence title) { + getMixin(HeaderMixin.class).setText(title); + } + + public CharSequence getHeaderText() { + return getMixin(HeaderMixin.class).getText(); + } + + public void setHeaderColor(ColorStateList color) { + final ColoredHeaderMixin mixin = (ColoredHeaderMixin) getMixin(HeaderMixin.class); + mixin.setColor(color); + } + + public ColorStateList getHeaderColor() { + final ColoredHeaderMixin mixin = (ColoredHeaderMixin) getMixin(HeaderMixin.class); + return mixin.getColor(); + } + + public void setIcon(Drawable icon) { + getMixin(IconMixin.class).setIcon(icon); + } + + public Drawable getIcon() { + return getMixin(IconMixin.class).getIcon(); + } + + /** + * Sets the primary color of this layout, which will be used to determine the color of the + * progress bar and the background pattern. + */ + public void setPrimaryColor(@NonNull ColorStateList color) { + primaryColor = color; + updateBackground(); + getMixin(ProgressBarMixin.class).setColor(color); + } + + public ColorStateList getPrimaryColor() { + return primaryColor; + } + + /** + * Sets the base color of the background view, which is the status bar for phones and the full- + * screen background for tablets. If {@link #isBackgroundPatterned()} is true, the pattern will be + * drawn with this color. + * + * @param color The color to use as the base color of the background. If {@code null}, {@link + * #getPrimaryColor()} will be used. + */ + public void setBackgroundBaseColor(@Nullable ColorStateList color) { + backgroundBaseColor = color; + updateBackground(); + } + + /** + * @return The base color of the background. {@code null} indicates the background will be drawn + * with {@link #getPrimaryColor()}. + */ + @Nullable + public ColorStateList getBackgroundBaseColor() { + return backgroundBaseColor; + } + + /** + * Sets whether the background should be {@link GlifPatternDrawable}. If {@code false}, the + * background will be a solid color. + */ + public void setBackgroundPatterned(boolean patterned) { + backgroundPatterned = patterned; + updateBackground(); + } + + /** @return True if this view uses {@link GlifPatternDrawable} as background. */ + public boolean isBackgroundPatterned() { + return backgroundPatterned; + } + + private void updateBackground() { + final View patternBg = findManagedViewById(R.id.suw_pattern_bg); + if (patternBg != null) { + int backgroundColor = 0; + if (backgroundBaseColor != null) { + backgroundColor = backgroundBaseColor.getDefaultColor(); + } else if (primaryColor != null) { + backgroundColor = primaryColor.getDefaultColor(); + } + Drawable background = + backgroundPatterned + ? new GlifPatternDrawable(backgroundColor) + : new ColorDrawable(backgroundColor); + if (patternBg instanceof StatusBarBackgroundLayout) { + ((StatusBarBackgroundLayout) patternBg).setStatusBarBackground(background); + } else { + patternBg.setBackgroundDrawable(background); + } } + } - public boolean isProgressBarShown() { - return getMixin(ProgressBarMixin.class).isShown(); - } + public boolean isProgressBarShown() { + return getMixin(ProgressBarMixin.class).isShown(); + } - public void setProgressBarShown(boolean shown) { - getMixin(ProgressBarMixin.class).setShown(shown); - } + public void setProgressBarShown(boolean shown) { + getMixin(ProgressBarMixin.class).setShown(shown); + } - public ProgressBar peekProgressBar() { - return getMixin(ProgressBarMixin.class).peekProgressBar(); - } + public ProgressBar peekProgressBar() { + return getMixin(ProgressBarMixin.class).peekProgressBar(); + } } diff --git a/library/main/src/com/android/setupwizardlib/GlifListLayout.java b/library/main/src/com/android/setupwizardlib/GlifListLayout.java index 8266e5f..c4f66fa 100644 --- a/library/main/src/com/android/setupwizardlib/GlifListLayout.java +++ b/library/main/src/com/android/setupwizardlib/GlifListLayout.java @@ -26,7 +26,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ListAdapter; import android.widget.ListView; - import com.android.setupwizardlib.template.ListMixin; import com.android.setupwizardlib.template.ListViewScrollHandlingDelegate; import com.android.setupwizardlib.template.RequireScrollMixin; @@ -37,130 +36,113 @@ import com.android.setupwizardlib.template.RequireScrollMixin; */ public class GlifListLayout extends GlifLayout { - /* static section */ - - private static final String TAG = "GlifListLayout"; - - /* non-static section */ - - private ListMixin mListMixin; - - 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) { - mListMixin = new ListMixin(this, attrs, defStyleAttr); - registerMixin(ListMixin.class, mListMixin); - - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - requireScrollMixin.setScrollHandlingDelegate( - new ListViewScrollHandlingDelegate(requireScrollMixin, getListView())); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mListMixin.onLayout(); - } - - @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); - } - - public ListView getListView() { - return mListMixin.getListView(); - } - - public void setAdapter(ListAdapter adapter) { - mListMixin.setAdapter(adapter); - } - - public ListAdapter getAdapter() { - return mListMixin.getAdapter(); - } - - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - mListMixin.setDividerInset(inset); - } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start 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}. - * @param end The number of pixels to inset on the "end" side of the list divider. - * - * @see ListMixin#setDividerInsets(int, int) - */ - public void setDividerInsets(int start, int end) { - mListMixin.setDividerInsets(start, end); - } - - /** - * @deprecated Use {@link #getDividerInsetStart()} instead. - */ - @Deprecated - public int getDividerInset() { - return mListMixin.getDividerInset(); - } - - /** - * @see ListMixin#getDividerInsetStart() - */ - public int getDividerInsetStart() { - return mListMixin.getDividerInsetStart(); - } - - /** - * @see ListMixin#getDividerInsetEnd() - */ - public int getDividerInsetEnd() { - return mListMixin.getDividerInsetEnd(); - } - - /** - * @see ListMixin#getDivider() - */ - public Drawable getDivider() { - return mListMixin.getDivider(); - } + private ListMixin listMixin; + + 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(null, 0); + } + + public GlifListLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public GlifListLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + listMixin = new ListMixin(this, attrs, defStyleAttr); + registerMixin(ListMixin.class, listMixin); + + final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); + requireScrollMixin.setScrollHandlingDelegate( + new ListViewScrollHandlingDelegate(requireScrollMixin, getListView())); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + listMixin.onLayout(); + } + + @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); + } + + public ListView getListView() { + return listMixin.getListView(); + } + + public void setAdapter(ListAdapter adapter) { + listMixin.setAdapter(adapter); + } + + public ListAdapter getAdapter() { + return listMixin.getAdapter(); + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + listMixin.setDividerInset(inset); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start 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}. + * @param end The number of pixels to inset on the "end" side of the list divider. + * @see ListMixin#setDividerInsets(int, int) + */ + public void setDividerInsets(int start, int end) { + listMixin.setDividerInsets(start, end); + } + + /** @deprecated Use {@link #getDividerInsetStart()} instead. */ + @Deprecated + public int getDividerInset() { + return listMixin.getDividerInset(); + } + + /** @see ListMixin#getDividerInsetStart() */ + public int getDividerInsetStart() { + return listMixin.getDividerInsetStart(); + } + + /** @see ListMixin#getDividerInsetEnd() */ + public int getDividerInsetEnd() { + return listMixin.getDividerInsetEnd(); + } + + /** @see ListMixin#getDivider() */ + public Drawable getDivider() { + return listMixin.getDivider(); + } } diff --git a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java index caf92ac..f65f3ec 100644 --- a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java +++ b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java @@ -31,10 +31,8 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; - import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; - import java.lang.ref.SoftReference; /** @@ -42,263 +40,258 @@ import java.lang.ref.SoftReference; * 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<Bitmap> 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); - - public GlifPatternDrawable(int color) { - setColor(color); + /* + * 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 the width. (Range is 0 - 1) + private static final float SCALE_FOCUS_X = .146f; + // Y coordinate of scale focus, as a fraction 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<Bitmap> bitmapCache; + private static Path[] patternPaths; + private static int[] patternLightness; + + 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(); } - - @Override - public void draw(@NonNull 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); - canvas.drawColor(Color.BLACK); - mTempPaint.setColor(Color.WHITE); - canvas.drawBitmap(bitmap, 0, 0, mTempPaint); - canvas.drawColor(mColor); - - canvas.restore(); + return new GlifPatternDrawable(colorPrimary); + } + + @VisibleForTesting + public static void invalidatePattern() { + bitmapCache = null; + } + + /* non-static section */ + + private int color; + private final Paint tempPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + public GlifPatternDrawable(int color) { + setColor(color); + } + + @Override + public void draw(@NonNull Canvas canvas) { + final Rect bounds = getBounds(); + int drawableWidth = bounds.width(); + int drawableHeight = bounds.height(); + Bitmap bitmap = null; + if (bitmapCache != null) { + bitmap = bitmapCache.get(); } - - @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; + 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; + } } - 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.8571f); - 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(); - } + if (bitmap == null) { + // Reset the paint so it can be used to draw the paths in renderOnCanvas + tempPaint.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 - } + bitmap = createBitmapCache(drawableWidth, drawableHeight); + bitmapCache = new SoftReference<>(bitmap); - @Override - public void setColorFilter(ColorFilter colorFilter) { - // Ignore + // Reset the paint to so it can be used to draw the bitmap + tempPaint.reset(); } - @Override - public int getOpacity() { - return PixelFormat.UNKNOWN; + canvas.save(); + canvas.clipRect(bounds); + + scaleCanvasToBounds(canvas, bitmap, bounds); + canvas.drawColor(Color.BLACK); + tempPaint.setColor(Color.WHITE); + canvas.drawBitmap(bitmap, 0, 0, tempPaint); + canvas.drawColor(color); + + 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); + + tempPaint.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 (patternPaths == null) { + patternPaths = new Path[NUM_PATHS]; + // Lightness values of the pattern, range 0 - 255 + patternLightness = new int[] {10, 40, 51, 66, 91, 112, 130}; + + Path p = patternPaths[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 = patternPaths[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 = patternPaths[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 = patternPaths[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 = patternPaths[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 = patternPaths[5] = new Path(); + p.moveTo(178.44286f, 766.8571f); + 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 = patternPaths[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(); } - /** - * 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); - invalidateSelf(); + 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. + tempPaint.setColor(patternLightness[i] << 24); + canvas.drawPath(patternPaths[i], tempPaint); } - /** - * @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)); + canvas.restore(); + tempPaint.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 PixelFormat.UNKNOWN; + } + + /** + * 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); + this.color = Color.argb(COLOR_ALPHA_INT, r, g, b); + 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(color), Color.green(color), Color.blue(color)); + } } diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardItemsLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardItemsLayout.java index d520873..6197c4c 100644 --- a/library/main/src/com/android/setupwizardlib/SetupWizardItemsLayout.java +++ b/library/main/src/com/android/setupwizardlib/SetupWizardItemsLayout.java @@ -17,34 +17,30 @@ package com.android.setupwizardlib; import android.content.Context; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.widget.ListAdapter; - -import androidx.annotation.Nullable; - import com.android.setupwizardlib.items.ItemAdapter; -/** - * @deprecated Use {@link SetupWizardListLayout} instead. - */ +/** @deprecated Use {@link SetupWizardListLayout} instead. */ @Deprecated public class SetupWizardItemsLayout extends SetupWizardListLayout { - public SetupWizardItemsLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } + public SetupWizardItemsLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } - public SetupWizardItemsLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public SetupWizardItemsLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @Override - @Nullable - public ItemAdapter getAdapter() { - final ListAdapter adapter = super.getAdapter(); - if (adapter instanceof ItemAdapter) { - return (ItemAdapter) adapter; - } - return null; + @Override + @Nullable + public ItemAdapter getAdapter() { + final ListAdapter adapter = super.getAdapter(); + if (adapter instanceof ItemAdapter) { + return (ItemAdapter) adapter; } + return null; + } } diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java index 065d2ef..792d102 100644 --- a/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java +++ b/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java @@ -38,7 +38,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ScrollView; import android.widget.TextView; - import com.android.setupwizardlib.template.HeaderMixin; import com.android.setupwizardlib.template.NavigationBarMixin; import com.android.setupwizardlib.template.ProgressBarMixin; @@ -49,385 +48,377 @@ import com.android.setupwizardlib.view.NavigationBar; public class SetupWizardLayout extends TemplateLayout { - private static final String TAG = "SetupWizardLayout"; - - 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) { - registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr)); - registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this)); - registerMixin(NavigationBarMixin.class, new NavigationBarMixin(this)); - final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this); - registerMixin(RequireScrollMixin.class, requireScrollMixin); - - final ScrollView scrollView = getScrollView(); - if (scrollView != null) { - requireScrollMixin.setScrollHandlingDelegate( - new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView)); - } - - 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 suwDecorPaddingTop 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); - - a.recycle(); - } - - @Override - protected Parcelable onSaveInstanceState() { - final Parcelable parcelable = super.onSaveInstanceState(); - final SavedState ss = new SavedState(parcelable); - ss.mIsProgressBarShown = isProgressBarShown(); - return ss; + private static final String TAG = "SetupWizardLayout"; + + 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) { + registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr)); + registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this)); + registerMixin(NavigationBarMixin.class, new NavigationBarMixin(this)); + final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this); + registerMixin(RequireScrollMixin.class, requireScrollMixin); + + final ScrollView scrollView = getScrollView(); + if (scrollView != null) { + requireScrollMixin.setScrollHandlingDelegate( + new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView)); + } + + 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 suwDecorPaddingTop 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); + + a.recycle(); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable parcelable = super.onSaveInstanceState(); + final SavedState ss = new SavedState(parcelable); + ss.isProgressBarShown = 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.isProgressBarShown; + setProgressBarShown(isProgressBarShown); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_template; + } + return inflateTemplate(inflater, R.style.SuwThemeMaterial_Light, template); + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_layout_content; + } + return super.findContainer(containerId); + } + + public NavigationBar getNavigationBar() { + return getMixin(NavigationBarMixin.class).getNavigationBar(); + } + + public ScrollView getScrollView() { + final View view = findManagedViewById(R.id.suw_bottom_scroll_view); + return view instanceof ScrollView ? (ScrollView) view : null; + } + + public void requireScrollToBottom() { + final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); + final NavigationBar navigationBar = getNavigationBar(); + if (navigationBar != null) { + requireScrollMixin.requireScrollWithNavigationBar(navigationBar); + } else { + Log.e(TAG, "Cannot require scroll. Navigation bar is null."); + } + } + + public void setHeaderText(int title) { + getMixin(HeaderMixin.class).setText(title); + } + + public void setHeaderText(CharSequence title) { + getMixin(HeaderMixin.class).setText(title); + } + + public CharSequence getHeaderText() { + return getMixin(HeaderMixin.class).getText(); + } + + public TextView getHeaderTextView() { + return getMixin(HeaderMixin.class).getTextView(); + } + + /** + * 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. + * + * <p>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; + } + } + + public boolean isProgressBarShown() { + return getMixin(ProgressBarMixin.class).isShown(); + } + + /** + * 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) { + getMixin(ProgressBarMixin.class).setShown(shown); + } + + /** @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) { + getMixin(ProgressBarMixin.class).setColor(color); + } + + public ColorStateList getProgressBarColor() { + return getMixin(ProgressBarMixin.class).getColor(); + } + + /* Misc */ + + protected static class SavedState extends BaseSavedState { + + boolean isProgressBarShown = false; + + public SavedState(Parcelable parcelable) { + super(parcelable); + } + + public SavedState(Parcel source) { + super(source); + isProgressBarShown = source.readInt() != 0; } @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; - setProgressBarShown(isProgressBarShown); - } - - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_template; - } - return inflateTemplate(inflater, R.style.SuwThemeMaterial_Light, template); - } - - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_layout_content; - } - return super.findContainer(containerId); - } - - public NavigationBar getNavigationBar() { - return getMixin(NavigationBarMixin.class).getNavigationBar(); - } - - public ScrollView getScrollView() { - final View view = findManagedViewById(R.id.suw_bottom_scroll_view); - return view instanceof ScrollView ? (ScrollView) view : null; - } - - public void requireScrollToBottom() { - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - final NavigationBar navigationBar = getNavigationBar(); - if (navigationBar != null) { - requireScrollMixin.requireScrollWithNavigationBar(navigationBar); - } else { - Log.e(TAG, "Cannot require scroll. Navigation bar is null."); - } - } - - public void setHeaderText(int title) { - getMixin(HeaderMixin.class).setText(title); - } - - public void setHeaderText(CharSequence title) { - getMixin(HeaderMixin.class).setText(title); - } - - public CharSequence getHeaderText() { - return getMixin(HeaderMixin.class).getText(); - } - - public TextView getHeaderTextView() { - return getMixin(HeaderMixin.class).getTextView(); - } - - /** - * 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); - } + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(isProgressBarShown ? 1 : 0); } - /** - * 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); - } - } + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { - /** - * 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. - * - * <p>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); - } + @Override + public SavedState createFromParcel(Parcel parcel) { + return new SavedState(parcel); + } - 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; - } - } - - public boolean isProgressBarShown() { - return getMixin(ProgressBarMixin.class).isShown(); - } - - /** - * 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) { - getMixin(ProgressBarMixin.class).setShown(shown); - } - - /** - * @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) { - getMixin(ProgressBarMixin.class).setColor(color); - } - - public ColorStateList getProgressBarColor() { - return getMixin(ProgressBarMixin.class).getColor(); - } - - /* 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<SavedState> CREATOR = - new Parcelable.Creator<SavedState>() { - - @Override - public SavedState createFromParcel(Parcel parcel) { - return new SavedState(parcel); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } } diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java index 050d566..f1c7e11 100644 --- a/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java +++ b/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java @@ -26,141 +26,128 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ListAdapter; import android.widget.ListView; - import com.android.setupwizardlib.template.ListMixin; import com.android.setupwizardlib.template.ListViewScrollHandlingDelegate; import com.android.setupwizardlib.template.RequireScrollMixin; public class SetupWizardListLayout extends SetupWizardLayout { - private static final String TAG = "SetupWizardListLayout"; - - private ListMixin mListMixin; - - 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) { - mListMixin = new ListMixin(this, attrs, defStyleAttr); - registerMixin(ListMixin.class, mListMixin); - - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - requireScrollMixin.setScrollHandlingDelegate( - new ListViewScrollHandlingDelegate(requireScrollMixin, getListView())); - } - - @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); - mListMixin.onLayout(); - } - - public ListView getListView() { - return mListMixin.getListView(); - } - - public void setAdapter(ListAdapter adapter) { - mListMixin.setAdapter(adapter); - } - - public ListAdapter getAdapter() { - return mListMixin.getAdapter(); - } - - /** - * 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}. - * - * @see ListMixin#setDividerInset(int) - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - mListMixin.setDividerInset(inset); - } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start 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}. - * @param end The number of pixels to inset on the "end" side of the list divider. - * - * @see ListMixin#setDividerInsets(int, int) - */ - public void setDividerInsets(int start, int end) { - mListMixin.setDividerInsets(start, end); - } - - /** - * @deprecated Use {@link #getDividerInsetStart()} instead. - */ - @Deprecated - public int getDividerInset() { - return mListMixin.getDividerInset(); - } - - /** - * @see ListMixin#getDividerInsetStart() - */ - public int getDividerInsetStart() { - return mListMixin.getDividerInsetStart(); - } - - /** - * @see ListMixin#getDividerInsetEnd() - */ - public int getDividerInsetEnd() { - return mListMixin.getDividerInsetEnd(); - } - - /** - * @see ListMixin#getDivider() - */ - public Drawable getDivider() { - return mListMixin.getDivider(); - } + private ListMixin listMixin; + + 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(null, 0); + } + + public SetupWizardListLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public SetupWizardListLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + listMixin = new ListMixin(this, attrs, defStyleAttr); + registerMixin(ListMixin.class, listMixin); + + final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); + requireScrollMixin.setScrollHandlingDelegate( + new ListViewScrollHandlingDelegate(requireScrollMixin, getListView())); + } + + @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); + listMixin.onLayout(); + } + + public ListView getListView() { + return listMixin.getListView(); + } + + public void setAdapter(ListAdapter adapter) { + listMixin.setAdapter(adapter); + } + + public ListAdapter getAdapter() { + return listMixin.getAdapter(); + } + + /** + * 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}. + * @see ListMixin#setDividerInset(int) + * @deprecated Use {@link #setDividerInsets(int, int)} instead. + */ + @Deprecated + public void setDividerInset(int inset) { + listMixin.setDividerInset(inset); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start 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}. + * @param end The number of pixels to inset on the "end" side of the list divider. + * @see ListMixin#setDividerInsets(int, int) + */ + public void setDividerInsets(int start, int end) { + listMixin.setDividerInsets(start, end); + } + + /** @deprecated Use {@link #getDividerInsetStart()} instead. */ + @Deprecated + public int getDividerInset() { + return listMixin.getDividerInset(); + } + + /** @see ListMixin#getDividerInsetStart() */ + public int getDividerInsetStart() { + return listMixin.getDividerInsetStart(); + } + + /** @see ListMixin#getDividerInsetEnd() */ + public int getDividerInsetEnd() { + return listMixin.getDividerInsetEnd(); + } + + /** @see ListMixin#getDivider() */ + public Drawable getDivider() { + return listMixin.getDivider(); + } } diff --git a/library/main/src/com/android/setupwizardlib/TemplateLayout.java b/library/main/src/com/android/setupwizardlib/TemplateLayout.java index 0108880..c53e176 100644 --- a/library/main/src/com/android/setupwizardlib/TemplateLayout.java +++ b/library/main/src/com/android/setupwizardlib/TemplateLayout.java @@ -20,256 +20,249 @@ import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.os.Build.VERSION_CODES; +import androidx.annotation.Keep; +import androidx.annotation.LayoutRes; +import androidx.annotation.StyleRes; 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 androidx.annotation.Keep; -import androidx.annotation.LayoutRes; -import androidx.annotation.StyleRes; - import com.android.setupwizardlib.template.Mixin; import com.android.setupwizardlib.util.FallbackThemeWrapper; - import java.util.HashMap; import java.util.Map; /** - * 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 + * 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; - - private Map<Class<? extends Mixin>, Mixin> mMixins = new HashMap<>(); + /** + * 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 container; - public TemplateLayout(Context context, int template, int containerId) { - super(context); - init(template, containerId, null, R.attr.suwLayoutTheme); - } + private final Map<Class<? extends Mixin>, Mixin> mixins = new HashMap<>(); - public TemplateLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(0, 0, attrs, R.attr.suwLayoutTheme); - } + public TemplateLayout(Context context, int template, int containerId) { + super(context); + init(template, containerId, null, R.attr.suwLayoutTheme); + } - @TargetApi(VERSION_CODES.HONEYCOMB) - public TemplateLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(0, 0, attrs, defStyleAttr); - } + public TemplateLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(0, 0, attrs, R.attr.suwLayoutTheme); + } - // 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); + @TargetApi(VERSION_CODES.HONEYCOMB) + public TemplateLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(0, 0, attrs, defStyleAttr); + } - a.recycle(); + // 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); } - - /** - * Registers a mixin with a given class. This method should be called in the constructor. - * - * @param cls The class to register the mixin. In most cases, {@code cls} is the same as - * {@code mixin.getClass()}, but {@code cls} can also be a super class of that. In - * the latter case the the mixin must be retrieved using {@code cls} in - * {@link #getMixin(Class)}, not the subclass. - * @param mixin The mixin to be registered. - * @param <M> The class of the mixin to register. This is the same as {@code cls} - */ - protected <M extends Mixin> void registerMixin(Class<M> cls, M mixin) { - mMixins.put(cls, mixin); + if (containerId == 0) { + containerId = a.getResourceId(R.styleable.SuwTemplateLayout_suwContainer, 0); } + inflateTemplate(template, 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. - */ - // Returning generic type is the common pattern used for findViewBy* methods - @SuppressWarnings("TypeParameterUnusedInFormals") - public <T extends View> T findManagedViewById(int id) { - return findViewById(id); - } + a.recycle(); + } - /** - * Get a {@link Mixin} from this template registered earlier in - * {@link #registerMixin(Class, Mixin)}. - * - * @param cls The class marker of Mixin being requested. The actual Mixin returned may be a - * subclass of this marker. Note that this must be the same class as registered in - * {@link #registerMixin(Class, Mixin)}, which is not necessarily the - * same as the concrete class of the instance returned by this method. - * @param <M> The type of the class marker. - * @return The mixin marked by {@code cls}, or null if the template does not have a matching - * mixin. - */ - @SuppressWarnings("unchecked") - public <M extends Mixin> M getMixin(Class<M> cls) { - return (M) mMixins.get(cls); - } + /** + * Registers a mixin with a given class. This method should be called in the constructor. + * + * @param cls The class to register the mixin. In most cases, {@code cls} is the same as {@code + * mixin.getClass()}, but {@code cls} can also be a super class of that. In the latter case + * the mixin must be retrieved using {@code cls} in {@link #getMixin(Class)}, not the + * subclass. + * @param mixin The mixin to be registered. + * @param <M> The class of the mixin to register. This is the same as {@code cls} + */ + protected <M extends Mixin> void registerMixin(Class<M> cls, M mixin) { + mixins.put(cls, mixin); + } - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - mContainer.addView(child, index, params); - } + /** + * 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. + */ + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public <T extends View> T findManagedViewById(int id) { + return findViewById(id); + } - private void addViewInternal(View child) { - super.addView(child, -1, generateDefaultLayoutParams()); - } + /** + * Get a {@link Mixin} from this template registered earlier in {@link #registerMixin(Class, + * Mixin)}. + * + * @param cls The class marker of Mixin being requested. The actual Mixin returned may be a + * subclass of this marker. Note that this must be the same class as registered in {@link + * #registerMixin(Class, Mixin)}, which is not necessarily the same as the concrete class of + * the instance returned by this method. + * @param <M> The type of the class marker. + * @return The mixin marked by {@code cls}, or null if the template does not have a matching + * mixin. + */ + @SuppressWarnings("unchecked") + public <M extends Mixin> M getMixin(Class<M> cls) { + return (M) mixins.get(cls); + } - private void inflateTemplate(int templateResource, int containerId) { - final LayoutInflater inflater = LayoutInflater.from(getContext()); - final View templateRoot = onInflateTemplate(inflater, templateResource); - addViewInternal(templateRoot); + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + container.addView(child, index, params); + } - mContainer = findContainer(containerId); - if (mContainer == null) { - throw new IllegalArgumentException("Container cannot be null in TemplateLayout"); - } - onTemplateInflated(); - } + private void addViewInternal(View child) { + super.addView(child, -1, generateDefaultLayoutParams()); + } - /** - * 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, @LayoutRes int template) { - return inflateTemplate(inflater, 0, template); - } + private void inflateTemplate(int templateResource, int containerId) { + final LayoutInflater inflater = LayoutInflater.from(getContext()); + final View templateRoot = onInflateTemplate(inflater, templateResource); + addViewInternal(templateRoot); - /** - * Inflate the template using the given inflater and theme. The fallback theme will be applied - * to the theme without overriding the values already defined in the theme, but simply providing - * default values for values which have not been defined. This allows templates to add - * additional required theme attributes without breaking existing clients. - * - * <p>In general, clients should still set the activity theme to the corresponding theme in - * setup wizard lib, so that the content area gets the correct styles as well. - * - * @param inflater A LayoutInflater to inflate the template. - * @param fallbackTheme A fallback theme to apply to the template. If the values defined in the - * fallback theme is already defined in the original theme, the value in - * the original theme takes precedence. - * @param template The layout template to be inflated. - * @return Root of the inflated layout. - * - * @see FallbackThemeWrapper - */ - protected final View inflateTemplate(LayoutInflater inflater, @StyleRes int fallbackTheme, - @LayoutRes int template) { - if (template == 0) { - throw new IllegalArgumentException("android:layout not specified for TemplateLayout"); - } - if (fallbackTheme != 0) { - inflater = LayoutInflater.from( - new FallbackThemeWrapper(inflater.getContext(), fallbackTheme)); - } - return inflater.inflate(template, this, false); + container = findContainer(containerId); + if (container == null) { + throw new IllegalArgumentException("Container cannot be null in TemplateLayout"); } + onTemplateInflated(); + } - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - // Maintain compatibility with the deprecated way of specifying container ID. - containerId = getContainerId(); - } - return (ViewGroup) findViewById(containerId); + /** + * Inflate the template using the given inflater and theme. The fallback theme will be applied to + * the theme without overriding the values already defined in the theme, but simply providing + * default values for values which have not been defined. This allows templates to add additional + * required theme attributes without breaking existing clients. + * + * <p>In general, clients should still set the activity theme to the corresponding theme in setup + * wizard lib, so that the content area gets the correct styles as well. + * + * @param inflater A LayoutInflater to inflate the template. + * @param fallbackTheme A fallback theme to apply to the template. If the values defined in the + * fallback theme is already defined in the original theme, the value in the original theme + * takes precedence. + * @param template The layout template to be inflated. + * @return Root of the inflated layout. + * @see FallbackThemeWrapper + */ + protected final View inflateTemplate( + LayoutInflater inflater, @StyleRes int fallbackTheme, @LayoutRes int template) { + if (template == 0) { + throw new IllegalArgumentException("android:layout not specified for TemplateLayout"); } - - /** - * 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() { + if (fallbackTheme != 0) { + inflater = + LayoutInflater.from(new FallbackThemeWrapper(inflater.getContext(), fallbackTheme)); } + return inflater.inflate(template, this, false); + } + + /** + * 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, @LayoutRes int template) { + return inflateTemplate(inflater, 0, template); + } - /** - * @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; + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + // Maintain compatibility with the deprecated way of specifying container ID. + containerId = getContainerId(); } + return (ViewGroup) findViewById(containerId); + } - /* Animator support */ + /** + * 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() {} - private float mXFraction; - private ViewTreeObserver.OnPreDrawListener mPreDrawListener; + /** + * @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; + } - /** - * 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 - * <code> - * -keep @androidx.annotation.Keep class * - * </code> - * 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); - } - } - } + /* Animator support */ + + private float xFraction; + private ViewTreeObserver.OnPreDrawListener preDrawListener; - /** - * 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; + /** + * 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 <code> + * -keep @androidx.annotation.Keep class * + * </code> to your proguard configuration if you are seeing mysterious {@link NoSuchMethodError} + * at runtime. + */ + @Keep + @TargetApi(VERSION_CODES.HONEYCOMB) + public void setXFraction(float fraction) { + xFraction = 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 (preDrawListener == null) { + preDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getViewTreeObserver().removeOnPreDrawListener(preDrawListener); + setXFraction(xFraction); + return true; + } + }; + getViewTreeObserver().addOnPreDrawListener(preDrawListener); + } } + } + + /** + * 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 xFraction; + } } diff --git a/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java b/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java index f438691..2ea5288 100644 --- a/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java +++ b/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java @@ -24,95 +24,86 @@ import android.view.ViewConfiguration; /** * Helper class to detect the consective-tap gestures on a view. * - * <p/>This class is instantiated and used similar to a GestureDetector, where onTouchEvent should - * be called when there are MotionEvents this detector should know about. + * <p>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 final int mConsecutiveTapTimeout; + public interface OnConsecutiveTapsListener { + /** Callback method when the user tapped on the target view X number of times. */ + void onConsecutiveTaps(int numOfConsecutiveTaps); + } - private int mConsecutiveTapsCounter = 0; - private MotionEvent mPreviousTapEvent; + private final View view; + private final OnConsecutiveTapsListener listener; + private final int consecutiveTapTouchSlopSquare; + private final int consecutiveTapTimeout; - /** - * @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; - mConsecutiveTapTimeout = ViewConfiguration.getDoubleTapTimeout(); - } + private int consecutiveTapsCounter = 0; + private MotionEvent previousTapEvent; - /** - * 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; - } + /** + * @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) { + this.listener = listener; + this.view = view; + int doubleTapSlop = ViewConfiguration.get(this.view.getContext()).getScaledDoubleTapSlop(); + consecutiveTapTouchSlopSquare = doubleTapSlop * doubleTapSlop; + consecutiveTapTimeout = ViewConfiguration.getDoubleTapTimeout(); + } - if (mPreviousTapEvent != null) { - mPreviousTapEvent.recycle(); - } - mPreviousTapEvent = MotionEvent.obtain(ev); + /** + * 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]; + view.getLocationOnScreen(leftTop); + viewRect.set( + leftTop[0], leftTop[1], leftTop[0] + view.getWidth(), leftTop[1] + view.getHeight()); + if (viewRect.contains((int) ev.getX(), (int) ev.getY())) { + if (isConsecutiveTap(ev)) { + consecutiveTapsCounter++; + } else { + consecutiveTapsCounter = 1; } - } + listener.onConsecutiveTaps(consecutiveTapsCounter); + } else { + // Touch outside the target view. Reset counter. + consecutiveTapsCounter = 0; + } - /** - * Resets the consecutive-tap counter to zero. - */ - public void resetCounter() { - mConsecutiveTapsCounter = 0; + if (previousTapEvent != null) { + previousTapEvent.recycle(); + } + previousTapEvent = MotionEvent.obtain(ev); } + } - /** - * Returns true if the distance between consecutive tap is within - * {@link #mConsecutiveTapTouchSlopSquare}. False, otherwise. - */ - private boolean isConsecutiveTap(MotionEvent currentTapEvent) { - if (mPreviousTapEvent == null) { - return false; - } + /** Resets the consecutive-tap counter to zero. */ + public void resetCounter() { + consecutiveTapsCounter = 0; + } - double deltaX = mPreviousTapEvent.getX() - currentTapEvent.getX(); - double deltaY = mPreviousTapEvent.getY() - currentTapEvent.getY(); - long deltaTime = currentTapEvent.getEventTime() - mPreviousTapEvent.getEventTime(); - return (deltaX * deltaX + deltaY * deltaY <= mConsecutiveTapTouchSlopSquare) - && deltaTime < mConsecutiveTapTimeout; + /** + * Returns true if the distance between consecutive tap is within {@link + * #consecutiveTapTouchSlopSquare}. False, otherwise. + */ + private boolean isConsecutiveTap(MotionEvent currentTapEvent) { + if (previousTapEvent == null) { + return false; } + + double deltaX = previousTapEvent.getX() - currentTapEvent.getX(); + double deltaY = previousTapEvent.getY() - currentTapEvent.getY(); + long deltaTime = currentTapEvent.getEventTime() - previousTapEvent.getEventTime(); + return (deltaX * deltaX + deltaY * deltaY <= consecutiveTapTouchSlopSquare) + && deltaTime < consecutiveTapTimeout; + } } diff --git a/library/main/src/com/android/setupwizardlib/items/AbstractItem.java b/library/main/src/com/android/setupwizardlib/items/AbstractItem.java index 11a9939..88f9294 100644 --- a/library/main/src/com/android/setupwizardlib/items/AbstractItem.java +++ b/library/main/src/com/android/setupwizardlib/items/AbstractItem.java @@ -25,42 +25,42 @@ import android.util.AttributeSet; */ public abstract class AbstractItem extends AbstractItemHierarchy implements IItem { - public AbstractItem() { - super(); - } + public AbstractItem() { + super(); + } - public AbstractItem(Context context, AttributeSet attrs) { - super(context, attrs); - } + public AbstractItem(Context context, AttributeSet attrs) { + super(context, attrs); + } - @Override - public int getCount() { - return 1; - } + @Override + public int getCount() { + return 1; + } - @Override - public IItem getItemAt(int position) { - return this; - } + @Override + public IItem getItemAt(int position) { + return this; + } - @Override - public ItemHierarchy findItemById(int id) { - if (id == getId()) { - return this; - } - return null; + @Override + public ItemHierarchy findItemById(int id) { + if (id == getId()) { + return this; } + return null; + } - /** - * Convenience method to notify the adapter that the contents of this item has changed. This - * only includes non-structural changes. Changes that causes the item to be removed should use - * the other notification methods. - * - * @see #notifyItemRangeChanged(int, int) - * @see #notifyItemRangeInserted(int, int) - * @see #notifyItemRangeRemoved(int, int) - */ - public void notifyItemChanged() { - notifyItemRangeChanged(0, 1); - } + /** + * Convenience method to notify the adapter that the contents of this item has changed. This only + * includes non-structural changes. Changes that causes the item to be removed should use the + * other notification methods. + * + * @see #notifyItemRangeChanged(int, int) + * @see #notifyItemRangeInserted(int, int) + * @see #notifyItemRangeRemoved(int, int) + */ + public void notifyItemChanged() { + notifyItemRangeChanged(0, 1); + } } diff --git a/library/main/src/com/android/setupwizardlib/items/AbstractItemHierarchy.java b/library/main/src/com/android/setupwizardlib/items/AbstractItemHierarchy.java index 805e7af..e33cc2f 100644 --- a/library/main/src/com/android/setupwizardlib/items/AbstractItemHierarchy.java +++ b/library/main/src/com/android/setupwizardlib/items/AbstractItemHierarchy.java @@ -20,138 +20,123 @@ import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; - import com.android.setupwizardlib.R; - import java.util.ArrayList; -/** - * An abstract item hierarchy; provides default implementation for ID and observers. - */ +/** An abstract item hierarchy; provides default implementation for ID and observers. */ public abstract class AbstractItemHierarchy implements ItemHierarchy { - /* static section */ + /* static section */ - private static final String TAG = "AbstractItemHierarchy"; + private static final String TAG = "AbstractItemHierarchy"; - /* non-static section */ + /* non-static section */ - private ArrayList<Observer> mObservers = new ArrayList<>(); - private int mId = 0; + private final ArrayList<Observer> observers = new ArrayList<>(); + private int id = 0; - public AbstractItemHierarchy() { - } + 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 AbstractItemHierarchy(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwAbstractItem); + id = a.getResourceId(R.styleable.SuwAbstractItem_android_id, 0); + a.recycle(); + } - public void setId(int id) { - mId = id; - } + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public int getViewId() { + return getId(); + } - public int getId() { - return mId; + @Override + public void registerObserver(Observer observer) { + observers.add(observer); + } + + @Override + public void unregisterObserver(Observer observer) { + observers.remove(observer); + } + + /** @see Observer#onChanged(ItemHierarchy) */ + public void notifyChanged() { + for (Observer observer : observers) { + observer.onChanged(this); } + } - public int getViewId() { - return getId(); + /** @see Observer#onItemRangeChanged(ItemHierarchy, int, int) */ + public void notifyItemRangeChanged(int position, int itemCount) { + if (position < 0) { + Log.w(TAG, "notifyItemRangeChanged: Invalid position=" + position); + return; + } + if (itemCount < 0) { + Log.w(TAG, "notifyItemRangeChanged: Invalid itemCount=" + itemCount); + return; } - @Override - public void registerObserver(Observer observer) { - mObservers.add(observer); + for (Observer observer : observers) { + observer.onItemRangeChanged(this, position, itemCount); } + } - @Override - public void unregisterObserver(Observer observer) { - mObservers.remove(observer); + /** @see Observer#onItemRangeInserted(ItemHierarchy, int, int) */ + public void notifyItemRangeInserted(int position, int itemCount) { + if (position < 0) { + Log.w(TAG, "notifyItemRangeInserted: Invalid position=" + position); + return; + } + if (itemCount < 0) { + Log.w(TAG, "notifyItemRangeInserted: Invalid itemCount=" + itemCount); + return; } - /** - * @see Observer#onChanged(ItemHierarchy) - */ - public void notifyChanged() { - for (Observer observer : mObservers) { - observer.onChanged(this); - } + for (Observer observer : observers) { + observer.onItemRangeInserted(this, position, itemCount); } + } - /** - * @see Observer#onItemRangeChanged(ItemHierarchy, int, int) - */ - public void notifyItemRangeChanged(int position, int itemCount) { - if (position < 0) { - Log.w(TAG, "notifyItemRangeChanged: Invalid position=" + position); - return; - } - if (itemCount < 0) { - Log.w(TAG, "notifyItemRangeChanged: Invalid itemCount=" + itemCount); - return; - } - - for (Observer observer : mObservers) { - observer.onItemRangeChanged(this, position, itemCount); - } + /** @see Observer#onItemRangeMoved(ItemHierarchy, int, int, int) */ + public void notifyItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + if (fromPosition < 0) { + Log.w(TAG, "notifyItemRangeMoved: Invalid fromPosition=" + fromPosition); + return; + } + if (toPosition < 0) { + Log.w(TAG, "notifyItemRangeMoved: Invalid toPosition=" + toPosition); + return; + } + if (itemCount < 0) { + Log.w(TAG, "notifyItemRangeMoved: Invalid itemCount=" + itemCount); + return; } - /** - * @see Observer#onItemRangeInserted(ItemHierarchy, int, int) - */ - public void notifyItemRangeInserted(int position, int itemCount) { - if (position < 0) { - Log.w(TAG, "notifyItemRangeInserted: Invalid position=" + position); - return; - } - if (itemCount < 0) { - Log.w(TAG, "notifyItemRangeInserted: Invalid itemCount=" + itemCount); - return; - } - - for (Observer observer : mObservers) { - observer.onItemRangeInserted(this, position, itemCount); - } + for (Observer observer : observers) { + observer.onItemRangeMoved(this, fromPosition, toPosition, itemCount); } + } - /** - * @see Observer#onItemRangeMoved(ItemHierarchy, int, int, int) - */ - public void notifyItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - if (fromPosition < 0) { - Log.w(TAG, "notifyItemRangeMoved: Invalid fromPosition=" + fromPosition); - return; - } - if (toPosition < 0) { - Log.w(TAG, "notifyItemRangeMoved: Invalid toPosition=" + toPosition); - return; - } - if (itemCount < 0) { - Log.w(TAG, "notifyItemRangeMoved: Invalid itemCount=" + itemCount); - return; - } - - for (Observer observer : mObservers) { - observer.onItemRangeMoved(this, fromPosition, toPosition, itemCount); - } + /** @see Observer#onItemRangeRemoved(ItemHierarchy, int, int) */ + public void notifyItemRangeRemoved(int position, int itemCount) { + if (position < 0) { + Log.w(TAG, "notifyItemRangeInserted: Invalid position=" + position); + return; + } + if (itemCount < 0) { + Log.w(TAG, "notifyItemRangeInserted: Invalid itemCount=" + itemCount); + return; } - /** - * @see Observer#onItemRangeRemoved(ItemHierarchy, int, int) - */ - public void notifyItemRangeRemoved(int position, int itemCount) { - if (position < 0) { - Log.w(TAG, "notifyItemRangeInserted: Invalid position=" + position); - return; - } - if (itemCount < 0) { - Log.w(TAG, "notifyItemRangeInserted: Invalid itemCount=" + itemCount); - return; - } - - for (Observer observer : mObservers) { - observer.onItemRangeRemoved(this, position, itemCount); - } + for (Observer observer : observers) { + observer.onItemRangeRemoved(this, position, itemCount); } + } } diff --git a/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java b/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java index 06ce4ac..80b9453 100644 --- a/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java +++ b/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java @@ -21,16 +21,15 @@ 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}. + * A list item with one or more buttons, declared as {@link + * com.android.setupwizardlib.items.ButtonItem}. * * <p>Example usage: + * * <pre>{@code * <ButtonBarItem> * @@ -48,81 +47,81 @@ import java.util.ArrayList; */ public class ButtonBarItem extends AbstractItem implements ItemInflater.ItemParent { - private final ArrayList<ButtonItem> 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; + private final ArrayList<ButtonItem> buttons = new ArrayList<>(); + private boolean visible = 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) { + this.visible = visible; + } + + public boolean isVisible() { + return visible; + } + + @Override + 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 : buttons) { + Button button = buttonItem.createButton(layout); + layout.addView(button); } - @Override - public int getLayoutResource() { - return R.layout.suw_items_button_bar; - } - - public void setVisible(boolean visible) { - mVisible = visible; - } - - public boolean isVisible() { - return mVisible; - } + view.setId(getViewId()); + } - @Override - public int getViewId() { - return getId(); + @Override + public void addChild(ItemHierarchy child) { + if (child instanceof ButtonItem) { + buttons.add((ButtonItem) child); + } else { + throw new UnsupportedOperationException("Cannot add non-button item to Button Bar"); } + } - @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 ItemHierarchy findItemById(int id) { + if (getId() == id) { + return this; } - - @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; + for (ButtonItem button : buttons) { + final ItemHierarchy item = button.findItemById(id); + if (item != null) { + return item; + } } + return null; + } } diff --git a/library/main/src/com/android/setupwizardlib/items/ButtonItem.java b/library/main/src/com/android/setupwizardlib/items/ButtonItem.java index 07802ae..b398f4d 100644 --- a/library/main/src/com/android/setupwizardlib/items/ButtonItem.java +++ b/library/main/src/com/android/setupwizardlib/items/ButtonItem.java @@ -25,7 +25,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; - import com.android.setupwizardlib.R; /** @@ -34,128 +33,123 @@ import com.android.setupwizardlib.R; */ 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 = createButton(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); - mButton.setId(getViewId()); - return mButton; - } - - @Override - public void onClick(View v) { - if (mListener != null) { - mListener.onClick(this); - } - } - - @SuppressLint("InflateParams") // This is used similar to Button(Context), so it's OK to not - // specify the parent. - private Button createButton(Context context) { - // Inflate a single button from XML, so that when using support lib, it will take advantage - // of the injected layout inflater and give us AppCompatButton instead. - return (Button) LayoutInflater.from(context).inflate(R.layout.suw_button, null, false); - } + public interface OnClickListener { + void onClick(ButtonItem item); + } + + private boolean enabled = true; + private CharSequence text; + private int theme = R.style.SuwButtonItem; + private OnClickListener listener; + + private Button button; + + public ButtonItem() { + super(); + } + + public ButtonItem(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwButtonItem); + enabled = a.getBoolean(R.styleable.SuwButtonItem_android_enabled, true); + text = a.getText(R.styleable.SuwButtonItem_android_text); + theme = a.getResourceId(R.styleable.SuwButtonItem_android_theme, R.style.SuwButtonItem); + a.recycle(); + } + + public void setOnClickListener(OnClickListener listener) { + this.listener = listener; + } + + public void setText(CharSequence text) { + this.text = text; + } + + public CharSequence getText() { + return text; + } + + /** + * 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) { + this.theme = theme; + button = null; + } + + /** @return Resource ID of the theme used by this button. */ + public int getTheme() { + return theme; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public int getCount() { + return 0; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @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 (button == null) { + Context context = parent.getContext(); + if (theme != 0) { + context = new ContextThemeWrapper(context, theme); + } + button = createButton(context); + button.setOnClickListener(this); + } else { + if (button.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) button.getParent()).removeView(button); + } + } + button.setEnabled(enabled); + button.setText(text); + button.setId(getViewId()); + return button; + } + + @SuppressLint("InflateParams") // This is used similar to Button(Context), so it's OK to not + // specify the parent. + private Button createButton(Context context) { + // Inflate a single button from XML, so that when using support lib, it will take advantage + // of the injected layout inflater and give us AppCompatButton instead. + return (Button) LayoutInflater.from(context).inflate(R.layout.suw_button, null, false); + } + + @Override + public void onClick(View v) { + if (listener != null) { + listener.onClick(this); + } + } } diff --git a/library/main/src/com/android/setupwizardlib/items/IItem.java b/library/main/src/com/android/setupwizardlib/items/IItem.java index 26391dc..cd29ec2 100644 --- a/library/main/src/com/android/setupwizardlib/items/IItem.java +++ b/library/main/src/com/android/setupwizardlib/items/IItem.java @@ -18,31 +18,27 @@ package com.android.setupwizardlib.items; import android.view.View; -/** - * Representation of an item in an {@link ItemHierarchy}. - */ +/** 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(); + /** + * 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); + /** + * 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(); + /** @return True if this item is enabled. */ + boolean isEnabled(); } diff --git a/library/main/src/com/android/setupwizardlib/items/Item.java b/library/main/src/com/android/setupwizardlib/items/Item.java index fc8823e..c0d49d3 100644 --- a/library/main/src/com/android/setupwizardlib/items/Item.java +++ b/library/main/src/com/android/setupwizardlib/items/Item.java @@ -23,7 +23,6 @@ import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; import android.widget.TextView; - import com.android.setupwizardlib.R; /** @@ -32,146 +31,145 @@ import com.android.setupwizardlib.R; */ 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; - notifyItemChanged(); - } - - @Override - public int getCount() { - return isVisible() ? 1 : 0; - } - - @Override - public boolean isEnabled() { - return mEnabled; - } - - public void setIcon(Drawable icon) { - mIcon = icon; - notifyItemChanged(); - } - - public Drawable getIcon() { - return mIcon; - } - - public void setLayoutResource(int layoutResource) { - mLayoutRes = layoutResource; - notifyItemChanged(); - } - - @Override - public int getLayoutResource() { - return mLayoutRes; - } - - public void setSummary(CharSequence summary) { - mSummary = summary; - notifyItemChanged(); - } - - public CharSequence getSummary() { - return mSummary; - } - - public void setTitle(CharSequence title) { - mTitle = title; - notifyItemChanged(); - } - - public CharSequence getTitle() { - return mTitle; - } - - public void setVisible(boolean visible) { - if (mVisible == visible) { - return; - } - mVisible = visible; - if (!visible) { - notifyItemRangeRemoved(0, 1); - } else { - notifyItemRangeInserted(0, 1); - } - } - - public boolean isVisible() { - return mVisible; - } - - @Override - 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); - onMergeIconStateAndLevels(iconView, icon); - iconView.setImageDrawable(icon); - iconContainer.setVisibility(View.VISIBLE); - } else { - iconContainer.setVisibility(View.GONE); - } - - view.setId(getViewId()); - } - - /** - * Copies state and level information from {@link #getIcon()} to the currently bound view's - * ImageView. Subclasses can override this method to change whats being copied from the icon - * to the ImageView. - */ - protected void onMergeIconStateAndLevels(ImageView iconView, Drawable icon) { - iconView.setImageState(icon.getState(), false /* merge */); - iconView.setImageLevel(icon.getLevel()); - } + private boolean enabled = true; + private Drawable icon; + private int layoutRes; + private CharSequence summary; + private CharSequence title; + private boolean visible = true; + + public Item() { + super(); + layoutRes = getDefaultLayoutResource(); + } + + public Item(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwItem); + enabled = a.getBoolean(R.styleable.SuwItem_android_enabled, true); + icon = a.getDrawable(R.styleable.SuwItem_android_icon); + title = a.getText(R.styleable.SuwItem_android_title); + summary = a.getText(R.styleable.SuwItem_android_summary); + layoutRes = a.getResourceId(R.styleable.SuwItem_android_layout, getDefaultLayoutResource()); + visible = a.getBoolean(R.styleable.SuwItem_android_visible, true); + a.recycle(); + } + + protected int getDefaultLayoutResource() { + return R.layout.suw_items_default; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + notifyItemChanged(); + } + + @Override + public int getCount() { + return isVisible() ? 1 : 0; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + public void setIcon(Drawable icon) { + this.icon = icon; + notifyItemChanged(); + } + + public Drawable getIcon() { + return icon; + } + + public void setLayoutResource(int layoutResource) { + layoutRes = layoutResource; + notifyItemChanged(); + } + + @Override + public int getLayoutResource() { + return layoutRes; + } + + public void setSummary(CharSequence summary) { + this.summary = summary; + notifyItemChanged(); + } + + public CharSequence getSummary() { + return summary; + } + + public void setTitle(CharSequence title) { + this.title = title; + notifyItemChanged(); + } + + public CharSequence getTitle() { + return title; + } + + public void setVisible(boolean visible) { + if (this.visible == visible) { + return; + } + this.visible = visible; + if (!visible) { + notifyItemRangeRemoved(0, 1); + } else { + notifyItemRangeInserted(0, 1); + } + } + + public boolean isVisible() { + return visible; + } + + @Override + 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); + onMergeIconStateAndLevels(iconView, icon); + iconView.setImageDrawable(icon); + iconContainer.setVisibility(View.VISIBLE); + } else { + iconContainer.setVisibility(View.GONE); + } + + view.setId(getViewId()); + } + + /** + * Copies state and level information from {@link #getIcon()} to the currently bound view's + * ImageView. Subclasses can override this method to change whats being copied from the icon to + * the ImageView. + */ + protected void onMergeIconStateAndLevels(ImageView iconView, Drawable icon) { + iconView.setImageState(icon.getState(), false /* merge */); + iconView.setImageLevel(icon.getLevel()); + } } diff --git a/library/main/src/com/android/setupwizardlib/items/ItemAdapter.java b/library/main/src/com/android/setupwizardlib/items/ItemAdapter.java index 53285e7..851736c 100644 --- a/library/main/src/com/android/setupwizardlib/items/ItemAdapter.java +++ b/library/main/src/com/android/setupwizardlib/items/ItemAdapter.java @@ -23,130 +23,130 @@ 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. + * 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 void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - onChanged(itemHierarchy); - } - - @Override - public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - onChanged(itemHierarchy); - } - - @Override - public void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition, - int itemCount) { - onChanged(itemHierarchy); - } - - @Override - public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - onChanged(itemHierarchy); - } - - @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); - } - } + private final ItemHierarchy itemHierarchy; + private final ViewTypes viewTypes = new ViewTypes(); + + public ItemAdapter(ItemHierarchy hierarchy) { + itemHierarchy = hierarchy; + itemHierarchy.registerObserver(this); + refreshViewTypes(); + } + + @Override + public int getCount() { + return itemHierarchy.getCount(); + } + + @Override + public IItem getItem(int position) { + return itemHierarchy.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 viewTypes.get(layoutRes); + } + + @Override + public int getViewTypeCount() { + return viewTypes.size(); + } + + private void refreshViewTypes() { + for (int i = 0; i < getCount(); i++) { + IItem item = getItem(i); + viewTypes.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 void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + onChanged(itemHierarchy); + } + + @Override + public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + onChanged(itemHierarchy); + } + + @Override + public void onItemRangeMoved( + ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount) { + onChanged(itemHierarchy); + } + + @Override + public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + onChanged(itemHierarchy); + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + public ItemHierarchy findItemById(int id) { + return itemHierarchy.findItemById(id); + } + + public ItemHierarchy getRootItemHierarchy() { + return itemHierarchy; + } + + /** + * 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 final SparseIntArray positionMap = new SparseIntArray(); + private int nextPosition = 0; + + public int add(int id) { + if (positionMap.indexOfKey(id) < 0) { + positionMap.put(id, nextPosition); + nextPosition++; + } + return positionMap.get(id); + } + + public int size() { + return positionMap.size(); + } + + public int get(int id) { + return positionMap.get(id); + } + } } diff --git a/library/main/src/com/android/setupwizardlib/items/ItemGroup.java b/library/main/src/com/android/setupwizardlib/items/ItemGroup.java index 97b3199..246469f 100644 --- a/library/main/src/com/android/setupwizardlib/items/ItemGroup.java +++ b/library/main/src/com/android/setupwizardlib/items/ItemGroup.java @@ -20,301 +20,288 @@ import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.util.SparseIntArray; - import java.util.ArrayList; import java.util.List; -public class ItemGroup extends AbstractItemHierarchy implements ItemInflater.ItemParent, - ItemHierarchy.Observer { - - /* static section */ - - private static final String TAG = "ItemGroup"; - - /** - * 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); +public class ItemGroup extends AbstractItemHierarchy + implements ItemInflater.ItemParent, ItemHierarchy.Observer { + + /* static section */ + + private static final String TAG = "ItemGroup"; + + /** + * 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 + } } - - /** - * Same as {@link List#indexOf(Object)}, but using identity comparison rather than - * {@link Object#equals(Object)}. - */ - private static <T> int identityIndexOf(List<T> list, T object) { - final int count = list.size(); - for (int i = 0; i < count; i++) { - if (list.get(i) == object) { - return i; - } - } - return -1; + // 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); + } + + /** + * Same as {@link List#indexOf(Object)}, but using identity comparison rather than {@link + * Object#equals(Object)}. + */ + private static <T> int identityIndexOf(List<T> list, T object) { + final int count = list.size(); + for (int i = 0; i < count; i++) { + if (list.get(i) == object) { + return i; + } } - - /* non-static section */ - - private List<ItemHierarchy> 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 [ <Empty Item Hierarchy> ] - * - * 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(); + return -1; + } + + /* non-static section */ + + private final List<ItemHierarchy> children = new ArrayList<>(); + + /** + * A mapping from the index of an item hierarchy in children, to the first position in which the + * corresponding child hierarchy represents. For example: + * + * <p>ItemHierarchy Item Item Position Index + * + * <p>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 + * + * <p>1 [ <Empty Item Hierarchy> ] + * + * <p>2 [ Use cellular data ] 5 + * + * <p>3 [ Don't connect ] 6 + * + * <p>For this example of Wi-Fi screen, the following mapping will be produced: [ 0 -> 0 | 2 -> 5 + * | 3 -> 6 ] + * + * <p>Also note how ItemHierarchy index 1 is not present in the map, because it is empty. + * + * <p>ItemGroup uses this map to look for which ItemHierarchy an item at a given position belongs + * to. + */ + private final SparseIntArray hierarchyStart = new SparseIntArray(); + + private int count = 0; + private boolean dirty = 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) { + dirty = true; + children.add(child); + child.registerObserver(this); + + final int count = child.getCount(); + if (count > 0) { + notifyItemRangeInserted(getChildPosition(child), count); } - - 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) { - mDirty = true; - mChildren.add(child); - child.registerObserver(this); - - final int count = child.getCount(); - if (count > 0) { - notifyItemRangeInserted(getChildPosition(child), count); - } + } + + /** + * 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) { + final int childIndex = identityIndexOf(children, child); + final int childPosition = getChildPosition(childIndex); + dirty = true; + if (childIndex != -1) { + final int childCount = child.getCount(); + children.remove(childIndex); + child.unregisterObserver(this); + if (childCount > 0) { + notifyItemRangeRemoved(childPosition, childCount); + } + return true; } + return false; + } - /** - * 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) { - final int childIndex = identityIndexOf(mChildren, child); - final int childPosition = getChildPosition(childIndex); - mDirty = true; - if (childIndex != -1) { - final int childCount = child.getCount(); - mChildren.remove(childIndex); - child.unregisterObserver(this); - if (childCount > 0) { - notifyItemRangeRemoved(childPosition, childCount); - } - return true; - } - return false; + /** Remove all children from this hierarchy. */ + public void clear() { + if (children.isEmpty()) { + return; } - /** - * Remove all children from this hierarchy. - */ - public void clear() { - if (mChildren.size() == 0) { - return; - } - - final int numRemoved = getCount(); + final int numRemoved = getCount(); - for (ItemHierarchy item : mChildren) { - item.unregisterObserver(this); - } - mDirty = true; - mChildren.clear(); - notifyItemRangeRemoved(0, numRemoved); + for (ItemHierarchy item : children) { + item.unregisterObserver(this); } - - @Override - public int getCount() { - updateDataIfNeeded(); - return mCount; + dirty = true; + children.clear(); + notifyItemRangeRemoved(0, numRemoved); + } + + @Override + public int getCount() { + updateDataIfNeeded(); + return count; + } + + @Override + public IItem getItemAt(int position) { + int itemIndex = getItemIndex(position); + ItemHierarchy item = children.get(itemIndex); + int subpos = position - hierarchyStart.get(itemIndex); + return item.getItemAt(subpos); + } + + @Override + public void onChanged(ItemHierarchy hierarchy) { + // Need to set dirty, because our children may have gotten more items. + dirty = true; + notifyChanged(); + } + + /** + * @return The "Item Position" of the given child, or -1 if the child is not found. If the given + * child is empty, position of the next visible item is returned. + */ + private int getChildPosition(ItemHierarchy child) { + // Check the identity of the child rather than using .equals(), because here we want + // to find the index of the instance itself rather than something that equals to it. + return getChildPosition(identityIndexOf(children, child)); + } + + private int getChildPosition(int childIndex) { + updateDataIfNeeded(); + if (childIndex != -1) { + int childPos = -1; + int childCount = children.size(); + for (int i = childIndex; childPos < 0 && i < childCount; i++) { + // Find the position of the first visible child after childIndex. This is required + // when removing the last item from a nested ItemGroup. + childPos = hierarchyStart.get(i, -1); + } + if (childPos < 0) { + // If the last item in a group is being removed, there will be no visible item. + // In that case return the count instead, since that is where the item would have + // been if the child is not empty. + childPos = getCount(); + } + return childPos; } - - @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); + return -1; + } + + @Override + public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + // No need to set dirty because onItemRangeChanged does not include any structural changes. + final int childPosition = getChildPosition(itemHierarchy); + if (childPosition >= 0) { + notifyItemRangeChanged(childPosition + positionStart, itemCount); + } else { + Log.e(TAG, "Unexpected child change " + itemHierarchy); } - - @Override - public void onChanged(ItemHierarchy hierarchy) { - // Need to set dirty, because our children may have gotten more items. - mDirty = true; - notifyChanged(); + } + + @Override + public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + dirty = true; + final int childPosition = getChildPosition(itemHierarchy); + if (childPosition >= 0) { + notifyItemRangeInserted(childPosition + positionStart, itemCount); + } else { + Log.e(TAG, "Unexpected child insert " + itemHierarchy); } - - /** - * @return The "Item Position" of the given child, or -1 if the child is not found. If the given - * child is empty, position of the next visible item is returned. - */ - private int getChildPosition(ItemHierarchy child) { - // Check the identity of the child rather than using .equals(), because here we want - // to find the index of the instance itself rather than something that equals to it. - return getChildPosition(identityIndexOf(mChildren, child)); + } + + @Override + public void onItemRangeMoved( + ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount) { + dirty = true; + final int childPosition = getChildPosition(itemHierarchy); + if (childPosition >= 0) { + notifyItemRangeMoved(childPosition + fromPosition, childPosition + toPosition, itemCount); + } else { + Log.e(TAG, "Unexpected child move " + itemHierarchy); } - - private int getChildPosition(int childIndex) { - updateDataIfNeeded(); - if (childIndex != -1) { - int childPos = -1; - int childCount = mChildren.size(); - for (int i = childIndex; childPos < 0 && i < childCount; i++) { - // Find the position of the first visible child after childIndex. This is required - // when removing the last item from a nested ItemGroup. - childPos = mHierarchyStart.get(i, -1); - } - if (childPos < 0) { - // If the last item in a group is being removed, there will be no visible item. - // In that case return the count instead, since that is where the item would have - // been if the child is not empty. - childPos = getCount(); - } - return childPos; - } - return -1; + } + + @Override + public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + dirty = true; + final int childPosition = getChildPosition(itemHierarchy); + if (childPosition >= 0) { + notifyItemRangeRemoved(childPosition + positionStart, itemCount); + } else { + Log.e(TAG, "Unexpected child remove " + itemHierarchy); } + } - @Override - public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - // No need to set dirty because onItemRangeChanged does not include any structural changes. - final int childPosition = getChildPosition(itemHierarchy); - if (childPosition >= 0) { - notifyItemRangeChanged(childPosition + positionStart, itemCount); - } else { - Log.e(TAG, "Unexpected child change " + itemHierarchy); - } + @Override + public ItemHierarchy findItemById(int id) { + if (id == getId()) { + return this; } - - @Override - public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - mDirty = true; - final int childPosition = getChildPosition(itemHierarchy); - if (childPosition >= 0) { - notifyItemRangeInserted(childPosition + positionStart, itemCount); - } else { - Log.e(TAG, "Unexpected child insert " + itemHierarchy); - } + for (ItemHierarchy child : children) { + ItemHierarchy childFindItem = child.findItemById(id); + if (childFindItem != null) { + return childFindItem; + } } - - @Override - public void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition, - int itemCount) { - mDirty = true; - final int childPosition = getChildPosition(itemHierarchy); - if (childPosition >= 0) { - notifyItemRangeMoved(childPosition + fromPosition, childPosition + toPosition, - itemCount); - } else { - Log.e(TAG, "Unexpected child move " + itemHierarchy); + return null; + } + + /** If dirty, this method will recalculate the number of items and hierarchyStart. */ + private void updateDataIfNeeded() { + if (dirty) { + count = 0; + hierarchyStart.clear(); + for (int itemIndex = 0; itemIndex < children.size(); itemIndex++) { + ItemHierarchy item = children.get(itemIndex); + if (item.getCount() > 0) { + hierarchyStart.put(itemIndex, count); } + count += item.getCount(); + } + dirty = false; } - - @Override - public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - mDirty = true; - final int childPosition = getChildPosition(itemHierarchy); - if (childPosition >= 0) { - notifyItemRangeRemoved(childPosition + positionStart, itemCount); - } else { - Log.e(TAG, "Unexpected child remove " + itemHierarchy); - } + } + + /** + * 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 >= count) { + throw new IndexOutOfBoundsException("size=" + count + "; index=" + position); } - - @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; + int result = binarySearch(hierarchyStart, position); + if (result < 0) { + throw new IllegalStateException("Cannot have item start index < 0"); } + return result; + } } diff --git a/library/main/src/com/android/setupwizardlib/items/ItemHierarchy.java b/library/main/src/com/android/setupwizardlib/items/ItemHierarchy.java index 627b6f0..85e0870 100644 --- a/library/main/src/com/android/setupwizardlib/items/ItemHierarchy.java +++ b/library/main/src/com/android/setupwizardlib/items/ItemHierarchy.java @@ -20,82 +20,69 @@ 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} + * <p>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 { /** - * Observer for any changes in this hierarchy. If anything updated that causes this hierarchy to - * show different content, this observer should be called. + * Called when an underlying data update that can cause this hierarchy to show different content + * has occurred. + * + * <p>Note: This is a catch-all notification, but recycler view will have a harder time figuring + * out the animations for the change, and might even not animate the change at all. */ - interface Observer { - /** - * Called when an underlying data update that can cause this hierarchy to show different - * content has occurred. - * - * <p>Note: This is a catch-all notification, but recycler view will have a harder time - * figuring out the animations for the change, and might even not animate the change at all. - */ - void onChanged(ItemHierarchy itemHierarchy); + void onChanged(ItemHierarchy itemHierarchy); - /** - * Called when an underlying data update that can cause changes that are local to the given - * items. This method indicates that there are no structural changes like inserting or - * removing items. - */ - void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount); + /** + * Called when an underlying data update that can cause changes that are local to the given + * items. This method indicates that there are no structural changes like inserting or removing + * items. + */ + void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount); - /** - * Called when items are inserted at the given position. - */ - void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount); + /** Called when items are inserted at the given position. */ + void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount); - /** - * Called when the given items are moved to a different position. - */ - void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition, - int itemCount); + /** Called when the given items are moved to a different position. */ + void onItemRangeMoved( + ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount); - /** - * Called when the given items are removed from the item hierarchy. - */ - void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount); - } + /** Called when the given items are removed from the item hierarchy. */ + void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount); + } - /** - * Register an observer to observe changes for this item hierarchy. - */ - void registerObserver(Observer observer); + /** Register an observer to observe changes for this item hierarchy. */ + void registerObserver(Observer observer); - /** - * Unregister a previously registered observer. - */ - void unregisterObserver(Observer observer); + /** Unregister a previously registered observer. */ + void unregisterObserver(Observer observer); - /** - * @return the number of items this item hierarchy represent. - */ - int getCount(); + /** @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); + /** + * 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); + /** + * 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/library/main/src/com/android/setupwizardlib/items/ItemInflater.java b/library/main/src/com/android/setupwizardlib/items/ItemInflater.java index 618d785..26cdbbd 100644 --- a/library/main/src/com/android/setupwizardlib/items/ItemInflater.java +++ b/library/main/src/com/android/setupwizardlib/items/ItemInflater.java @@ -18,26 +18,24 @@ package com.android.setupwizardlib.items; import android.content.Context; -/** - * Inflate {@link Item} hierarchies from XML files. - */ +/** Inflate {@link Item} hierarchies from XML files. */ public class ItemInflater extends ReflectionInflater<ItemHierarchy> { - public interface ItemParent { - void addChild(ItemHierarchy child); - } + public interface ItemParent { + void addChild(ItemHierarchy child); + } - public ItemInflater(Context context) { - super(context); - setDefaultPackage(Item.class.getPackage().getName() + "."); - } + public ItemInflater(Context context) { + super(context); + setDefaultPackage(Item.class.getPackage().getName() + "."); + } - @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); - } + @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/library/main/src/com/android/setupwizardlib/items/ReflectionInflater.java b/library/main/src/com/android/setupwizardlib/items/ReflectionInflater.java index 8ffa943..3382f56 100644 --- a/library/main/src/com/android/setupwizardlib/items/ReflectionInflater.java +++ b/library/main/src/com/android/setupwizardlib/items/ReflectionInflater.java @@ -17,12 +17,10 @@ package com.android.setupwizardlib.items; import android.content.Context; -import android.util.AttributeSet; -import android.view.InflateException; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import android.util.AttributeSet; +import android.view.InflateException; import java.lang.reflect.Constructor; import java.util.HashMap; @@ -38,107 +36,104 @@ import java.util.HashMap; */ public abstract class ReflectionInflater<T> extends SimpleInflater<T> { - /* static section */ - - private static final Class<?>[] CONSTRUCTOR_SIGNATURE = - new Class<?>[] {Context.class, AttributeSet.class}; - - private static final HashMap<String, Constructor<?>> sConstructorMap = new HashMap<>(); - - /* non-static section */ - - // Array used to contain the constructor arguments (Context, AttributeSet), to avoid allocating - // a new array for creation of every item. - private final Object[] mTempConstructorArgs = new Object[2]; - - @Nullable - private String mDefaultPackage; - - @NonNull - private final Context mContext; - - /** - * Create a new inflater instance associated with a particular Context. - * - * @param context The context used to resolve resource IDs. This context is also passed to the - * constructor of the items created as the first argument. - */ - protected ReflectionInflater(@NonNull Context context) { - super(context.getResources()); - mContext = context; + /* static section */ + + private static final Class<?>[] CONSTRUCTOR_SIGNATURE = + new Class<?>[] {Context.class, AttributeSet.class}; + + private static final HashMap<String, Constructor<?>> constructorMap = new HashMap<>(); + + /* non-static section */ + + // Array used to contain the constructor arguments (Context, AttributeSet), to avoid allocating + // a new array for creation of every item. + private final Object[] tempConstructorArgs = new Object[2]; + + @Nullable private String defaultPackage; + + @NonNull private final Context context; + + /** + * Create a new inflater instance associated with a particular Context. + * + * @param context The context used to resolve resource IDs. This context is also passed to the + * constructor of the items created as the first argument. + */ + protected ReflectionInflater(@NonNull Context context) { + super(context.getResources()); + this.context = context; + } + + @NonNull + public Context getContext() { + return context; + } + + /** + * Instantiate the class by name. This attempts to instantiate class of the given {@code name} + * found in this inflater's ClassLoader. + * + * @param tagName The full name of the class to be instantiated. + * @param attrs The XML attributes supplied for this instance. + * @return The newly instantiated item. + */ + @NonNull + public final T createItem(String tagName, String prefix, AttributeSet attrs) { + String qualifiedName = tagName; + if (prefix != null && qualifiedName.indexOf('.') == -1) { + qualifiedName = prefix.concat(qualifiedName); } + @SuppressWarnings("unchecked") // qualifiedName should correspond to a subclass of T + Constructor<? extends T> constructor = + (Constructor<? extends T>) constructorMap.get(qualifiedName); - @NonNull - public Context getContext() { - return mContext; - } - - /** - * Instantiate the class by name. This attempts to instantiate class of the given {@code name} - * found in this inflater's ClassLoader. - * - * @param tagName The full name of the class to be instantiated. - * @param attrs The XML attributes supplied for this instance. - * - * @return The newly instantiated item. - */ - @NonNull - public final T createItem(String tagName, String prefix, AttributeSet attrs) { - String qualifiedName = tagName; - if (prefix != null && qualifiedName.indexOf('.') == -1) { - qualifiedName = prefix.concat(qualifiedName); - } + try { + if (constructor == null) { + // Class not found in the cache, see if it's real, and try to add it @SuppressWarnings("unchecked") // qualifiedName should correspond to a subclass of T - Constructor<? extends T> constructor = - (Constructor<? extends T>) sConstructorMap.get(qualifiedName); - - try { - if (constructor == null) { - // Class not found in the cache, see if it's real, and try to add it - @SuppressWarnings("unchecked") // qualifiedName should correspond to a subclass of T - Class<? extends T> clazz = - (Class<? extends T>) mContext.getClassLoader().loadClass(qualifiedName); - constructor = clazz.getConstructor(CONSTRUCTOR_SIGNATURE); - constructor.setAccessible(true); - sConstructorMap.put(tagName, constructor); - } - - mTempConstructorArgs[0] = mContext; - mTempConstructorArgs[1] = attrs; - final T item = constructor.newInstance(mTempConstructorArgs); - mTempConstructorArgs[0] = null; - mTempConstructorArgs[1] = null; - return item; - } catch (Exception e) { - throw new InflateException(attrs.getPositionDescription() - + ": Error inflating class " + qualifiedName, e); - } - } - - @Override - protected T onCreateItem(String tagName, AttributeSet attrs) { - return createItem(tagName, mDefaultPackage, attrs); - } - - /** - * 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(@Nullable String defaultPackage) { - mDefaultPackage = defaultPackage; - } - - /** - * Returns the default package, or null if it is not set. - * - * @see #setDefaultPackage(String) - * @return The default package. - */ - @Nullable - public String getDefaultPackage() { - return mDefaultPackage; + Class<? extends T> clazz = + (Class<? extends T>) context.getClassLoader().loadClass(qualifiedName); + constructor = clazz.getConstructor(CONSTRUCTOR_SIGNATURE); + constructor.setAccessible(true); + constructorMap.put(tagName, constructor); + } + + tempConstructorArgs[0] = context; + tempConstructorArgs[1] = attrs; + final T item = constructor.newInstance(tempConstructorArgs); + tempConstructorArgs[0] = null; + tempConstructorArgs[1] = null; + return item; + } catch (Exception e) { + throw new InflateException( + attrs.getPositionDescription() + ": Error inflating class " + qualifiedName, e); } + } + + @Override + protected T onCreateItem(String tagName, AttributeSet attrs) { + return createItem(tagName, defaultPackage, attrs); + } + + /** + * 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(@Nullable String defaultPackage) { + this.defaultPackage = defaultPackage; + } + + /** + * Returns the default package, or null if it is not set. + * + * @see #setDefaultPackage(String) + * @return The default package. + */ + @Nullable + public String getDefaultPackage() { + return defaultPackage; + } } diff --git a/library/main/src/com/android/setupwizardlib/items/SimpleInflater.java b/library/main/src/com/android/setupwizardlib/items/SimpleInflater.java index 0b12aca..767362c 100644 --- a/library/main/src/com/android/setupwizardlib/items/SimpleInflater.java +++ b/library/main/src/com/android/setupwizardlib/items/SimpleInflater.java @@ -18,18 +18,15 @@ package com.android.setupwizardlib.items; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import androidx.annotation.NonNull; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import android.view.InflateException; - -import androidx.annotation.NonNull; - +import java.io.IOException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; - /** * A simple XML inflater, which takes care of moving the parser to the correct position. Subclasses * need to implement {@link #onCreateItem(String, AttributeSet)} to create an object representation @@ -40,154 +37,154 @@ import java.io.IOException; */ public abstract class SimpleInflater<T> { - private static final String TAG = "SimpleInflater"; - private static final boolean DEBUG = false; - - protected final Resources mResources; - - /** - * Create a new inflater instance associated with a particular Resources bundle. - * - * @param resources The Resources class used to resolve given resource IDs. - */ - protected SimpleInflater(@NonNull Resources resources) { - mResources = resources; + private static final String TAG = "SimpleInflater"; + private static final boolean DEBUG = false; + + protected final Resources mResources; + + /** + * Create a new inflater instance associated with a particular Resources bundle. + * + * @param resources The Resources class used to resolve given resource IDs. + */ + protected SimpleInflater(@NonNull Resources resources) { + mResources = resources; + } + + public Resources getResources() { + return mResources; + } + + /** + * Inflate a new hierarchy from the specified XML resource. Throws InflaterException if there is + * an error. + * + * @param resId ID for an XML resource to load (e.g. <code>R.xml.my_xml</code>) + * @return The root of the inflated hierarchy. + */ + public T inflate(int resId) { + XmlResourceParser parser = getResources().getXml(resId); + try { + return inflate(parser); + } finally { + parser.close(); } - - public Resources getResources() { - return mResources; - } - - /** - * Inflate a new hierarchy from the specified XML resource. Throws InflaterException if there is - * an error. - * - * @param resId ID for an XML resource to load (e.g. <code>R.xml.my_xml</code>) - * @return The root of the inflated hierarchy. - */ - public T inflate(int resId) { - XmlResourceParser parser = getResources().getXml(resId); - try { - return inflate(parser); - } finally { - parser.close(); - } - } - - /** - * Inflate a new hierarchy from the specified XML node. Throws InflaterException if there is an - * error. - * <p> - * <em><strong>Important</strong></em> 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. - * @return The root of the inflated hierarchy. - */ - public T inflate(XmlPullParser parser) { - final AttributeSet attrs = Xml.asAttributeSet(parser); - T createdItem; - - try { - // Look for the root node. - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - // continue - } - - if (type != XmlPullParser.START_TAG) { - throw new InflateException(parser.getPositionDescription() - + ": No start tag found!"); - } - - createdItem = createItemFromTag(parser.getName(), attrs); - - rInflate(parser, createdItem, attrs); - } catch (XmlPullParserException e) { - throw new InflateException(e.getMessage(), e); - } catch (IOException e) { - throw new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); - } - - return createdItem; + } + + /** + * Inflate a new hierarchy from the specified XML node. Throws InflaterException if there is an + * error. + * + * <p><em><strong>Important</strong></em> 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. + * @return The root of the inflated hierarchy. + */ + public T inflate(XmlPullParser parser) { + final AttributeSet attrs = Xml.asAttributeSet(parser); + T createdItem; + + try { + // Look for the root node. + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // continue + } + + if (type != XmlPullParser.START_TAG) { + throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); + } + + createdItem = createItemFromTag(parser.getName(), attrs); + + rInflate(parser, createdItem, attrs); + } catch (XmlPullParserException e) { + throw new InflateException(e.getMessage(), e); + } catch (IOException e) { + throw new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); } - /** - * This routine is responsible for creating the correct subclass of item - * given the xml element name. - * - * @param tagName The XML tag name for the item to be created. - * @param attrs An AttributeSet of attributes to apply to the item. - * @return The item created. - */ - protected abstract T onCreateItem(String tagName, AttributeSet attrs); - - private T createItemFromTag(String name, AttributeSet attrs) { - try { - T item = onCreateItem(name, attrs); - if (DEBUG) Log.v(TAG, item + " created for <" + name + ">"); - return item; - } catch (InflateException e) { - throw e; - } catch (Exception e) { - throw new InflateException(attrs.getPositionDescription() - + ": Error inflating class " + name, e); - } + return createdItem; + } + + /** + * This routine is responsible for creating the correct subclass of item given the xml element + * name. + * + * @param tagName The XML tag name for the item to be created. + * @param attrs An AttributeSet of attributes to apply to the item. + * @return The item created. + */ + protected abstract T onCreateItem(String tagName, AttributeSet attrs); + + private T createItemFromTag(String name, AttributeSet attrs) { + try { + T item = onCreateItem(name, attrs); + if (DEBUG) { + Log.v(TAG, item + " created for <" + name + ">"); + } + return item; + } catch (InflateException e) { + throw e; + } catch (Exception e) { + throw new InflateException( + attrs.getPositionDescription() + ": Error inflating class " + name, e); } + } - /** - * 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 parent, 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) { + /** + * 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 parent, final AttributeSet attrs) + throws XmlPullParserException, IOException { + final int depth = parser.getDepth(); - if (type != XmlPullParser.START_TAG) { - continue; - } + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { - if (onInterceptCreateItem(parser, parent, attrs)) { - continue; - } + if (type != XmlPullParser.START_TAG) { + continue; + } - String name = parser.getName(); - T item = createItemFromTag(name, attrs); + if (onInterceptCreateItem(parser, parent, attrs)) { + continue; + } - onAddChildItem(parent, item); + String name = parser.getName(); + T item = createItemFromTag(name, attrs); - rInflate(parser, item, attrs); - } - } + onAddChildItem(parent, item); - /** - * Whether item creation should be intercepted to perform custom handling on the parser rather - * than creating an object from it. This is used in rare cases where a tag doesn't correspond - * to creation of an object. - * - * 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. That is, this method is responsible for parsing the element - * at the given position together with all of its child tags. - * - * Note that parsing of the root tag cannot be intercepted. - * - * @param parser XML dom node containing the description of the hierarchy. - * @param parent The item that should be the parent of whatever you create. - * @param attrs An AttributeSet of attributes to apply to the item. - * @return True to continue parsing without calling {@link #onCreateItem(String, AttributeSet)}, - * or false if this inflater should proceed to create an item. - */ - protected boolean onInterceptCreateItem(XmlPullParser parser, T parent, AttributeSet attrs) - throws XmlPullParserException { - return false; + rInflate(parser, item, attrs); } - - protected abstract void onAddChildItem(T parent, T child); + } + + /** + * Whether item creation should be intercepted to perform custom handling on the parser rather + * than creating an object from it. This is used in rare cases where a tag doesn't correspond to + * creation of an object. + * + * <p>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. That is, this method is responsible for parsing the element at + * the given position together with all of its child tags. + * + * <p>Note that parsing of the root tag cannot be intercepted. + * + * @param parser XML dom node containing the description of the hierarchy. + * @param parent The item that should be the parent of whatever you create. + * @param attrs An AttributeSet of attributes to apply to the item. + * @return True to continue parsing without calling {@link #onCreateItem(String, AttributeSet)}, + * or false if this inflater should proceed to create an item. + */ + protected boolean onInterceptCreateItem(XmlPullParser parser, T parent, AttributeSet attrs) + throws XmlPullParserException { + return false; + } + + protected abstract void onAddChildItem(T parent, T child); } diff --git a/library/main/src/com/android/setupwizardlib/span/LinkSpan.java b/library/main/src/com/android/setupwizardlib/span/LinkSpan.java index 3dd783b..49ce1b9 100644 --- a/library/main/src/com/android/setupwizardlib/span/LinkSpan.java +++ b/library/main/src/com/android/setupwizardlib/span/LinkSpan.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.graphics.Typeface; import android.os.Build; +import androidx.annotation.Nullable; import android.text.Selection; import android.text.Spannable; import android.text.TextPaint; @@ -28,126 +29,120 @@ import android.util.Log; import android.view.View; import android.widget.TextView; -import androidx.annotation.Nullable; - /** * A clickable span that will listen for click events and send it back to the context. To use this - * class, implement {@link OnLinkClickListener} in your TextView, or use - * {@link com.android.setupwizardlib.view.RichTextView#setOnClickListener(View.OnClickListener)}. + * class, implement {@link OnLinkClickListener} in your TextView, or use {@link + * com.android.setupwizardlib.view.RichTextView#setOnClickListener(View.OnClickListener)}. * - * <p />Note on accessibility: For TalkBack to be able to traverse and interact with the links, you + * <p>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. - */ + /* + * 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 */ + /* static section */ - private static final String TAG = "LinkSpan"; + private static final String TAG = "LinkSpan"; - private static final Typeface TYPEFACE_MEDIUM = - Typeface.create("sans-serif-medium", Typeface.NORMAL); + private static final Typeface TYPEFACE_MEDIUM = + Typeface.create("sans-serif-medium", Typeface.NORMAL); - /** - * @deprecated Use {@link OnLinkClickListener} - */ - @Deprecated - public interface OnClickListener { - void onClick(LinkSpan span); - } + /** @deprecated Use {@link OnLinkClickListener} */ + @Deprecated + public interface OnClickListener { + void onClick(LinkSpan span); + } + + /** + * Listener that is invoked when a link span is clicked. If the containing view of this span + * implements this interface, this will be invoked when the link is clicked. + */ + public interface OnLinkClickListener { /** - * Listener that is invoked when a link span is clicked. If the containing view of this span - * implements this interface, this will be invoked when the link is clicked. + * Called when a link has been clicked. + * + * @param span The span that was clicked. + * @return True if the click was handled, stopping further propagation of the click event. */ - public interface OnLinkClickListener { - - /** - * Called when a link has been clicked. - * - * @param span The span that was clicked. - * @return True if the click was handled, stopping further propagation of the click event. - */ - boolean onLinkClick(LinkSpan span); + boolean onLinkClick(LinkSpan span); + } + + /* non-static section */ + + private final String id; + + public LinkSpan(String id) { + this.id = id; + } + + @Override + public void onClick(View view) { + if (dispatchClick(view)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // Prevent the touch event from bubbling up to the parent views. + view.cancelPendingInputEvents(); + } + } else { + Log.w(TAG, "Dropping click event. No listener attached."); } - - /* non-static section */ - - private final String mId; - - public LinkSpan(String id) { - mId = id; + if (view instanceof TextView) { + // Remove the highlight effect when the click happens by clearing the selection + CharSequence text = ((TextView) view).getText(); + if (text instanceof Spannable) { + Selection.setSelection((Spannable) text, 0); + } } + } - @Override - public void onClick(View view) { - if (dispatchClick(view)) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - // Prevent the touch event from bubbling up to the parent views. - view.cancelPendingInputEvents(); - } - } else { - Log.w(TAG, "Dropping click event. No listener attached."); - } - if (view instanceof TextView) { - // Remove the highlight effect when the click happens by clearing the selection - CharSequence text = ((TextView) view).getText(); - if (text instanceof Spannable) { - Selection.setSelection((Spannable) text, 0); - } - } + private boolean dispatchClick(View view) { + boolean handled = false; + if (view instanceof OnLinkClickListener) { + handled = ((OnLinkClickListener) view).onLinkClick(this); } - - private boolean dispatchClick(View view) { - boolean handled = false; - if (view instanceof OnLinkClickListener) { - handled = ((OnLinkClickListener) view).onLinkClick(this); - } - if (!handled) { - final OnClickListener listener = getLegacyListenerFromContext(view.getContext()); - if (listener != null) { - listener.onClick(this); - handled = true; - } - } - return handled; + if (!handled) { + final OnClickListener listener = getLegacyListenerFromContext(view.getContext()); + if (listener != null) { + listener.onClick(this); + handled = true; + } } - - /** - * @deprecated Deprecated together with {@link OnClickListener} - */ - @Nullable - @Deprecated - private OnClickListener getLegacyListenerFromContext(@Nullable Context context) { - while (true) { - if (context instanceof OnClickListener) { - return (OnClickListener) context; - } else if (context instanceof ContextWrapper) { - // Unwrap any context wrapper, in base the base context implements onClickListener. - // ContextWrappers cannot have circular base contexts, so at some point this will - // reach the one of the other cases and return. - context = ((ContextWrapper) context).getBaseContext(); - } else { - return null; - } - } - } - - @Override - public void updateDrawState(TextPaint drawState) { - super.updateDrawState(drawState); - drawState.setUnderlineText(false); - drawState.setTypeface(TYPEFACE_MEDIUM); - } - - public String getId() { - return mId; + return handled; + } + + /** @deprecated Deprecated together with {@link OnClickListener} */ + @Nullable + @Deprecated + private OnClickListener getLegacyListenerFromContext(@Nullable Context context) { + while (true) { + if (context instanceof OnClickListener) { + return (OnClickListener) context; + } else if (context instanceof ContextWrapper) { + // Unwrap any context wrapper, in base the base context implements onClickListener. + // ContextWrappers cannot have circular base contexts, so at some point this will + // reach the one of the other cases and return. + context = ((ContextWrapper) context).getBaseContext(); + } else { + return null; + } } + } + + @Override + public void updateDrawState(TextPaint drawState) { + super.updateDrawState(drawState); + drawState.setUnderlineText(false); + drawState.setTypeface(TYPEFACE_MEDIUM); + } + + public String getId() { + return id; + } } diff --git a/library/main/src/com/android/setupwizardlib/span/SpanHelper.java b/library/main/src/com/android/setupwizardlib/span/SpanHelper.java index d75e338..4037e6d 100644 --- a/library/main/src/com/android/setupwizardlib/span/SpanHelper.java +++ b/library/main/src/com/android/setupwizardlib/span/SpanHelper.java @@ -23,14 +23,14 @@ import android.text.Spannable; */ 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); - } + /** + * 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/library/main/src/com/android/setupwizardlib/template/ButtonFooterMixin.java b/library/main/src/com/android/setupwizardlib/template/ButtonFooterMixin.java index a8580a3..c872f6b 100644 --- a/library/main/src/com/android/setupwizardlib/template/ButtonFooterMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/ButtonFooterMixin.java @@ -18,6 +18,10 @@ package com.android.setupwizardlib.template; import android.annotation.SuppressLint; import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.StyleRes; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -25,12 +29,6 @@ import android.view.ViewStub; import android.widget.Button; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.annotation.StyleRes; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; @@ -41,134 +39,131 @@ import com.android.setupwizardlib.TemplateLayout; */ public class ButtonFooterMixin implements Mixin { - private final Context mContext; - - @Nullable - private final ViewStub mFooterStub; - - private LinearLayout mButtonContainer; - - /** - * Create a mixin for managing buttons on the footer. - * - * @param layout The {@link TemplateLayout} containing this mixin. - */ - public ButtonFooterMixin(TemplateLayout layout) { - mContext = layout.getContext(); - mFooterStub = (ViewStub) layout.findManagedViewById(R.id.suw_layout_footer); - } - - /** - * Add a button with the given text and style. Common style for GLIF are - * {@code SuwGlifButton.Primary} and {@code SuwGlifButton.Secondary}. - * - * @param text The label for the button. - * @param theme Theme resource to be used for this button. Since this is applied as a theme, - * the resource will typically apply {@code android:buttonStyle} so it will be - * applied to the button as a style as well. - * - * @return The button that was created. - */ - public Button addButton(CharSequence text, @StyleRes int theme) { - Button button = createThemedButton(mContext, theme); - button.setText(text); - return addButton(button); - } - - /** - * Add a button with the given text and style. Common style for GLIF are - * {@code SuwGlifButton.Primary} and {@code SuwGlifButton.Secondary}. - * - * @param text The label for the button. - * @param theme Theme resource to be used for this button. Since this is applied as a theme, - * the resource will typically apply {@code android:buttonStyle} so it will be - * applied to the button as a style as well. - * - * @return The button that was created. - */ - public Button addButton(@StringRes int text, @StyleRes int theme) { - Button button = createThemedButton(mContext, theme); - button.setText(text); - return addButton(button); - } - - /** - * Add a button to the footer. - * - * @param button The button to be added to the footer. - * @return The button that was added. - */ - public Button addButton(Button button) { - final LinearLayout buttonContainer = ensureFooterInflated(); - buttonContainer.addView(button); - return button; - } - - /** - * Add a space to the footer. Spaces will share the remaining space of footer, so for example, - * [Button] [space] [Button] [space] [Button] will give you 3 buttons, left, center, and right - * aligned. - * - * @return The view that was used as space. - */ - public View addSpace() { - final LinearLayout buttonContainer = ensureFooterInflated(); - View space = new View(buttonContainer.getContext()); - space.setLayoutParams(new LayoutParams(0, 0, 1.0f)); - space.setVisibility(View.INVISIBLE); - buttonContainer.addView(space); - return space; + private final Context context; + + @Nullable private final ViewStub footerStub; + + private LinearLayout buttonContainer; + + /** + * Create a mixin for managing buttons on the footer. + * + * @param layout The {@link TemplateLayout} containing this mixin. + */ + public ButtonFooterMixin(TemplateLayout layout) { + context = layout.getContext(); + footerStub = (ViewStub) layout.findManagedViewById(R.id.suw_layout_footer); + } + + /** + * Add a button with the given text and style. Common style for GLIF are {@code + * SuwGlifButton.Primary} and {@code SuwGlifButton.Secondary}. + * + * @param text The label for the button. + * @param theme Theme resource to be used for this button. Since this is applied as a theme, the + * resource will typically apply {@code android:buttonStyle} so it will be applied to the + * button as a style as well. + * @return The button that was created. + */ + public Button addButton(CharSequence text, @StyleRes int theme) { + Button button = createThemedButton(context, theme); + button.setText(text); + return addButton(button); + } + + /** + * Add a button with the given text and style. Common style for GLIF are {@code + * SuwGlifButton.Primary} and {@code SuwGlifButton.Secondary}. + * + * @param text The label for the button. + * @param theme Theme resource to be used for this button. Since this is applied as a theme, the + * resource will typically apply {@code android:buttonStyle} so it will be applied to the + * button as a style as well. + * @return The button that was created. + */ + public Button addButton(@StringRes int text, @StyleRes int theme) { + Button button = createThemedButton(context, theme); + button.setText(text); + return addButton(button); + } + + /** + * Add a button to the footer. + * + * @param button The button to be added to the footer. + * @return The button that was added. + */ + public Button addButton(Button button) { + final LinearLayout buttonContainer = ensureFooterInflated(); + buttonContainer.addView(button); + return button; + } + + /** + * Add a space to the footer. Spaces will share the remaining space of footer, so for example, + * [Button] [space] [Button] [space] [Button] will give you 3 buttons, left, center, and right + * aligned. + * + * @return The view that was used as space. + */ + public View addSpace() { + final LinearLayout buttonContainer = ensureFooterInflated(); + View space = new View(buttonContainer.getContext()); + space.setLayoutParams(new LayoutParams(0, 0, 1.0f)); + space.setVisibility(View.INVISIBLE); + buttonContainer.addView(space); + return space; + } + + /** + * Remove a previously added button. + * + * @param button The button to be removed. + */ + public void removeButton(Button button) { + if (buttonContainer != null) { + buttonContainer.removeView(button); } - - /** - * Remove a previously added button. - * - * @param button The button to be removed. - */ - public void removeButton(Button button) { - if (mButtonContainer != null) { - mButtonContainer.removeView(button); - } - } - - /** - * Remove a previously added space. - * - * @param space The space to be removed. - */ - public void removeSpace(View space) { - if (mButtonContainer != null) { - mButtonContainer.removeView(space); - } + } + + /** + * Remove a previously added space. + * + * @param space The space to be removed. + */ + public void removeSpace(View space) { + if (buttonContainer != null) { + buttonContainer.removeView(space); } - - /** - * Remove all views, including spaces, from the footer. Note that if the footer container is - * already inflated, this will not remove the container itself. - */ - public void removeAllViews() { - if (mButtonContainer != null) { - mButtonContainer.removeAllViews(); - } + } + + /** + * Remove all views, including spaces, from the footer. Note that if the footer container is + * already inflated, this will not remove the container itself. + */ + public void removeAllViews() { + if (buttonContainer != null) { + buttonContainer.removeAllViews(); } - - @NonNull - private LinearLayout ensureFooterInflated() { - if (mButtonContainer == null) { - if (mFooterStub == null) { - throw new IllegalStateException("Footer stub is not found in this template"); - } - mFooterStub.setLayoutResource(R.layout.suw_glif_footer_button_bar); - mButtonContainer = (LinearLayout) mFooterStub.inflate(); - } - return mButtonContainer; - } - - @SuppressLint("InflateParams") - private Button createThemedButton(Context context, @StyleRes int theme) { - // Inflate a single button from XML, which when using support lib, will take advantage of - // the injected layout inflater and give us AppCompatButton instead. - LayoutInflater inflater = LayoutInflater.from(new ContextThemeWrapper(context, theme)); - return (Button) inflater.inflate(R.layout.suw_button, null, false); + } + + @NonNull + private LinearLayout ensureFooterInflated() { + if (buttonContainer == null) { + if (footerStub == null) { + throw new IllegalStateException("Footer stub is not found in this template"); + } + footerStub.setLayoutResource(R.layout.suw_glif_footer_button_bar); + buttonContainer = (LinearLayout) footerStub.inflate(); } + return buttonContainer; + } + + @SuppressLint("InflateParams") + private Button createThemedButton(Context context, @StyleRes int theme) { + // Inflate a single button from XML, which when using support lib, will take advantage of + // the injected layout inflater and give us AppCompatButton instead. + LayoutInflater inflater = LayoutInflater.from(new ContextThemeWrapper(context, theme)); + return (Button) inflater.inflate(R.layout.suw_button, null, false); + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ColoredHeaderMixin.java b/library/main/src/com/android/setupwizardlib/template/ColoredHeaderMixin.java index ccc5aad..acdaa67 100644 --- a/library/main/src/com/android/setupwizardlib/template/ColoredHeaderMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/ColoredHeaderMixin.java @@ -20,54 +20,51 @@ import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.util.AttributeSet; import android.widget.TextView; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; /** * A {@link Mixin} displaying a header text that can be set to different colors. This Mixin is - * registered to the tempalte using HeaderMixin.class, and can be retrieved using: - * {@code (ColoredHeaderMixin) templateLayout.getMixin(HeaderMixin.class}. + * registered to the template using HeaderMixin.class, and can be retrieved using: {@code + * (ColoredHeaderMixin) templateLayout.getMixin(HeaderMixin.class}. */ public class ColoredHeaderMixin extends HeaderMixin { - /** - * {@inheritDoc} - */ - public ColoredHeaderMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { - super(layout, attrs, defStyleAttr); - - final TypedArray a = layout.getContext().obtainStyledAttributes( - attrs, R.styleable.SuwColoredHeaderMixin, defStyleAttr, 0); + /** {@inheritDoc} */ + public ColoredHeaderMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { + super(layout, attrs, defStyleAttr); - // Set the header color - final ColorStateList headerColor = - a.getColorStateList(R.styleable.SuwColoredHeaderMixin_suwHeaderColor); - if (headerColor != null) { - setColor(headerColor); - } + final TypedArray a = + layout + .getContext() + .obtainStyledAttributes(attrs, R.styleable.SuwColoredHeaderMixin, defStyleAttr, 0); - a.recycle(); + // Set the header color + final ColorStateList headerColor = + a.getColorStateList(R.styleable.SuwColoredHeaderMixin_suwHeaderColor); + if (headerColor != null) { + setColor(headerColor); } - /** - * Sets the color of the header text. This can also be set via XML using - * {@code app:suwHeaderColor}. - * - * @param color The text color of the header. - */ - public void setColor(ColorStateList color) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setTextColor(color); - } - } + a.recycle(); + } - /** - * @return The current text color of the header. - */ - public ColorStateList getColor() { - final TextView titleView = getTextView(); - return titleView != null ? titleView.getTextColors() : null; + /** + * Sets the color of the header text. This can also be set via XML using {@code + * app:suwHeaderColor}. + * + * @param color The text color of the header. + */ + public void setColor(ColorStateList color) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setTextColor(color); } + } + + /** @return The current text color of the header. */ + public ColorStateList getColor() { + final TextView titleView = getTextView(); + return titleView != null ? titleView.getTextColors() : null; + } } diff --git a/library/main/src/com/android/setupwizardlib/template/HeaderMixin.java b/library/main/src/com/android/setupwizardlib/template/HeaderMixin.java index 604de9a..7d7fb4a 100644 --- a/library/main/src/com/android/setupwizardlib/template/HeaderMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/HeaderMixin.java @@ -17,80 +17,74 @@ package com.android.setupwizardlib.template; import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.widget.TextView; - import androidx.annotation.AttrRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import android.util.AttributeSet; +import android.widget.TextView; import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; -/** - * A {@link Mixin} for setting and getting the header text. - */ +/** A {@link Mixin} for setting and getting the header text. */ public class HeaderMixin implements Mixin { - private TemplateLayout mTemplateLayout; - - /** - * @param layout The layout this Mixin belongs to. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. - */ - public HeaderMixin(@NonNull TemplateLayout layout, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr) { - mTemplateLayout = layout; + private final TemplateLayout templateLayout; - final TypedArray a = layout.getContext().obtainStyledAttributes( - attrs, R.styleable.SuwHeaderMixin, defStyleAttr, 0); + /** + * @param layout The layout this Mixin belongs to. + * @param attrs XML attributes given to the layout. + * @param defStyleAttr The default style attribute as given to the constructor of the layout. + */ + public HeaderMixin( + @NonNull TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + templateLayout = layout; - // Set the header text - final CharSequence headerText = a.getText(R.styleable.SuwHeaderMixin_suwHeaderText); - if (headerText != null) { - setText(headerText); - } + final TypedArray a = + layout + .getContext() + .obtainStyledAttributes(attrs, R.styleable.SuwHeaderMixin, defStyleAttr, 0); - a.recycle(); + // Set the header text + final CharSequence headerText = a.getText(R.styleable.SuwHeaderMixin_suwHeaderText); + if (headerText != null) { + setText(headerText); } - /** - * @return The TextView displaying the header. - */ - public TextView getTextView() { - return (TextView) mTemplateLayout.findManagedViewById(R.id.suw_layout_title); - } + a.recycle(); + } - /** - * Sets the header text. This can also be set via the XML attribute {@code app:suwHeaderText}. - * - * @param title The resource ID of the text to be set as header. - */ - public void setText(int title) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setText(title); - } - } + /** @return The TextView displaying the header. */ + public TextView getTextView() { + return (TextView) templateLayout.findManagedViewById(R.id.suw_layout_title); + } - /** - * Sets the header text. This can also be set via the XML attribute {@code app:suwHeaderText}. - * - * @param title The text to be set as header. - */ - public void setText(CharSequence title) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setText(title); - } + /** + * Sets the header text. This can also be set via the XML attribute {@code app:suwHeaderText}. + * + * @param title The resource ID of the text to be set as header. + */ + public void setText(int title) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setText(title); } + } - /** - * @return The current header text. - */ - public CharSequence getText() { - final TextView titleView = getTextView(); - return titleView != null ? titleView.getText() : null; + /** + * Sets the header text. This can also be set via the XML attribute {@code app:suwHeaderText}. + * + * @param title The text to be set as header. + */ + public void setText(CharSequence title) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setText(title); } + } + + /** @return The current header text. */ + public CharSequence getText() { + final TextView titleView = getTextView(); + return titleView != null ? titleView.getText() : null; + } } diff --git a/library/main/src/com/android/setupwizardlib/template/IconMixin.java b/library/main/src/com/android/setupwizardlib/template/IconMixin.java index 5f5c915..e28f67d 100644 --- a/library/main/src/com/android/setupwizardlib/template/IconMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/IconMixin.java @@ -19,100 +19,88 @@ package com.android.setupwizardlib.template; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import androidx.annotation.DrawableRes; import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; - -import androidx.annotation.DrawableRes; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; -/** - * A {@link Mixin} for setting an icon on the template layout. - */ +/** A {@link Mixin} for setting an icon on the template layout. */ public class IconMixin implements Mixin { - private TemplateLayout mTemplateLayout; - - /** - * @param layout The template layout that this Mixin is a part of. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. - */ - public IconMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { - mTemplateLayout = layout; - final Context context = layout.getContext(); - - final TypedArray a = - context.obtainStyledAttributes(attrs, R.styleable.SuwIconMixin, defStyleAttr, 0); + private final TemplateLayout templateLayout; - final @DrawableRes int icon = a.getResourceId(R.styleable.SuwIconMixin_android_icon, 0); - if (icon != 0) { - setIcon(icon); - } + /** + * @param layout The template layout that this Mixin is a part of. + * @param attrs XML attributes given to the layout. + * @param defStyleAttr The default style attribute as given to the constructor of the layout. + */ + public IconMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { + templateLayout = layout; + final Context context = layout.getContext(); - a.recycle(); - } - - /** - * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. - * - * @param icon A drawable icon. - */ - public void setIcon(Drawable icon) { - final ImageView iconView = getView(); - if (iconView != null) { - iconView.setImageDrawable(icon); - iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE); - } - } + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwIconMixin, defStyleAttr, 0); - /** - * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. - * - * @param icon A drawable icon resource. - */ - public void setIcon(@DrawableRes int icon) { - final ImageView iconView = getView(); - if (iconView != null) { - // Note: setImageResource on the ImageView is overridden in AppCompatImageView for - // support lib users, which enables vector drawable compat to work on versions pre-L. - iconView.setImageResource(icon); - iconView.setVisibility(icon != 0 ? View.VISIBLE : View.GONE); - } + final @DrawableRes int icon = a.getResourceId(R.styleable.SuwIconMixin_android_icon, 0); + if (icon != 0) { + setIcon(icon); } - /** - * @return The icon previously set in {@link #setIcon(Drawable)} or {@code android:icon} - */ - public Drawable getIcon() { - final ImageView iconView = getView(); - return iconView != null ? iconView.getDrawable() : null; + a.recycle(); + } + + /** + * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. + * + * @param icon A drawable icon. + */ + public void setIcon(Drawable icon) { + final ImageView iconView = getView(); + if (iconView != null) { + iconView.setImageDrawable(icon); + iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE); } - - /** - * Sets the content description of the icon view - */ - public void setContentDescription(CharSequence description) { - final ImageView iconView = getView(); - if (iconView != null) { - iconView.setContentDescription(description); - } + } + + /** + * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. + * + * @param icon A drawable icon resource. + */ + public void setIcon(@DrawableRes int icon) { + final ImageView iconView = getView(); + if (iconView != null) { + // Note: setImageResource on the ImageView is overridden in AppCompatImageView for + // support lib users, which enables vector drawable compat to work on versions pre-L. + iconView.setImageResource(icon); + iconView.setVisibility(icon != 0 ? View.VISIBLE : View.GONE); } - - /** - * @return The content description of the icon view - */ - public CharSequence getContentDescription() { - final ImageView iconView = getView(); - return iconView != null ? iconView.getContentDescription() : null; - } - - /** - * @return The ImageView responsible for displaying the icon. - */ - protected ImageView getView() { - return (ImageView) mTemplateLayout.findManagedViewById(R.id.suw_layout_icon); + } + + /** @return The icon previously set in {@link #setIcon(Drawable)} or {@code android:icon} */ + public Drawable getIcon() { + final ImageView iconView = getView(); + return iconView != null ? iconView.getDrawable() : null; + } + + /** Sets the content description of the icon view */ + public void setContentDescription(CharSequence description) { + final ImageView iconView = getView(); + if (iconView != null) { + iconView.setContentDescription(description); } + } + + /** @return The content description of the icon view */ + public CharSequence getContentDescription() { + final ImageView iconView = getView(); + return iconView != null ? iconView.getContentDescription() : null; + } + + /** @return The ImageView responsible for displaying the icon. */ + protected ImageView getView() { + return (ImageView) templateLayout.findManagedViewById(R.id.suw_layout_icon); + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ListMixin.java b/library/main/src/com/android/setupwizardlib/template/ListMixin.java index cbc29b5..066141e 100644 --- a/library/main/src/com/android/setupwizardlib/template/ListMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/ListMixin.java @@ -21,16 +21,14 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION_CODES; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.widget.HeaderViewListAdapter; import android.widget.ListAdapter; import android.widget.ListView; - -import androidx.annotation.AttrRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.items.ItemAdapter; @@ -38,188 +36,170 @@ import com.android.setupwizardlib.items.ItemGroup; import com.android.setupwizardlib.items.ItemInflater; import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper; -/** - * A {@link Mixin} for interacting with ListViews. - */ +/** A {@link Mixin} for interacting with ListViews. */ public class ListMixin implements Mixin { - private TemplateLayout mTemplateLayout; - - @Nullable - private ListView mListView; - - private Drawable mDivider; - private Drawable mDefaultDivider; - - private int mDividerInsetStart; - private int mDividerInsetEnd; - - /** - * @param layout The layout this mixin belongs to. - */ - public ListMixin(@NonNull TemplateLayout layout, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr) { - mTemplateLayout = layout; - - final Context context = layout.getContext(); - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.SuwListMixin, defStyleAttr, 0); - - final int entries = a.getResourceId(R.styleable.SuwListMixin_android_entries, 0); - if (entries != 0) { - final ItemGroup inflated = - (ItemGroup) new ItemInflater(context).inflate(entries); - setAdapter(new ItemAdapter(inflated)); - } - int dividerInset = - a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInset, -1); - if (dividerInset != -1) { - setDividerInset(dividerInset); - } else { - int dividerInsetStart = - a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetStart, 0); - int dividerInsetEnd = - a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetEnd, 0); - setDividerInsets(dividerInsetStart, dividerInsetEnd); - } - a.recycle(); - } + private final TemplateLayout templateLayout; - /** - * @return The list view contained in the layout, as marked by {@code @android:id/list}. This - * will return {@code null} if the list doesn't exist in the layout. - */ - public ListView getListView() { - return getListViewInternal(); - } + @Nullable private ListView listView; - // Client code can assume getListView() will not be null if they know their template contains - // the list, but this mixin cannot. Any usages of getListView in this mixin needs null checks. - @Nullable - private ListView getListViewInternal() { - if (mListView == null) { - final View list = mTemplateLayout.findManagedViewById(android.R.id.list); - if (list instanceof ListView) { - mListView = (ListView) list; - } - } - return mListView; - } + private Drawable divider; + private Drawable defaultDivider; - /** - * List mixin needs to update the dividers if the layout direction has changed. This method - * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template - * is called. - */ - public void onLayout() { - if (mDivider == null) { - // Update divider in case layout direction has just been resolved - updateDivider(); - } - } + private int dividerInsetStart; + private int dividerInsetEnd; - /** - * Gets the adapter of the list view in this layout. If the adapter is a HeaderViewListAdapter, - * this method will unwrap it and return the underlying adapter. - * - * @return The adapter, or {@code null} if there is no list, or if the list has no adapter. - */ - public ListAdapter getAdapter() { - final ListView listView = getListViewInternal(); - if (listView != null) { - final ListAdapter adapter = listView.getAdapter(); - if (adapter instanceof HeaderViewListAdapter) { - return ((HeaderViewListAdapter) adapter).getWrappedAdapter(); - } - return adapter; - } - return null; - } + /** @param layout The layout this mixin belongs to. */ + public ListMixin( + @NonNull TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + templateLayout = layout; - /** - * Sets the adapter on the list view in this layout. - */ - public void setAdapter(ListAdapter adapter) { - final ListView listView = getListViewInternal(); - if (listView != null) { - listView.setAdapter(adapter); - } - } + final Context context = layout.getContext(); + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwListMixin, defStyleAttr, 0); - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - setDividerInsets(inset, 0); + final int entries = a.getResourceId(R.styleable.SuwListMixin_android_entries, 0); + if (entries != 0) { + final ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(entries); + setAdapter(new ItemAdapter(inflated)); } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start 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}. - * @param end The number of pixels to inset on the "end" side of the list divider. - */ - public void setDividerInsets(int start, int end) { - mDividerInsetStart = start; - mDividerInsetEnd = end; - updateDivider(); + int dividerInset = a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInset, -1); + if (dividerInset != -1) { + setDividerInset(dividerInset); + } else { + int dividerInsetStart = + a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetStart, 0); + int dividerInsetEnd = a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetEnd, 0); + setDividerInsets(dividerInsetStart, dividerInsetEnd); } - - /** - * @return The number of pixels inset on the start side of the divider. - * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. - */ - @Deprecated - public int getDividerInset() { - return getDividerInsetStart(); + a.recycle(); + } + + /** + * @return The list view contained in the layout, as marked by {@code @android:id/list}. This will + * return {@code null} if the list doesn't exist in the layout. + */ + public ListView getListView() { + return getListViewInternal(); + } + + // Client code can assume getListView() will not be null if they know their template contains + // the list, but this mixin cannot. Any usages of getListView in this mixin needs null checks. + @Nullable + private ListView getListViewInternal() { + if (listView == null) { + final View list = templateLayout.findManagedViewById(android.R.id.list); + if (list instanceof ListView) { + listView = (ListView) list; + } } - - /** - * @return The number of pixels inset on the start side of the divider. - */ - public int getDividerInsetStart() { - return mDividerInsetStart; + return listView; + } + + /** + * List mixin needs to update the dividers if the layout direction has changed. This method should + * be called when {@link View#onLayout(boolean, int, int, int, int)} of the template is called. + */ + public void onLayout() { + if (divider == null) { + // Update divider in case layout direction has just been resolved + updateDivider(); } - - /** - * @return The number of pixels inset on the end side of the divider. - */ - public int getDividerInsetEnd() { - return mDividerInsetEnd; + } + + /** + * Gets the adapter of the list view in this layout. If the adapter is a HeaderViewListAdapter, + * this method will unwrap it and return the underlying adapter. + * + * @return The adapter, or {@code null} if there is no list, or if the list has no adapter. + */ + public ListAdapter getAdapter() { + final ListView listView = getListViewInternal(); + if (listView != null) { + final ListAdapter adapter = listView.getAdapter(); + if (adapter instanceof HeaderViewListAdapter) { + return ((HeaderViewListAdapter) adapter).getWrappedAdapter(); + } + return adapter; } - - private void updateDivider() { - final ListView listView = getListViewInternal(); - if (listView == null) { - return; - } - boolean shouldUpdate = true; - if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - shouldUpdate = mTemplateLayout.isLayoutDirectionResolved(); - } - if (shouldUpdate) { - if (mDefaultDivider == null) { - mDefaultDivider = listView.getDivider(); - } - mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable( - mDefaultDivider, - mDividerInsetStart /* start */, - 0 /* top */, - mDividerInsetEnd /* end */, - 0 /* bottom */, - mTemplateLayout); - listView.setDivider(mDivider); - } + return null; + } + + /** Sets the adapter on the list view in this layout. */ + public void setAdapter(ListAdapter adapter) { + final ListView listView = getListViewInternal(); + if (listView != null) { + listView.setAdapter(adapter); } - - /** - * @return The drawable used as the divider. - */ - public Drawable getDivider() { - return mDivider; + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + setDividerInsets(inset, 0); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start 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}. + * @param end The number of pixels to inset on the "end" side of the list divider. + */ + public void setDividerInsets(int start, int end) { + dividerInsetStart = start; + dividerInsetEnd = end; + updateDivider(); + } + + /** + * @return The number of pixels inset on the start side of the divider. + * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. + */ + @Deprecated + public int getDividerInset() { + return getDividerInsetStart(); + } + + /** @return The number of pixels inset on the start side of the divider. */ + public int getDividerInsetStart() { + return dividerInsetStart; + } + + /** @return The number of pixels inset on the end side of the divider. */ + public int getDividerInsetEnd() { + return dividerInsetEnd; + } + + private void updateDivider() { + final ListView listView = getListViewInternal(); + if (listView == null) { + return; } + boolean shouldUpdate = true; + if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + shouldUpdate = templateLayout.isLayoutDirectionResolved(); + } + if (shouldUpdate) { + if (defaultDivider == null) { + defaultDivider = listView.getDivider(); + } + divider = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + defaultDivider, + dividerInsetStart /* start */, + 0 /* top */, + dividerInsetEnd /* end */, + 0 /* bottom */, + templateLayout); + listView.setDivider(divider); + } + } + + /** @return The drawable used as the divider. */ + public Drawable getDivider() { + return divider; + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java b/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java index faea305..8614ff4 100644 --- a/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java +++ b/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java @@ -16,73 +16,67 @@ package com.android.setupwizardlib.template; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.Log; import android.widget.AbsListView; import android.widget.ListAdapter; import android.widget.ListView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate; /** - * {@link ScrollHandlingDelegate} which analyzes scroll events from {@link ListView} and - * notifies {@link RequireScrollMixin} about scrollability changes. + * {@link ScrollHandlingDelegate} which analyzes scroll events from {@link ListView} and notifies + * {@link RequireScrollMixin} about scrollability changes. */ -public class ListViewScrollHandlingDelegate implements ScrollHandlingDelegate, - AbsListView.OnScrollListener { +public class ListViewScrollHandlingDelegate + implements ScrollHandlingDelegate, AbsListView.OnScrollListener { - private static final String TAG = "ListViewDelegate"; + private static final String TAG = "ListViewDelegate"; - private static final int SCROLL_DURATION = 500; + private static final int SCROLL_DURATION = 500; - @NonNull - private final RequireScrollMixin mRequireScrollMixin; + @NonNull private final RequireScrollMixin requireScrollMixin; - @Nullable - private final ListView mListView; + @Nullable private final ListView listView; - public ListViewScrollHandlingDelegate( - @NonNull RequireScrollMixin requireScrollMixin, - @Nullable ListView listView) { - mRequireScrollMixin = requireScrollMixin; - mListView = listView; - } + public ListViewScrollHandlingDelegate( + @NonNull RequireScrollMixin requireScrollMixin, @Nullable ListView listView) { + this.requireScrollMixin = requireScrollMixin; + this.listView = listView; + } - @Override - public void startListening() { - if (mListView != null) { - mListView.setOnScrollListener(this); + @Override + public void startListening() { + if (listView != null) { + listView.setOnScrollListener(this); - final ListAdapter adapter = mListView.getAdapter(); - if (mListView.getLastVisiblePosition() < adapter.getCount()) { - mRequireScrollMixin.notifyScrollabilityChange(true); - } - } else { - Log.w(TAG, "Cannot require scroll. List view is null"); - } + final ListAdapter adapter = listView.getAdapter(); + if (listView.getLastVisiblePosition() < adapter.getCount()) { + requireScrollMixin.notifyScrollabilityChange(true); + } + } else { + Log.w(TAG, "Cannot require scroll. List view is null"); } + } - @Override - public void pageScrollDown() { - if (mListView != null) { - final int height = mListView.getHeight(); - mListView.smoothScrollBy(height, SCROLL_DURATION); - } + @Override + public void pageScrollDown() { + if (listView != null) { + final int height = listView.getHeight(); + listView.smoothScrollBy(height, SCROLL_DURATION); } + } - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) {} - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - if (firstVisibleItem + visibleItemCount >= totalItemCount) { - mRequireScrollMixin.notifyScrollabilityChange(false); - } else { - mRequireScrollMixin.notifyScrollabilityChange(true); - } + @Override + public void onScroll( + AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (firstVisibleItem + visibleItemCount >= totalItemCount) { + requireScrollMixin.notifyScrollabilityChange(false); + } else { + requireScrollMixin.notifyScrollabilityChange(true); } + } } diff --git a/library/main/src/com/android/setupwizardlib/template/Mixin.java b/library/main/src/com/android/setupwizardlib/template/Mixin.java index b7b8893..b460777 100644 --- a/library/main/src/com/android/setupwizardlib/template/Mixin.java +++ b/library/main/src/com/android/setupwizardlib/template/Mixin.java @@ -22,5 +22,4 @@ package com.android.setupwizardlib.template; * @see com.android.setupwizardlib.TemplateLayout#registerMixin(Class, Mixin) * @see com.android.setupwizardlib.TemplateLayout#getMixin(Class) */ -public interface Mixin { -} +public interface Mixin {} diff --git a/library/main/src/com/android/setupwizardlib/template/NavigationBarMixin.java b/library/main/src/com/android/setupwizardlib/template/NavigationBarMixin.java index df35017..2412eda 100644 --- a/library/main/src/com/android/setupwizardlib/template/NavigationBarMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/NavigationBarMixin.java @@ -17,67 +17,60 @@ package com.android.setupwizardlib.template; import android.view.View; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.view.NavigationBar; import com.android.setupwizardlib.view.NavigationBar.NavigationBarListener; -/** - * A {@link Mixin} for interacting with a {@link NavigationBar}. - */ +/** A {@link Mixin} for interacting with a {@link NavigationBar}. */ public class NavigationBarMixin implements Mixin { - private TemplateLayout mTemplateLayout; + private final TemplateLayout templateLayout; - /** - * @param layout The layout this mixin belongs to. - */ - public NavigationBarMixin(TemplateLayout layout) { - mTemplateLayout = layout; - } + /** @param layout The layout this mixin belongs to. */ + public NavigationBarMixin(TemplateLayout layout) { + templateLayout = layout; + } - /** - * @return The navigation bar instance in the layout, or null if the layout does not have a - * navigation bar. - */ - public NavigationBar getNavigationBar() { - final View view = mTemplateLayout.findManagedViewById(R.id.suw_layout_navigation_bar); - return view instanceof NavigationBar ? (NavigationBar) view : null; - } + /** + * @return The navigation bar instance in the layout, or null if the layout does not have a + * navigation bar. + */ + public NavigationBar getNavigationBar() { + final View view = templateLayout.findManagedViewById(R.id.suw_layout_navigation_bar); + return view instanceof NavigationBar ? (NavigationBar) view : null; + } - /** - * Sets the label of the next button. - * - * @param text Label of the next button. - */ - public void setNextButtonText(int text) { - getNavigationBar().getNextButton().setText(text); - } + /** + * Sets the label of the next button. + * + * @param text Label of the next button. + */ + public void setNextButtonText(int text) { + getNavigationBar().getNextButton().setText(text); + } - /** - * Sets the label of the next button. - * - * @param text Label of the next button. - */ - public void setNextButtonText(CharSequence text) { - getNavigationBar().getNextButton().setText(text); - } + /** + * Sets the label of the next button. + * + * @param text Label of the next button. + */ + public void setNextButtonText(CharSequence text) { + getNavigationBar().getNextButton().setText(text); + } - /** - * @return The current label of the next button. - */ - public CharSequence getNextButtonText() { - return getNavigationBar().getNextButton().getText(); - } + /** @return The current label of the next button. */ + public CharSequence getNextButtonText() { + return getNavigationBar().getNextButton().getText(); + } - /** - * Sets the listener to handle back and next button clicks in the navigation bar. - * - * @see NavigationBar#setNavigationBarListener(NavigationBarListener) - * @see NavigationBarListener - */ - public void setNavigationBarListener(NavigationBarListener listener) { - getNavigationBar().setNavigationBarListener(listener); - } + /** + * Sets the listener to handle back and next button clicks in the navigation bar. + * + * @see NavigationBar#setNavigationBarListener(NavigationBarListener) + * @see NavigationBarListener + */ + public void setNavigationBarListener(NavigationBarListener listener) { + getNavigationBar().setNavigationBarListener(listener); + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ProgressBarMixin.java b/library/main/src/com/android/setupwizardlib/template/ProgressBarMixin.java index 504b2f0..0e128c4 100644 --- a/library/main/src/com/android/setupwizardlib/template/ProgressBarMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/ProgressBarMixin.java @@ -19,121 +19,109 @@ package com.android.setupwizardlib.template; import android.content.res.ColorStateList; import android.os.Build; import android.os.Build.VERSION_CODES; +import androidx.annotation.Nullable; import android.view.View; import android.view.ViewStub; import android.widget.ProgressBar; - -import androidx.annotation.Nullable; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; -/** - * A {@link Mixin} for showing a progress bar. - */ +/** A {@link Mixin} for showing a progress bar. */ public class ProgressBarMixin implements Mixin { - private TemplateLayout mTemplateLayout; + private final TemplateLayout templateLayout; - @Nullable - private ColorStateList mColor; + @Nullable private ColorStateList color; - /** - * @param layout The layout this mixin belongs to. - */ - public ProgressBarMixin(TemplateLayout layout) { - mTemplateLayout = layout; - } + /** @param layout The layout this mixin belongs to. */ + public ProgressBarMixin(TemplateLayout layout) { + templateLayout = layout; + } - /** - * @return True if the progress bar is currently shown. - */ - public boolean isShown() { - final View progressBar = mTemplateLayout.findManagedViewById(R.id.suw_layout_progress); - return progressBar != null && progressBar.getVisibility() == View.VISIBLE; - } + /** @return True if the progress bar is currently shown. */ + public boolean isShown() { + final View progressBar = templateLayout.findManagedViewById(R.id.suw_layout_progress); + return progressBar != null && progressBar.getVisibility() == View.VISIBLE; + } - /** - * Sets whether the progress bar is shown. If the progress bar has not been inflated from the - * stub, this method will inflate the progress bar. - * - * @param shown True to show the progress bar, false to hide it. - */ - public void setShown(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); - } - } + /** + * Sets whether the progress bar is shown. If the progress bar has not been inflated from the + * stub, this method will inflate the progress bar. + * + * @param shown True to show the progress bar, false to hide it. + */ + public void setShown(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) mTemplateLayout.findManagedViewById(R.id.suw_layout_progress_stub); - if (progressBarStub != null) { - progressBarStub.inflate(); - } - setColor(mColor); - } - return peekProgressBar(); + /** + * 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) templateLayout.findManagedViewById(R.id.suw_layout_progress_stub); + if (progressBarStub != null) { + progressBarStub.inflate(); + } + setColor(color); } + return peekProgressBar(); + } - /** - * Gets the progress bar in the layout only if it has been installed. - * {@link #setShown(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 #setShown(boolean)} with true was - * not called before this, or if the template does not contain a progress bar. - */ - public ProgressBar peekProgressBar() { - return (ProgressBar) mTemplateLayout.findManagedViewById(R.id.suw_layout_progress); - } + /** + * Gets the progress bar in the layout only if it has been installed. {@link #setShown(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 #setShown(boolean)} with true was not called before this, + * or if the template does not contain a progress bar. + */ + public ProgressBar peekProgressBar() { + return (ProgressBar) templateLayout.findManagedViewById(R.id.suw_layout_progress); + } - /** - * Sets the color of the indeterminate progress bar. This method is a no-op on SDK < 21. - */ - public void setColor(@Nullable ColorStateList color) { - mColor = color; - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - final ProgressBar bar = peekProgressBar(); - if (bar != null) { - bar.setIndeterminateTintList(color); - if (Build.VERSION.SDK_INT >= VERSION_CODES.M || color != null) { - // There is a bug in Lollipop where setting the progress tint color to null - // will crash with "java.lang.NullPointerException: Attempt to invoke virtual - // method 'int android.graphics.Paint.getAlpha()' on a null object reference" - // at android.graphics.drawable.NinePatchDrawable.draw(:250) - // The bug doesn't affect ProgressBar on M because it uses ShapeDrawable instead - // of NinePatchDrawable. (commit 6a8253fdc9f4574c28b4beeeed90580ffc93734a) - bar.setProgressBackgroundTintList(color); - } - } + /** Sets the color of the indeterminate progress bar. This method is a no-op on SDK < 21. */ + public void setColor(@Nullable ColorStateList color) { + this.color = color; + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + final ProgressBar bar = peekProgressBar(); + if (bar != null) { + bar.setIndeterminateTintList(color); + if (Build.VERSION.SDK_INT >= VERSION_CODES.M || color != null) { + // There is a bug in Lollipop where setting the progress tint color to null + // will crash with "java.lang.NullPointerException: Attempt to invoke virtual + // method 'int android.graphics.Paint.getAlpha()' on a null object reference" + // at android.graphics.drawable.NinePatchDrawable.draw(:250) + // The bug doesn't affect ProgressBar on M because it uses ShapeDrawable instead + // of NinePatchDrawable. (commit 6a8253fdc9f4574c28b4beeeed90580ffc93734a) + bar.setProgressBackgroundTintList(color); } + } } + } - /** - * @return The color previously set in {@link #setColor(ColorStateList)}, or null if the color - * is not set. In case of null, the color of the progress bar will be inherited from the theme. - */ - @Nullable - public ColorStateList getColor() { - return mColor; - } + /** + * @return The color previously set in {@link #setColor(ColorStateList)}, or null if the color is + * not set. In case of null, the color of the progress bar will be inherited from the theme. + */ + @Nullable + public ColorStateList getColor() { + return color; + } } diff --git a/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java b/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java index fd3303b..02bcc1c 100644 --- a/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java @@ -18,244 +18,222 @@ package com.android.setupwizardlib.template; import android.os.Handler; import android.os.Looper; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; - +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.view.NavigationBar; /** - * A mixin to require the a scrollable container (BottomScrollView, RecyclerView or ListView) to - * be scrolled to bottom, making sure that the user sees all content above and below the fold. + * A mixin to require the a scrollable container (BottomScrollView, RecyclerView or ListView) to be + * scrolled to bottom, making sure that the user sees all content above and below the fold. */ public class RequireScrollMixin implements Mixin { - /* static section */ - - /** - * Listener for when the require-scroll state changes. Note that this only requires the user to - * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to - * bottom is not required again. - */ - public interface OnRequireScrollStateChangedListener { + /* static section */ - /** - * Called when require-scroll state changed. - * - * @param scrollNeeded True if the user should be required to scroll to bottom. - */ - void onRequireScrollStateChanged(boolean scrollNeeded); - } + /** + * Listener for when the require-scroll state changes. Note that this only requires the user to + * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to bottom + * is not required again. + */ + public interface OnRequireScrollStateChangedListener { /** - * A delegate to detect scrollability changes and to scroll the page. This provides a layer - * of abstraction for BottomScrollView, RecyclerView and ListView. The delegate should call - * {@link #notifyScrollabilityChange(boolean)} when the view scrollability is changed. - */ - interface ScrollHandlingDelegate { - - /** - * Starts listening to scrollability changes at the target scrollable container. - */ - void startListening(); - - /** - * Scroll the page content down by one page. - */ - void pageScrollDown(); - } - - /* non-static section */ - - @NonNull - private final TemplateLayout mTemplateLayout; - - private final Handler mHandler = new Handler(Looper.getMainLooper()); - - private boolean mRequiringScrollToBottom = false; - - // Whether the user have seen the more button yet. - private boolean mEverScrolledToBottom = false; - - private ScrollHandlingDelegate mDelegate; - - @Nullable - private OnRequireScrollStateChangedListener mListener; - - /** - * @param templateLayout The template containing this mixin - */ - public RequireScrollMixin(@NonNull TemplateLayout templateLayout) { - mTemplateLayout = templateLayout; - } - - /** - * Sets the delegate to handle scrolling. The type of delegate should depend on whether the - * scrolling view is a BottomScrollView, RecyclerView or ListView. - */ - public void setScrollHandlingDelegate(@NonNull ScrollHandlingDelegate delegate) { - mDelegate = delegate; - } - - /** - * Listen to require scroll state changes. When scroll is required, - * {@link OnRequireScrollStateChangedListener#onRequireScrollStateChanged(boolean)} is called - * with {@code true}, and vice versa. - */ - public void setOnRequireScrollStateChangedListener( - @Nullable OnRequireScrollStateChangedListener listener) { - mListener = listener; - } - - /** - * @return The scroll state listener previously set, or {@code null} if none is registered. - */ - public OnRequireScrollStateChangedListener getOnRequireScrollStateChangedListener() { - return mListener; - } - - /** - * Creates an {@link OnClickListener} which if scrolling is required, will scroll the page down, - * and if scrolling is not required, delegates to the wrapped {@code listener}. Note that you - * should call {@link #requireScroll()} as well in order to start requiring scrolling. + * Called when require-scroll state changed. * - * @param listener The listener to be invoked when scrolling is not needed and the user taps on - * the button. If {@code null}, the click listener will be a no-op when scroll - * is not required. - * @return A new {@link OnClickListener} which will scroll the page down or delegate to the - * given listener depending on the current require-scroll state. - */ - public OnClickListener createOnClickListener(@Nullable final OnClickListener listener) { - return new OnClickListener() { - @Override - public void onClick(View view) { - if (mRequiringScrollToBottom) { - mDelegate.pageScrollDown(); - } else if (listener != null) { - listener.onClick(view); - } - } - }; - } - - /** - * Coordinate with the given navigation bar to require scrolling on the page. The more button - * will be shown instead of the next button while scrolling is required. + * @param scrollNeeded True if the user should be required to scroll to bottom. */ - public void requireScrollWithNavigationBar(@NonNull final NavigationBar navigationBar) { - setOnRequireScrollStateChangedListener( - new OnRequireScrollStateChangedListener() { - @Override - public void onRequireScrollStateChanged(boolean scrollNeeded) { - navigationBar.getMoreButton() - .setVisibility(scrollNeeded ? View.VISIBLE : View.GONE); - navigationBar.getNextButton() - .setVisibility(scrollNeeded ? View.GONE : View.VISIBLE); - } - }); - navigationBar.getMoreButton().setOnClickListener(createOnClickListener(null)); - requireScroll(); - } - - /** - * @see #requireScrollWithButton(Button, CharSequence, OnClickListener) - */ - public void requireScrollWithButton( - @NonNull Button button, - @StringRes int moreText, - @Nullable OnClickListener onClickListener) { - requireScrollWithButton(button, button.getContext().getText(moreText), onClickListener); - } - - /** - * Use the given {@code button} to require scrolling. When scrolling is required, the button - * label will change to {@code moreText}, and tapping the button will cause the page to scroll - * down. - * - * <p>Note: Calling {@link View#setOnClickListener} on the button after this method will remove - * its link to the require-scroll mechanism. If you need to do that, obtain the click listener - * from {@link #createOnClickListener(OnClickListener)}. - * - * <p>Note: The normal button label is taken from the button's text at the time of calling this - * method. Calling {@link android.widget.TextView#setText} after calling this method causes - * undefined behavior. - * - * @param button The button to use for require scroll. The button's "normal" label is taken from - * the text at the time of calling this method, and the click listener of it will - * be replaced. - * @param moreText The button label when scroll is required. - * @param onClickListener The listener for clicks when scrolling is not required. - */ - public void requireScrollWithButton( - @NonNull final Button button, - final CharSequence moreText, - @Nullable OnClickListener onClickListener) { - final CharSequence nextText = button.getText(); - button.setOnClickListener(createOnClickListener(onClickListener)); - setOnRequireScrollStateChangedListener(new OnRequireScrollStateChangedListener() { - @Override - public void onRequireScrollStateChanged(boolean scrollNeeded) { - button.setText(scrollNeeded ? moreText : nextText); - } + void onRequireScrollStateChanged(boolean scrollNeeded); + } + + /** + * A delegate to detect scrollability changes and to scroll the page. This provides a layer of + * abstraction for BottomScrollView, RecyclerView and ListView. The delegate should call {@link + * #notifyScrollabilityChange(boolean)} when the view scrollability is changed. + */ + interface ScrollHandlingDelegate { + + /** Starts listening to scrollability changes at the target scrollable container. */ + void startListening(); + + /** Scroll the page content down by one page. */ + void pageScrollDown(); + } + + /* non-static section */ + + private final Handler handler = new Handler(Looper.getMainLooper()); + + private boolean requiringScrollToBottom = false; + + // Whether the user have seen the more button yet. + private boolean everScrolledToBottom = false; + + private ScrollHandlingDelegate delegate; + + @Nullable private OnRequireScrollStateChangedListener listener; + + /** @param templateLayout The template containing this mixin */ + public RequireScrollMixin(@NonNull TemplateLayout templateLayout) { + } + + /** + * Sets the delegate to handle scrolling. The type of delegate should depend on whether the + * scrolling view is a BottomScrollView, RecyclerView or ListView. + */ + public void setScrollHandlingDelegate(@NonNull ScrollHandlingDelegate delegate) { + this.delegate = delegate; + } + + /** + * Listen to require scroll state changes. When scroll is required, {@link + * OnRequireScrollStateChangedListener#onRequireScrollStateChanged(boolean)} is called with {@code + * true}, and vice versa. + */ + public void setOnRequireScrollStateChangedListener( + @Nullable OnRequireScrollStateChangedListener listener) { + this.listener = listener; + } + + /** @return The scroll state listener previously set, or {@code null} if none is registered. */ + public OnRequireScrollStateChangedListener getOnRequireScrollStateChangedListener() { + return listener; + } + + /** + * Creates an {@link OnClickListener} which if scrolling is required, will scroll the page down, + * and if scrolling is not required, delegates to the wrapped {@code listener}. Note that you + * should call {@link #requireScroll()} as well in order to start requiring scrolling. + * + * @param listener The listener to be invoked when scrolling is not needed and the user taps on + * the button. If {@code null}, the click listener will be a no-op when scroll is not + * required. + * @return A new {@link OnClickListener} which will scroll the page down or delegate to the given + * listener depending on the current require-scroll state. + */ + public OnClickListener createOnClickListener(@Nullable final OnClickListener listener) { + return new OnClickListener() { + @Override + public void onClick(View view) { + if (requiringScrollToBottom) { + delegate.pageScrollDown(); + } else if (listener != null) { + listener.onClick(view); + } + } + }; + } + + /** + * Coordinate with the given navigation bar to require scrolling on the page. The more button will + * be shown instead of the next button while scrolling is required. + */ + public void requireScrollWithNavigationBar(@NonNull final NavigationBar navigationBar) { + setOnRequireScrollStateChangedListener( + new OnRequireScrollStateChangedListener() { + @Override + public void onRequireScrollStateChanged(boolean scrollNeeded) { + navigationBar.getMoreButton().setVisibility(scrollNeeded ? View.VISIBLE : View.GONE); + navigationBar.getNextButton().setVisibility(scrollNeeded ? View.GONE : View.VISIBLE); + } }); - requireScroll(); - } - - /** - * @return True if scrolling is required. Note that this mixin only requires the user to - * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to - * bottom is not required again. - */ - public boolean isScrollingRequired() { - return mRequiringScrollToBottom; + navigationBar.getMoreButton().setOnClickListener(createOnClickListener(null)); + requireScroll(); + } + + /** @see #requireScrollWithButton(Button, CharSequence, OnClickListener) */ + public void requireScrollWithButton( + @NonNull Button button, @StringRes int moreText, @Nullable OnClickListener onClickListener) { + requireScrollWithButton(button, button.getContext().getText(moreText), onClickListener); + } + + /** + * Use the given {@code button} to require scrolling. When scrolling is required, the button label + * will change to {@code moreText}, and tapping the button will cause the page to scroll down. + * + * <p>Note: Calling {@link View#setOnClickListener} on the button after this method will remove + * its link to the require-scroll mechanism. If you need to do that, obtain the click listener + * from {@link #createOnClickListener(OnClickListener)}. + * + * <p>Note: The normal button label is taken from the button's text at the time of calling this + * method. Calling {@link android.widget.TextView#setText} after calling this method causes + * undefined behavior. + * + * @param button The button to use for require scroll. The button's "normal" label is taken from + * the text at the time of calling this method, and the click listener of it will be replaced. + * @param moreText The button label when scroll is required. + * @param onClickListener The listener for clicks when scrolling is not required. + */ + public void requireScrollWithButton( + @NonNull final Button button, + final CharSequence moreText, + @Nullable OnClickListener onClickListener) { + final CharSequence nextText = button.getText(); + button.setOnClickListener(createOnClickListener(onClickListener)); + setOnRequireScrollStateChangedListener( + new OnRequireScrollStateChangedListener() { + @Override + public void onRequireScrollStateChanged(boolean scrollNeeded) { + button.setText(scrollNeeded ? moreText : nextText); + } + }); + requireScroll(); + } + + /** + * @return True if scrolling is required. Note that this mixin only requires the user to scroll to + * the bottom once - if the user scrolled to the bottom and back-up, scrolling to bottom is + * not required again. + */ + public boolean isScrollingRequired() { + return requiringScrollToBottom; + } + + /** + * Start requiring scrolling on the layout. After calling this method, this mixin will start + * listening to scroll events from the scrolling container, and call {@link + * OnRequireScrollStateChangedListener} when the scroll state changes. + */ + public void requireScroll() { + delegate.startListening(); + } + + /** + * {@link ScrollHandlingDelegate} should call this method when the scrollability of the scrolling + * container changed, so this mixin can recompute whether scrolling should be required. + * + * @param canScrollDown True if the view can scroll down further. + */ + void notifyScrollabilityChange(boolean canScrollDown) { + if (canScrollDown == requiringScrollToBottom) { + // Already at the desired require-scroll state + return; } - - /** - * Start requiring scrolling on the layout. After calling this method, this mixin will start - * listening to scroll events from the scrolling container, and call - * {@link OnRequireScrollStateChangedListener} when the scroll state changes. - */ - public void requireScroll() { - mDelegate.startListening(); + if (canScrollDown) { + if (!everScrolledToBottom) { + postScrollStateChange(true); + requiringScrollToBottom = true; + } + } else { + postScrollStateChange(false); + requiringScrollToBottom = false; + everScrolledToBottom = true; } - - /** - * {@link ScrollHandlingDelegate} should call this method when the scrollability of the - * scrolling container changed, so this mixin can recompute whether scrolling should be - * required. - * - * @param canScrollDown True if the view can scroll down further. - */ - void notifyScrollabilityChange(boolean canScrollDown) { - if (canScrollDown == mRequiringScrollToBottom) { - // Already at the desired require-scroll state - return; - } - if (canScrollDown) { - if (!mEverScrolledToBottom) { - postScrollStateChange(true); - mRequiringScrollToBottom = true; - } - } else { - postScrollStateChange(false); - mRequiringScrollToBottom = false; - mEverScrolledToBottom = true; - } - } - - private void postScrollStateChange(final boolean scrollNeeded) { - mHandler.post(new Runnable() { - @Override - public void run() { - if (mListener != null) { - mListener.onRequireScrollStateChanged(scrollNeeded); - } + } + + private void postScrollStateChange(final boolean scrollNeeded) { + handler.post( + new Runnable() { + @Override + public void run() { + if (listener != null) { + listener.onRequireScrollStateChanged(scrollNeeded); } + } }); - } + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java b/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java index 9e4d1cf..dcaa379 100644 --- a/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java +++ b/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java @@ -16,12 +16,10 @@ package com.android.setupwizardlib.template; -import android.util.Log; -import android.widget.ScrollView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import android.util.Log; +import android.widget.ScrollView; import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate; import com.android.setupwizardlib.view.BottomScrollView; import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener; @@ -31,51 +29,48 @@ import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener; * notifies {@link RequireScrollMixin} about scrollability changes. */ public class ScrollViewScrollHandlingDelegate - implements ScrollHandlingDelegate, BottomScrollListener { + implements ScrollHandlingDelegate, BottomScrollListener { - private static final String TAG = "ScrollViewDelegate"; + private static final String TAG = "ScrollViewDelegate"; - @NonNull - private final RequireScrollMixin mRequireScrollMixin; + @NonNull private final RequireScrollMixin requireScrollMixin; - @Nullable - private final BottomScrollView mScrollView; + @Nullable private final BottomScrollView scrollView; - public ScrollViewScrollHandlingDelegate( - @NonNull RequireScrollMixin requireScrollMixin, - @Nullable ScrollView scrollView) { - mRequireScrollMixin = requireScrollMixin; - if (scrollView instanceof BottomScrollView) { - mScrollView = (BottomScrollView) scrollView; - } else { - Log.w(TAG, "Cannot set non-BottomScrollView. Found=" + scrollView); - mScrollView = null; - } + public ScrollViewScrollHandlingDelegate( + @NonNull RequireScrollMixin requireScrollMixin, @Nullable ScrollView scrollView) { + this.requireScrollMixin = requireScrollMixin; + if (scrollView instanceof BottomScrollView) { + this.scrollView = (BottomScrollView) scrollView; + } else { + Log.w(TAG, "Cannot set non-BottomScrollView. Found=" + scrollView); + this.scrollView = null; } + } - @Override - public void onScrolledToBottom() { - mRequireScrollMixin.notifyScrollabilityChange(false); - } + @Override + public void onScrolledToBottom() { + requireScrollMixin.notifyScrollabilityChange(false); + } - @Override - public void onRequiresScroll() { - mRequireScrollMixin.notifyScrollabilityChange(true); - } + @Override + public void onRequiresScroll() { + requireScrollMixin.notifyScrollabilityChange(true); + } - @Override - public void startListening() { - if (mScrollView != null) { - mScrollView.setBottomScrollListener(this); - } else { - Log.w(TAG, "Cannot require scroll. Scroll view is null."); - } + @Override + public void startListening() { + if (scrollView != null) { + scrollView.setBottomScrollListener(this); + } else { + Log.w(TAG, "Cannot require scroll. Scroll view is null."); } + } - @Override - public void pageScrollDown() { - if (mScrollView != null) { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - } + @Override + public void pageScrollDown() { + if (scrollView != null) { + scrollView.pageScroll(ScrollView.FOCUS_DOWN); } + } } diff --git a/library/main/src/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java b/library/main/src/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java index b0afaba..8214415 100644 --- a/library/main/src/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java +++ b/library/main/src/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java @@ -23,59 +23,77 @@ 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. - */ +/** 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}. - */ - @SuppressLint("InlinedApi") // Use of View.LAYOUT_DIRECTION_RTL is guarded by version check - 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 view}. + */ + @SuppressLint("InlinedApi") // Use of View.LAYOUT_DIRECTION_RTL is guarded by version check + 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}. - */ - @SuppressLint("InlinedApi") // Use of View.LAYOUT_DIRECTION_RTL is guarded by version check - 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 the layout direction of + * {@code context}. + */ + @SuppressLint("InlinedApi") // Use of View.LAYOUT_DIRECTION_RTL is guarded by version check + 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}. - */ - @SuppressLint("InlinedApi") // Given layoutDirection will not be View.LAYOUT_DIRECTION_RTL if - // SDK version doesn't support it. - public static InsetDrawable createRelativeInsetDrawable(Drawable drawable, - int insetStart, int insetTop, int insetEnd, int insetBottom, int layoutDirection) { - return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom, - layoutDirection == View.LAYOUT_DIRECTION_RTL); - } + /** + * Creates an {@link android.graphics.drawable.InsetDrawable} according to {@code + * layoutDirection}. + */ + @SuppressLint("InlinedApi") // Given layoutDirection will not be View.LAYOUT_DIRECTION_RTL if + // SDK version doesn't support it. + public static InsetDrawable createRelativeInsetDrawable( + Drawable drawable, + int insetStart, + int insetTop, + int insetEnd, + int insetBottom, + int layoutDirection) { + 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); - } + 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/library/main/src/com/android/setupwizardlib/util/FallbackThemeWrapper.java b/library/main/src/com/android/setupwizardlib/util/FallbackThemeWrapper.java index 2ec4f84..96f9162 100644 --- a/library/main/src/com/android/setupwizardlib/util/FallbackThemeWrapper.java +++ b/library/main/src/com/android/setupwizardlib/util/FallbackThemeWrapper.java @@ -18,37 +18,33 @@ package com.android.setupwizardlib.util; import android.content.Context; import android.content.res.Resources.Theme; -import android.view.ContextThemeWrapper; - import androidx.annotation.StyleRes; +import android.view.ContextThemeWrapper; /** - * Same as {@link ContextThemeWrapper}, but the base context's theme attributes take precedence - * over the wrapper context's. This is used to provide default values for theme attributes - * referenced in layouts, to remove the risk of crashing the client because of using the wrong - * theme. + * Same as {@link ContextThemeWrapper}, but the base context's theme attributes take precedence over + * the wrapper context's. This is used to provide default values for theme attributes referenced in + * layouts, to remove the risk of crashing the client because of using the wrong theme. */ public class FallbackThemeWrapper extends ContextThemeWrapper { - /** - * Creates a new context wrapper with the specified theme. - * - * The specified theme will be applied as fallbacks to the base context's theme. Any attributes - * defined in the base context's theme will retain their original values. Otherwise values in - * {@code themeResId} will be used. - * - * @param base The base context. - * @param themeResId The theme to use as fallback. - */ - public FallbackThemeWrapper(Context base, @StyleRes int themeResId) { - super(base, themeResId); - } + /** + * Creates a new context wrapper with the specified theme. + * + * <p>The specified theme will be applied as fallbacks to the base context's theme. Any attributes + * defined in the base context's theme will retain their original values. Otherwise values in + * {@code themeResId} will be used. + * + * @param base The base context. + * @param themeResId The theme to use as fallback. + */ + public FallbackThemeWrapper(Context base, @StyleRes int themeResId) { + super(base, themeResId); + } - /** - * {@inheritDoc} - */ - @Override - protected void onApplyThemeResource(Theme theme, int resId, boolean first) { - theme.applyStyle(resId, false /* force */); - } + /** {@inheritDoc} */ + @Override + protected void onApplyThemeResource(Theme theme, int resId, boolean first) { + theme.applyStyle(resId, false /* force */); + } } diff --git a/library/main/src/com/android/setupwizardlib/util/Partner.java b/library/main/src/com/android/setupwizardlib/util/Partner.java index 9eaedc3..ce782cb 100644 --- a/library/main/src/com/android/setupwizardlib/util/Partner.java +++ b/library/main/src/com/android/setupwizardlib/util/Partner.java @@ -26,14 +26,13 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.util.Log; - import androidx.annotation.AnyRes; import androidx.annotation.ColorRes; import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; - +import android.util.Log; import java.util.List; /** @@ -46,157 +45,156 @@ import java.util.List; */ 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, @DrawableRes int id) { - final ResourceEntry entry = getResourceEntry(context, id); - return entry.resources.getDrawable(entry.id); + 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 searched = false; + @Nullable private static Partner partner; + + /** + * Gets 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, @DrawableRes int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getDrawable(entry.id); + } + + /** + * Gets 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, @StringRes int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getString(entry.id); + } + + /** + * Gets a color from partner overlay, or if not available, the color from the original context. + */ + public static int getColor(Context context, @ColorRes int id) { + final ResourceEntry resourceEntry = getResourceEntry(context, id); + return resourceEntry.resources.getColor(resourceEntry.id); + } + + /** + * Gets a CharSequence from partner overlay, or if not available, the text from the original + * context. + */ + public static CharSequence getText(Context context, @StringRes int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getText(entry.id); + } + + /** + * Finds 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, @AnyRes 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.resources, partnerId, true); + } } - - /** - * 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, @StringRes int id) { - final ResourceEntry entry = getResourceEntry(context, id); - return entry.resources.getString(entry.id); - } - - /** - * Convenience method to get color from partner overlay, or if not available, the color from - * the original context. - */ - public static int getColor(Context context, @ColorRes int id) { - final ResourceEntry resourceEntry = getResourceEntry(context, id); - return resourceEntry.resources.getColor(resourceEntry.id); - } - - /** - * Convenience method to get a CharSequence from partner overlay, or if not available, the text - * from the original context. - */ - public static CharSequence getText(Context context, @StringRes int id) { - final ResourceEntry entry = getResourceEntry(context, id); - return entry.resources.getText(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, @AnyRes 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); + 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; } - - 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; + } + + /** + * Finds and returns 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 (!searched) { + PackageManager pm = context.getPackageManager(); + final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION); + List<ResolveInfo> receivers; + if (VERSION.SDK_INT >= VERSION_CODES.N) { + receivers = + pm.queryBroadcastReceivers( + intent, + PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + } else { + // On versions before N, direct boot doesn't exist. And the MATCH_SYSTEM_ONLY flag + // doesn't exist so we filter for system apps in code below. + receivers = pm.queryBroadcastReceivers(intent, 0); + } + + for (ResolveInfo info : receivers) { + if (info.activityInfo == null) { + continue; } - } - - /** - * 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); - List<ResolveInfo> receivers; - if (VERSION.SDK_INT >= VERSION_CODES.N) { - receivers = pm.queryBroadcastReceivers( - intent, - PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - } else { - // On versions before N, direct boot doesn't exist. And the MATCH_SYSTEM_ONLY flag - // doesn't exist so we filter for system apps in code below. - receivers = pm.queryBroadcastReceivers(intent, 0); - } - - for (ResolveInfo info : receivers) { - 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; + final ApplicationInfo appInfo = info.activityInfo.applicationInfo; + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + try { + final Resources res = pm.getResourcesForApplication(appInfo); + partner = new Partner(appInfo.packageName, res); + break; + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + appInfo.packageName); + } } - 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); + } + searched = true; } + return partner; + } + + @VisibleForTesting + public static synchronized void resetForTesting() { + searched = false; + partner = null; + } + + private final String packageName; + private final Resources resources; + + private Partner(String packageName, Resources res) { + this.packageName = packageName; + resources = res; + } + + public String getPackageName() { + return packageName; + } + + public Resources getResources() { + return resources; + } + + public int getIdentifier(String name, String defType) { + return resources.getIdentifier(name, defType, packageName); + } } diff --git a/library/main/src/com/android/setupwizardlib/util/ResultCodes.java b/library/main/src/com/android/setupwizardlib/util/ResultCodes.java index a429e73..ea20139 100644 --- a/library/main/src/com/android/setupwizardlib/util/ResultCodes.java +++ b/library/main/src/com/android/setupwizardlib/util/ResultCodes.java @@ -20,9 +20,9 @@ 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_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; + public static final int RESULT_FIRST_SETUP_USER = RESULT_FIRST_USER + 100; } diff --git a/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java b/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java index 7e3e885..42350cc 100644 --- a/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java +++ b/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java @@ -24,6 +24,7 @@ import android.content.res.TypedArray; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Handler; +import androidx.annotation.RequiresPermission; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -31,331 +32,327 @@ import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; -import androidx.annotation.RequiresPermission; - /** * 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. + * <p>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. - * - * <p>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); - } + 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. + * + * <p>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. - * - * <p>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); - } + } + + /** + * 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. + * + * <p>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 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(); + } } + } - /** - * 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 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 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 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); } - - /** - * 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); - } + } + + /** + * Sets whether the back button on the software navigation bar is visible. This only works if you + * have the STATUS_BAR permission. Otherwise framework will filter out this flag and this method + * call will not have any effect. + * + * <p>IMPORTANT: Do not assume that users have no way to go back when the back button is hidden. + * Many devices have physical back buttons, and accessibility services like TalkBack may have + * gestures mapped to back. Please use onBackPressed, onKeyDown, or other similar ways to make + * sure back button events are still handled (or ignored) properly. + */ + @RequiresPermission("android.permission.STATUS_BAR") + 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); + removeImmersiveFlagsFromDecorView(window, STATUS_BAR_DISABLE_BACK); + } else { + addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + addImmersiveFlagsToDecorView(window, STATUS_BAR_DISABLE_BACK); + } } - - /** - * Sets whether the back button on the software navigation bar is visible. This only works if - * you have the STATUS_BAR permission. Otherwise framework will filter out this flag and this - * method call will not have any effect. - * - * <p>IMPORTANT: Do not assume that users have no way to go back when the back button is hidden. - * Many devices have physical back buttons, and accessibility services like TalkBack may have - * gestures mapped to back. Please use onBackPressed, onKeyDown, or other similar ways to - * make sure back button events are still handled (or ignored) properly. - */ - @RequiresPermission("android.permission.STATUS_BAR") - 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); - removeImmersiveFlagsFromDecorView(window, STATUS_BAR_DISABLE_BACK); - } else { - addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); - addImmersiveFlagsToDecorView(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. + * + * <p>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. + * + * <p>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()); } - - /** - * 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. - * - * <p>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. - * - * <p>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.HONEYCOMB) - private static void addImmersiveFlagsToDecorView(final Window window, final int vis) { - getDecorView(window, new OnDecorViewInstalledListener() { - @Override - public void onDecorViewInstalled(View decorView) { - addVisibilityFlag(decorView, vis); - } + } + + /** + * 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.HONEYCOMB) + 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.HONEYCOMB) - private static void removeImmersiveFlagsFromDecorView(final Window window, final int vis) { - getDecorView(window, new OnDecorViewInstalledListener() { - @Override - public void onDecorViewInstalled(View decorView) { - removeVisibilityFlag(decorView, vis); - } + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + 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); - } - } + } + + private static void getDecorView(Window window, OnDecorViewInstalledListener callback) { + new DecorViewFinder().getDecorView(window, callback, PEEK_DECOR_VIEW_RETRIES); + } + + private static class DecorViewFinder { + + private final Handler handler = new Handler(); + private Window window; + private int retries; + private OnDecorViewInstalledListener callback; + + private final Runnable checkDecorViewRunnable = + 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 = window.peekDecorView(); + if (decorView != null) { + callback.onDecorViewInstalled(decorView); + } else { + retries--; + if (retries >= 0) { + // If the decor view is not installed yet, try again in the next loop. + handler.post(checkDecorViewRunnable); + } else { + Log.w(TAG, "Cannot get decor view of window: " + window); + } } + } }; - public void getDecorView(Window window, OnDecorViewInstalledListener callback, - int retries) { - mWindow = window; - mRetries = retries; - mCallback = callback; - mCheckDecorViewRunnable.run(); - } + public void getDecorView(Window window, OnDecorViewInstalledListener callback, int retries) { + this.window = window; + this.retries = retries; + this.callback = callback; + checkDecorViewRunnable.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() { + } + + 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 bottomOffset; + private boolean hasCalculatedBottomOffset = false; + + @Override + public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { + if (!hasCalculatedBottomOffset) { + bottomOffset = getBottomDistance(view); + hasCalculatedBottomOffset = true; + } + + int bottomInset = insets.getSystemWindowInsetBottom(); + + final int bottomMargin = Math.max(insets.getSystemWindowInsetBottom() - bottomOffset, 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); } + } - @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(); - } + 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/library/main/src/com/android/setupwizardlib/util/ThemeResolver.java b/library/main/src/com/android/setupwizardlib/util/ThemeResolver.java new file mode 100644 index 0000000..14fdc85 --- /dev/null +++ b/library/main/src/com/android/setupwizardlib/util/ThemeResolver.java @@ -0,0 +1,219 @@ +package com.android.setupwizardlib.util; + +import android.app.Activity; +import android.content.Intent; +import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; +import com.android.setupwizardlib.R; + +/** + * A resolver to resolve the theme from a string or an activity intent, setting options like the + * default theme and the oldest supported theme. Apps can share the resolver across the entire + * process by calling {@link #setDefault(ThemeResolver)} in {@link + * android.app.Application#onCreate()}. If an app needs more granular sharing of the theme default + * values, additional instances of {@link ThemeResolver} can be created using the builder. + */ +public class ThemeResolver { + @StyleRes private final int defaultTheme; + @Nullable private final String oldestSupportedTheme; + private final boolean useDayNight; + + @Nullable private static ThemeResolver defaultResolver; + + /** + * Sets the default instance used for the whole process. Can be null to reset the default to the + * preset one. + */ + public static void setDefault(@Nullable ThemeResolver resolver) { + defaultResolver = resolver; + } + + /** + * Returns the default instance, which can be changed using {@link #setDefault(ThemeResolver)}. + */ + public static ThemeResolver getDefault() { + if (defaultResolver == null) { + defaultResolver = + new ThemeResolver.Builder() + .setDefaultTheme(R.style.SuwThemeGlif_DayNight) + .setUseDayNight(true) + .build(); + } + return defaultResolver; + } + + private ThemeResolver( + int defaultTheme, @Nullable String oldestSupportedTheme, boolean useDayNight) { + this.defaultTheme = defaultTheme; + this.oldestSupportedTheme = oldestSupportedTheme; + this.useDayNight = useDayNight; + } + + /** + * Returns the style for the theme specified in the intent extra. If the specified string theme is + * older than the oldest supported theme, the default will be returned instead. Note that the + * default theme is returned without processing -- it may not be a DayNight theme even if {@link + * #useDayNight} is true. + */ + @StyleRes + public int resolve(Intent intent) { + return resolve( + intent.getStringExtra(WizardManagerHelper.EXTRA_THEME), + /* suppressDayNight= */ WizardManagerHelper.isSetupWizardIntent(intent)); + } + + /** + * Returns the style for the given string theme. If the specified string theme is older than the + * oldest supported theme, the default will be returned instead. Note that the default theme is + * returned without processing -- it may not be a DayNight theme even if {@link #useDayNight} is + * true. + */ + @StyleRes + public int resolve(@Nullable String theme) { + return resolve(theme, /* suppressDayNight= */ false); + } + + @StyleRes + private int resolve(@Nullable String theme, boolean suppressDayNight) { + int themeResource = + useDayNight && !suppressDayNight ? getDayNightThemeRes(theme) : getThemeRes(theme); + if (themeResource == 0) { + return defaultTheme; + } + + if (oldestSupportedTheme != null && compareThemes(theme, oldestSupportedTheme) < 0) { + return defaultTheme; + } + return themeResource; + } + + /** Reads the theme from the intent, and applies the resolved theme to the activity. */ + public void applyTheme(Activity activity) { + activity.setTheme(resolve(activity.getIntent())); + } + + /** + * Returns the corresponding DayNight theme resource ID for the given string theme. DayNight + * themes are themes that will be either light or dark depending on the system setting. For + * example, the string {@link WizardManagerHelper#THEME_GLIF_LIGHT} will return + * {@code @style/SuwThemeGlif.DayNight}. + */ + @StyleRes + private static int getDayNightThemeRes(@Nullable String theme) { + if (theme != null) { + switch (theme) { + case WizardManagerHelper.THEME_GLIF_V3_LIGHT: + case WizardManagerHelper.THEME_GLIF_V3: + return R.style.SuwThemeGlifV3_DayNight; + case WizardManagerHelper.THEME_GLIF_V2_LIGHT: + case WizardManagerHelper.THEME_GLIF_V2: + return R.style.SuwThemeGlifV2_DayNight; + case WizardManagerHelper.THEME_GLIF_LIGHT: + case WizardManagerHelper.THEME_GLIF: + return R.style.SuwThemeGlif_DayNight; + case WizardManagerHelper.THEME_MATERIAL_LIGHT: + case WizardManagerHelper.THEME_MATERIAL: + return R.style.SuwThemeMaterial_DayNight; + default: + // fall through + } + } + return 0; + } + + /** + * Returns the theme resource ID for the given string theme. For example, the string {@link + * WizardManagerHelper#THEME_GLIF_LIGHT} will return {@code @style/SuwThemeGlif.Light}. + */ + @StyleRes + private static int getThemeRes(@Nullable String theme) { + if (theme != null) { + switch (theme) { + case WizardManagerHelper.THEME_GLIF_V3_LIGHT: + return R.style.SuwThemeGlifV3_Light; + case WizardManagerHelper.THEME_GLIF_V3: + return R.style.SuwThemeGlifV3; + case WizardManagerHelper.THEME_GLIF_V2_LIGHT: + return R.style.SuwThemeGlifV2_Light; + case WizardManagerHelper.THEME_GLIF_V2: + return R.style.SuwThemeGlifV2; + case WizardManagerHelper.THEME_GLIF_LIGHT: + return R.style.SuwThemeGlif_Light; + case WizardManagerHelper.THEME_GLIF: + return R.style.SuwThemeGlif; + case WizardManagerHelper.THEME_MATERIAL_LIGHT: + return R.style.SuwThemeMaterial_Light; + case WizardManagerHelper.THEME_MATERIAL: + return R.style.SuwThemeMaterial; + default: + // fall through + } + } + return 0; + } + + /** Compares whether the versions of {@code theme1} and {@code theme2} to check which is newer. */ + private static int compareThemes(String theme1, String theme2) { + return Integer.valueOf(getThemeVersion(theme1)).compareTo(getThemeVersion(theme2)); + } + + /** + * Returns the version of the theme. The absolute number of the theme version is not defined, but + * a larger number in the version indicates a newer theme. + */ + private static int getThemeVersion(String theme) { + if (theme != null) { + switch (theme) { + case WizardManagerHelper.THEME_GLIF_V3_LIGHT: + case WizardManagerHelper.THEME_GLIF_V3: + return 4; + case WizardManagerHelper.THEME_GLIF_V2_LIGHT: + case WizardManagerHelper.THEME_GLIF_V2: + return 3; + case WizardManagerHelper.THEME_GLIF_LIGHT: + case WizardManagerHelper.THEME_GLIF: + return 2; + case WizardManagerHelper.THEME_MATERIAL_LIGHT: + case WizardManagerHelper.THEME_MATERIAL: + return 1; + default: + // fall through + } + } + return -1; + } + + /** Builder class for {@link ThemeResolver}. */ + public static class Builder { + @StyleRes private int defaultTheme = R.style.SuwThemeGlif_DayNight; + @Nullable private String oldestSupportedTheme = null; + private boolean useDayNight = true; + + public Builder() {} + + public Builder(ThemeResolver themeResolver) { + this.defaultTheme = themeResolver.defaultTheme; + this.oldestSupportedTheme = themeResolver.oldestSupportedTheme; + this.useDayNight = themeResolver.useDayNight; + } + + public Builder setDefaultTheme(@StyleRes int defaultTheme) { + this.defaultTheme = defaultTheme; + return this; + } + + public Builder setOldestSupportedTheme(String oldestSupportedTheme) { + this.oldestSupportedTheme = oldestSupportedTheme; + return this; + } + + public Builder setUseDayNight(boolean useDayNight) { + this.useDayNight = useDayNight; + return this; + } + + public ThemeResolver build() { + return new ThemeResolver(defaultTheme, oldestSupportedTheme, useDayNight); + } + } +} diff --git a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java index 0628192..4d75c78 100644 --- a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java +++ b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java @@ -16,314 +16,390 @@ package com.android.setupwizardlib.util; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Resources.Theme; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.Settings; - +import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.annotation.VisibleForTesting; - -import com.android.setupwizardlib.R; - import java.util.Arrays; +/** + * Helper to interact with Wizard Manager in setup wizard, which should be used when a screen is + * shown inside the setup flow. This includes things like parsing extras passed by Wizard Manager, + * and invoking Wizard Manager to start the next action. + */ public class WizardManagerHelper { - private static final String ACTION_NEXT = "com.android.wizard.NEXT"; - - // EXTRA_SCRIPT_URI and EXTRA_ACTION_ID are used in setup wizard in versions before M and are - // kept for backwards compatibility. - @VisibleForTesting - static final String EXTRA_SCRIPT_URI = "scriptUri"; - @VisibleForTesting - static final String EXTRA_ACTION_ID = "actionId"; - - @VisibleForTesting - static final String EXTRA_WIZARD_BUNDLE = "wizardBundle"; - private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode"; - @VisibleForTesting - static final String EXTRA_IS_FIRST_RUN = "firstRun"; - @VisibleForTesting - static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup"; - @VisibleForTesting - static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup"; - - 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"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the - * theme used in setup wizard for Nougat MR1. - */ - 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 Nougat MR1. - */ - public static final String THEME_GLIF_LIGHT = "glif_light"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the - * theme used in setup wizard for O DR. - */ - public static final String THEME_GLIF_V2 = "glif_v2"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in - * setup wizard for O DR. - */ - public static final String THEME_GLIF_V2_LIGHT = "glif_v2_light"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the - * theme used in setup wizard for P. - */ - public static final String THEME_GLIF_V3 = "glif_v3"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in - * setup wizard for P. - */ - public static final String THEME_GLIF_V3_LIGHT = "glif_v3_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)); - for (String key : Arrays.asList( - EXTRA_IS_FIRST_RUN, EXTRA_IS_DEFERRED_SETUP, EXTRA_IS_PRE_DEFERRED_SETUP)) { - dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false)); - } - - for (String key : Arrays.asList(EXTRA_THEME, EXTRA_SCRIPT_URI, EXTRA_ACTION_ID)) { - dstIntent.putExtra(key, srcIntent.getStringExtra(key)); - } - } - - /** - * 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); + private static final String ACTION_NEXT = "com.android.wizard.NEXT"; + + // EXTRA_SCRIPT_URI and EXTRA_ACTION_ID are used in setup wizard in versions before M and are + // kept for backwards compatibility. + @VisibleForTesting static final String EXTRA_SCRIPT_URI = "scriptUri"; + @VisibleForTesting static final String EXTRA_ACTION_ID = "actionId"; + + @VisibleForTesting static final String EXTRA_WIZARD_BUNDLE = "wizardBundle"; + private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode"; + @VisibleForTesting static final String EXTRA_IS_FIRST_RUN = "firstRun"; + @VisibleForTesting static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup"; + @VisibleForTesting static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup"; + @VisibleForTesting public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow"; + + 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"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme + * used in setup wizard for Nougat MR1. + */ + 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 Nougat MR1. + */ + public static final String THEME_GLIF_LIGHT = "glif_light"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme + * used in setup wizard for O DR. + */ + public static final String THEME_GLIF_V2 = "glif_v2"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in + * setup wizard for O DR. + */ + public static final String THEME_GLIF_V2_LIGHT = "glif_v2_light"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme + * used in setup wizard for P. + */ + public static final String THEME_GLIF_V3 = "glif_v3"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in + * setup wizard for P. + */ + public static final String THEME_GLIF_V3_LIGHT = "glif_v3_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()); } - - /** - * 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.JELLY_BEAN_MR1) { - 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 whether an intent is running in the deferred setup wizard flow. - * - * @param originalIntent The original intent that was used to start the step, usually via - * {@link android.app.Activity#getIntent()}. - * @return true if the intent passed in was running in deferred setup wizard. - */ - public static boolean isDeferredSetupWizard(Intent originalIntent) { - return originalIntent != null - && originalIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false); + 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)); + for (String key : + Arrays.asList( + EXTRA_IS_FIRST_RUN, + EXTRA_IS_DEFERRED_SETUP, + EXTRA_IS_PRE_DEFERRED_SETUP, + EXTRA_IS_SETUP_FLOW)) { + dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false)); } - /** - * Checks whether an intent is running in "pre-deferred" setup wizard flow. - * - * @param originalIntent The original intent that was used to start the step, usually via - * {@link android.app.Activity#getIntent()}. - * @return true if the intent passed in was running in "pre-deferred" setup wizard. - */ - public static boolean isPreDeferredSetupWizard(Intent originalIntent) { - return originalIntent != null - && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false); + for (String key : Arrays.asList(EXTRA_THEME, EXTRA_SCRIPT_URI, EXTRA_ACTION_ID)) { + dstIntent.putExtra(key, srcIntent.getStringExtra(key)); } - - /** - * 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); + } + + /** + * 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.JELLY_BEAN_MR1) { + 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 {@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_GLIF_LIGHT.equals(theme) || THEME_GLIF_V2_LIGHT.equals(theme) - || THEME_GLIF_V3_LIGHT.equals(theme)) { - return true; - } else if (THEME_HOLO.equals(theme) || THEME_MATERIAL.equals(theme) - || THEME_GLIF.equals(theme) || THEME_GLIF_V2.equals(theme) - || THEME_GLIF_V3.equals(theme)) { - return false; - } else { - return def; - } + } + + /** + * 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; } - - /** - * Gets the theme style resource defined by this library for the theme specified in the given - * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. - * - * @param intent The intent passed by setup wizard, or one with the theme propagated along using - * {@link #copyWizardManagerExtras(Intent, Intent)}. - * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if - * the given theme is not recognized. - * - * @see #getThemeRes(String, int) - */ - public static @StyleRes int getThemeRes(Intent intent, @StyleRes int defaultTheme) { - final String theme = intent.getStringExtra(EXTRA_THEME); - return getThemeRes(theme, defaultTheme); - } - - /** - * Gets the theme style resource defined by this library for the given theme name. For example, - * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. - * - * <p>If you require extra theme attributes but want to ensure forward compatibility with new - * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in - * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. - * - * <pre>{@code - * protected void onApplyThemeResource(Theme theme, int resid, boolean first) { - * super.onApplyThemeResource(theme, resid, first); - * theme.applyStyle(R.style.MyThemeOverlay, true); - * } - * }</pre> - * - * @param theme The string representation of the theme. - * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the - * given theme is not recognized. - */ - public static @StyleRes int getThemeRes(String theme, @StyleRes int defaultTheme) { - if (theme != null) { - switch (theme) { - case THEME_GLIF_V3_LIGHT: - return R.style.SuwThemeGlifV3_Light; - case THEME_GLIF_V3: - return R.style.SuwThemeGlifV3; - case THEME_GLIF_V2_LIGHT: - return R.style.SuwThemeGlifV2_Light; - case THEME_GLIF_V2: - return R.style.SuwThemeGlifV2; - case THEME_GLIF_LIGHT: - return R.style.SuwThemeGlif_Light; - case THEME_GLIF: - return R.style.SuwThemeGlif; - case THEME_MATERIAL_LIGHT: - return R.style.SuwThemeMaterial_Light; - case THEME_MATERIAL: - return R.style.SuwThemeMaterial; - default: - // fall through - } - } - return defaultTheme; + } + + /** + * Checks whether an intent is running in the deferred setup wizard flow. + * + * @param originalIntent The original intent that was used to start the step, usually via {@link + * android.app.Activity#getIntent()}. + * @return true if the intent passed in was running in deferred setup wizard. + */ + public static boolean isDeferredSetupWizard(Intent originalIntent) { + return originalIntent != null && originalIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false); + } + + /** + * Checks whether an intent is running in "pre-deferred" setup wizard flow. + * + * @param originalIntent The original intent that was used to start the step, usually via {@link + * android.app.Activity#getIntent()}. + * @return true if the intent passed in was running in "pre-deferred" setup wizard. + */ + public static boolean isPreDeferredSetupWizard(Intent originalIntent) { + return originalIntent != null + && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false); + } + + /** + * 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. Note that day-night themes are not taken into account by this method. + * + * @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. Note that day-night themes are not taken into account + * by this method. + * + * @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_GLIF_LIGHT.equals(theme) + || THEME_GLIF_V2_LIGHT.equals(theme) + || THEME_GLIF_V3_LIGHT.equals(theme)) { + return true; + } else if (THEME_HOLO.equals(theme) + || THEME_MATERIAL.equals(theme) + || THEME_GLIF.equals(theme) + || THEME_GLIF_V2.equals(theme) + || THEME_GLIF_V3.equals(theme)) { + return false; + } else { + return def; } + } + + /** + * Gets the theme style resource defined by this library for the theme specified in the given + * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. + * + * @param intent The intent passed by setup wizard, or one with the theme propagated along using + * {@link #copyWizardManagerExtras(Intent, Intent)}. + * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if + * the given theme is not recognized. + * @see #getThemeRes(String, int) + * @deprecated it is recommended to use {@link ThemeResolver} which allows setting the default + * theme in one place and applying it to multiple screens. + */ + @Deprecated + public static @StyleRes int getThemeRes(Intent intent, @StyleRes int defaultTheme) { + return new ThemeResolver.Builder(ThemeResolver.getDefault()) + .setDefaultTheme(defaultTheme) + .setUseDayNight(false) + .build() + .resolve(intent); + } + + /** + * Gets the theme style resource defined by this library for the theme specified in the given + * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. + * + * @param intent The intent passed by setup wizard, or one with the theme propagated along using + * {@link #copyWizardManagerExtras(Intent, Intent)}. + * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if + * the given theme is not recognized. Return the {@code defaultTheme} if the specified theme + * is older than the oldest supported one. + * @see #getThemeRes(String, int) + * @deprecated it is recommended to use {@link ThemeResolver} which allows setting the default + * theme and oldest supported theme in one place and applying it to multiple screens. + */ + @Deprecated + public static @StyleRes int getThemeRes( + Intent intent, @StyleRes int defaultTheme, @Nullable String oldestSupportedTheme) { + return new ThemeResolver.Builder(ThemeResolver.getDefault()) + .setDefaultTheme(defaultTheme) + .setUseDayNight(false) + .setOldestSupportedTheme(oldestSupportedTheme) + .build() + .resolve(intent); + } + + /** + * Gets the theme style resource defined by this library for the given theme name. For example, + * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. + * + * <p>If you require extra theme attributes but want to ensure forward compatibility with new + * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in + * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. + * + * <pre>{@code + * protected void onApplyThemeResource(Theme theme, int resid, boolean first) { + * super.onApplyThemeResource(theme, resid, first); + * theme.applyStyle(R.style.MyThemeOverlay, true); + * } + * }</pre> + * + * @param theme The string representation of the theme. + * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the + * given theme is not recognized. + * @deprecated it is recommended to use {@link ThemeResolver} which allows setting the default + * theme in one place and applying it to multiple screens. + */ + @Deprecated + public static @StyleRes int getThemeRes( + String theme, @StyleRes int defaultTheme, @Nullable String oldestSupportedTheme) { + return new ThemeResolver.Builder(ThemeResolver.getDefault()) + .setDefaultTheme(defaultTheme) + .setUseDayNight(false) + .setOldestSupportedTheme(oldestSupportedTheme) + .build() + .resolve(theme); + } + + /** + * Gets the theme style resource defined by this library for the given theme name. For example, + * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. + * + * <p>If you require extra theme attributes but want to ensure forward compatibility with new + * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in + * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. + * + * <pre>{@code + * protected void onApplyThemeResource(Theme theme, int resid, boolean first) { + * super.onApplyThemeResource(theme, resid, first); + * theme.applyStyle(R.style.MyThemeOverlay, true); + * } + * }</pre> + * + * @param theme The string representation of the theme. + * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the + * given theme is not recognized. + * @deprecated it is recommended to use {@link ThemeResolver} which allows setting the default + * theme in one place and applying it to multiple screens. + */ + @Deprecated + public static @StyleRes int getThemeRes(@Nullable String theme, @StyleRes int defaultTheme) { + return new ThemeResolver.Builder(ThemeResolver.getDefault()) + .setDefaultTheme(defaultTheme) + .setUseDayNight(false) + .build() + .resolve(theme); + } + + /** + * Reads the theme from the intent, and applies the theme to the activity as resolved by {@link + * ThemeResolver#getDefault()}. + * + * <p>If you require extra theme attributes, consider overriding {@link + * android.app.Activity#onApplyThemeResource} in your activity and call {@link + * Theme#applyStyle(int, boolean)} using your theme overlay. + * + * <pre>{@code + * protected void onApplyThemeResource(Theme theme, int resid, boolean first) { + * super.onApplyThemeResource(theme, resid, first); + * theme.applyStyle(R.style.MyThemeOverlay, true); + * } + * }</pre> + * + * @param activity the activity to get the intent from and apply the resulting theme to. + */ + public static void applyTheme(Activity activity) { + ThemeResolver.getDefault().applyTheme(activity); + } } diff --git a/library/main/src/com/android/setupwizardlib/view/BottomScrollView.java b/library/main/src/com/android/setupwizardlib/view/BottomScrollView.java index eeb40a9..962538f 100644 --- a/library/main/src/com/android/setupwizardlib/view/BottomScrollView.java +++ b/library/main/src/com/android/setupwizardlib/view/BottomScrollView.java @@ -17,12 +17,11 @@ package com.android.setupwizardlib.view; import android.content.Context; +import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; import android.view.View; import android.widget.ScrollView; -import androidx.annotation.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 @@ -30,75 +29,81 @@ import androidx.annotation.VisibleForTesting; */ public class BottomScrollView extends ScrollView { - public interface BottomScrollListener { - void onScrolledToBottom(); - void onRequiresScroll(); - } + public interface BottomScrollListener { + void onScrolledToBottom(); + + void onRequiresScroll(); + } - private BottomScrollListener mListener; - private int mScrollThreshold; - private boolean mRequiringScroll = false; + private BottomScrollListener listener; + private int scrollThreshold; + private boolean requiringScroll = false; - private final Runnable mCheckScrollRunnable = new Runnable() { + private final Runnable checkScrollRunnable = + new Runnable() { @Override public void run() { - checkScroll(); + checkScroll(); } - }; - - public BottomScrollView(Context context) { - super(context); + }; + + 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) { + listener = l; + } + + @VisibleForTesting + public BottomScrollListener getBottomScrollListener() { + return listener; + } + + @VisibleForTesting + public int getScrollThreshold() { + return scrollThreshold; + } + + @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) { + scrollThreshold = Math.max(0, child.getMeasuredHeight() - b + t - getPaddingBottom()); } - - public BottomScrollView(Context context, AttributeSet attrs) { - super(context, attrs); + 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(checkScrollRunnable); } + } - public BottomScrollView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + if (oldt != t) { + checkScroll(); } - - public void setBottomScrollListener(BottomScrollListener l) { - mListener = l; + } + + private void checkScroll() { + if (listener != null) { + if (getScrollY() >= scrollThreshold) { + listener.onScrolledToBottom(); + } else if (!requiringScroll) { + requiringScroll = true; + listener.onRequiresScroll(); + } } - - @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/library/main/src/com/android/setupwizardlib/view/ButtonBarLayout.java b/library/main/src/com/android/setupwizardlib/view/ButtonBarLayout.java index f7f5f99..4be4f56 100644 --- a/library/main/src/com/android/setupwizardlib/view/ButtonBarLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/ButtonBarLayout.java @@ -20,105 +20,99 @@ import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; - import com.android.setupwizardlib.R; /** * An extension of LinearLayout that automatically switches to vertical orientation when it can't * fit its child views horizontally. * - * Modified from {@code com.android.internal.widget.ButtonBarLayout} + * <p>Modified from {@code com.android.internal.widget.ButtonBarLayout} */ public class ButtonBarLayout extends LinearLayout { - private boolean mStacked = false; - private int mOriginalPaddingLeft; - private int mOriginalPaddingRight; + private boolean stacked = false; + private int originalPaddingLeft; + private int originalPaddingRight; - public ButtonBarLayout(Context context) { - super(context); - } + public ButtonBarLayout(Context context) { + super(context); + } - public ButtonBarLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } + public ButtonBarLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); - setStacked(false); + setStacked(false); - boolean needsRemeasure = false; + boolean needsRemeasure = false; - int initialWidthMeasureSpec = widthMeasureSpec; - if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { - // Measure with WRAP_CONTENT, so that we can compare the measured size with the - // available size to see if we need to stack. - initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + int initialWidthMeasureSpec = widthMeasureSpec; + if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { + // Measure with WRAP_CONTENT, so that we can compare the measured size with the + // available size to see if we need to stack. + initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - // We'll need to remeasure again to fill excess space. - needsRemeasure = true; - } + // We'll need to remeasure again to fill excess space. + needsRemeasure = true; + } - super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); + super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); - if (getMeasuredWidth() > widthSize) { - setStacked(true); + if (getMeasuredWidth() > widthSize) { + setStacked(true); - // Measure again in the new orientation. - needsRemeasure = true; - } + // Measure again in the new orientation. + needsRemeasure = true; + } - if (needsRemeasure) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } + if (needsRemeasure) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + } - private void setStacked(boolean stacked) { - if (mStacked == stacked) { - return; - } - mStacked = stacked; - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - LayoutParams childParams = (LayoutParams) child.getLayoutParams(); - if (stacked) { - child.setTag(R.id.suw_original_weight, childParams.weight); - childParams.weight = 0; - } else { - Float weight = (Float) child.getTag(R.id.suw_original_weight); - if (weight != null) { - childParams.weight = weight; - } - } - child.setLayoutParams(childParams); + private void setStacked(boolean stacked) { + if (this.stacked == stacked) { + return; + } + this.stacked = stacked; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + LayoutParams childParams = (LayoutParams) child.getLayoutParams(); + if (stacked) { + child.setTag(R.id.suw_original_weight, childParams.weight); + childParams.weight = 0; + } else { + Float weight = (Float) child.getTag(R.id.suw_original_weight); + if (weight != null) { + childParams.weight = weight; } + } + child.setLayoutParams(childParams); + } - setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); + setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); - // Reverse the child order, so that the primary button is towards the top when vertical - for (int i = childCount - 1; i >= 0; i--) { - bringChildToFront(getChildAt(i)); - } + // Reverse the child order, so that the primary button is towards the top when vertical + for (int i = childCount - 1; i >= 0; i--) { + bringChildToFront(getChildAt(i)); + } - if (stacked) { - // HACK: In the default button bar style, the left and right paddings are not - // balanced to compensate for different alignment for borderless (left) button and - // the raised (right) button. When it's stacked, we want the buttons to be centered, - // so we balance out the paddings here. - mOriginalPaddingLeft = getPaddingLeft(); - mOriginalPaddingRight = getPaddingRight(); - int paddingHorizontal = Math.max(mOriginalPaddingLeft, mOriginalPaddingRight); - setPadding( - paddingHorizontal, getPaddingTop(), paddingHorizontal, getPaddingBottom()); - } else { - setPadding( - mOriginalPaddingLeft, - getPaddingTop(), - mOriginalPaddingRight, - getPaddingBottom()); - } + if (stacked) { + // HACK: In the default button bar style, the left and right paddings are not + // balanced to compensate for different alignment for borderless (left) button and + // the raised (right) button. When it's stacked, we want the buttons to be centered, + // so we balance out the paddings here. + originalPaddingLeft = getPaddingLeft(); + originalPaddingRight = getPaddingRight(); + int paddingHorizontal = Math.max(originalPaddingLeft, originalPaddingRight); + setPadding(paddingHorizontal, getPaddingTop(), paddingHorizontal, getPaddingBottom()); + } else { + setPadding(originalPaddingLeft, getPaddingTop(), originalPaddingRight, getPaddingBottom()); } + } } diff --git a/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java b/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java index 9605f99..fd2319c 100644 --- a/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java @@ -19,74 +19,67 @@ package com.android.setupwizardlib.view; import android.annotation.TargetApi; import android.content.Context; import android.os.Build.VERSION_CODES; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.widget.Checkable; import android.widget.LinearLayout; -import androidx.annotation.Nullable; - /** - * A LinearLayout which is checkable. This will set the checked state when - * {@link #onCreateDrawableState(int)} is called, and can be used with - * {@code android:duplicateParentState} to propagate the drawable state to child views. + * A LinearLayout which is checkable. This will set the checked state when {@link + * #onCreateDrawableState(int)} is called, and can be used with {@code android:duplicateParentState} + * to propagate the drawable state to child views. */ public class CheckableLinearLayout extends LinearLayout implements Checkable { - private boolean mChecked = false; + private boolean checked = false; - public CheckableLinearLayout(Context context) { - super(context); - } + public CheckableLinearLayout(Context context) { + super(context); + } - public CheckableLinearLayout(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } + public CheckableLinearLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } - @TargetApi(VERSION_CODES.HONEYCOMB) - public CheckableLinearLayout( - Context context, - @Nullable AttributeSet attrs, - int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + @TargetApi(VERSION_CODES.HONEYCOMB) + public CheckableLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @TargetApi(VERSION_CODES.LOLLIPOP) - public CheckableLinearLayout( - Context context, - AttributeSet attrs, - int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } + @TargetApi(VERSION_CODES.LOLLIPOP) + public CheckableLinearLayout( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } - { - setFocusable(true); - } + { + setFocusable(true); + } - @Override - protected int[] onCreateDrawableState(int extraSpace) { - if (mChecked) { - final int[] superStates = super.onCreateDrawableState(extraSpace + 1); - final int[] checked = new int[] { android.R.attr.state_checked }; - return mergeDrawableStates(superStates, checked); - } else { - return super.onCreateDrawableState(extraSpace); - } + @Override + protected int[] onCreateDrawableState(int extraSpace) { + if (this.checked) { + final int[] superStates = super.onCreateDrawableState(extraSpace + 1); + final int[] checked = new int[] {android.R.attr.state_checked}; + return mergeDrawableStates(superStates, checked); + } else { + return super.onCreateDrawableState(extraSpace); } + } - @Override - public void setChecked(boolean checked) { - mChecked = checked; - refreshDrawableState(); - } + @Override + public void setChecked(boolean checked) { + this.checked = checked; + refreshDrawableState(); + } - @Override - public boolean isChecked() { - return mChecked; - } + @Override + public boolean isChecked() { + return checked; + } - @Override - public void toggle() { - setChecked(!isChecked()); - } + @Override + public void toggle() { + setChecked(!isChecked()); + } } diff --git a/library/main/src/com/android/setupwizardlib/view/FillContentLayout.java b/library/main/src/com/android/setupwizardlib/view/FillContentLayout.java index 2c28090..b72d4d2 100644 --- a/library/main/src/com/android/setupwizardlib/view/FillContentLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/FillContentLayout.java @@ -21,13 +21,12 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; - import com.android.setupwizardlib.R; /** - * A layout that will measure its children size based on the space it is given, by using its - * {@code android:minWidth}, {@code android:minHeight}, {@code android:maxWidth}, and - * {@code android:maxHeight} values. + * A layout that will measure its children size based on the space it is given, by using its {@code + * android:minWidth}, {@code android:minHeight}, {@code android:maxWidth}, and {@code + * android:maxHeight} values. * * <p>Typically this is used to show an illustration image or video on the screen. For optimal UX, * those assets typically want to occupy the remaining space available on screen within a certain @@ -42,84 +41,82 @@ import com.android.setupwizardlib.R; */ public class FillContentLayout extends FrameLayout { - private int mMaxWidth; - private int mMaxHeight; - - public FillContentLayout(Context context) { - this(context, null); - } - - public FillContentLayout(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.suwFillContentLayoutStyle); - } - - public FillContentLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } + private int maxWidth; + private int maxHeight; - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - TypedArray a = context.obtainStyledAttributes( - attrs, - R.styleable.SuwFillContentLayout, - defStyleAttr, - 0); + public FillContentLayout(Context context) { + this(context, null); + } - mMaxHeight = a.getDimensionPixelSize( - R.styleable.SuwFillContentLayout_android_maxHeight, -1); - mMaxWidth = a.getDimensionPixelSize(R.styleable.SuwFillContentLayout_android_maxWidth, -1); + public FillContentLayout(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.suwFillContentLayoutStyle); + } - a.recycle(); - } + public FillContentLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // Measure this view with the minWidth and minHeight, without asking the children. - // (Children size is the drawable's intrinsic size, and we don't want that to influence - // the size of the illustration). - setMeasuredDimension( - getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), - getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); - - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - measureIllustrationChild(getChildAt(i), getMeasuredWidth(), getMeasuredHeight()); - } - } + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwFillContentLayout, defStyleAttr, 0); - private void measureIllustrationChild(View child, int parentWidth, int parentHeight) { - // Modified from ViewGroup#measureChildWithMargins - final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + maxHeight = a.getDimensionPixelSize(R.styleable.SuwFillContentLayout_android_maxHeight, -1); + maxWidth = a.getDimensionPixelSize(R.styleable.SuwFillContentLayout_android_maxWidth, -1); - // Create measure specs that are no bigger than min(parentSize, maxSize) + a.recycle(); + } - int childWidthMeasureSpec = getMaxSizeMeasureSpec( - Math.min(mMaxWidth, parentWidth), - getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, - lp.width); - int childHeightMeasureSpec = getMaxSizeMeasureSpec( - Math.min(mMaxHeight, parentHeight), - getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, - lp.height); + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Measure this view with the minWidth and minHeight, without asking the children. + // (Children size is the drawable's intrinsic size, and we don't want that to influence + // the size of the illustration). + setMeasuredDimension( + getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), + getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + measureIllustrationChild(getChildAt(i), getMeasuredWidth(), getMeasuredHeight()); } - - private static int getMaxSizeMeasureSpec(int maxSize, int padding, int childDimension) { - // Modified from ViewGroup#getChildMeasureSpec - int size = Math.max(0, maxSize - padding); - - if (childDimension >= 0) { - // Child wants a specific size... so be it - return MeasureSpec.makeMeasureSpec(childDimension, MeasureSpec.EXACTLY); - } else if (childDimension == LayoutParams.MATCH_PARENT) { - // Child wants to be our size. So be it. - return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); - } else if (childDimension == LayoutParams.WRAP_CONTENT) { - // Child wants to determine its own size. It can't be - // bigger than us. - return MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); - } - return 0; + } + + private void measureIllustrationChild(View child, int parentWidth, int parentHeight) { + // Modified from ViewGroup#measureChildWithMargins + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + // Create measure specs that are no bigger than min(parentSize, maxSize) + + int childWidthMeasureSpec = + getMaxSizeMeasureSpec( + Math.min(maxWidth, parentWidth), + getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, + lp.width); + int childHeightMeasureSpec = + getMaxSizeMeasureSpec( + Math.min(maxHeight, parentHeight), + getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, + lp.height); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + private static int getMaxSizeMeasureSpec(int maxSize, int padding, int childDimension) { + // Modified from ViewGroup#getChildMeasureSpec + int size = Math.max(0, maxSize - padding); + + if (childDimension >= 0) { + // Child wants a specific size... so be it + return MeasureSpec.makeMeasureSpec(childDimension, MeasureSpec.EXACTLY); + } else if (childDimension == LayoutParams.MATCH_PARENT) { + // Child wants to be our size. So be it. + return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + } else if (childDimension == LayoutParams.WRAP_CONTENT) { + // Child wants to determine its own size. It can't be + // bigger than us. + return MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); } + return 0; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/Illustration.java b/library/main/src/com/android/setupwizardlib/view/Illustration.java index c6968f8..e9cd4b5 100644 --- a/library/main/src/com/android/setupwizardlib/view/Illustration.java +++ b/library/main/src/com/android/setupwizardlib/view/Illustration.java @@ -30,7 +30,6 @@ import android.util.LayoutDirection; import android.view.Gravity; import android.view.ViewOutlineProvider; import android.widget.FrameLayout; - import com.android.setupwizardlib.R; /** @@ -39,184 +38,190 @@ import com.android.setupwizardlib.R; * drawable to fit the width of the view and fills the rest with the background. * * <p>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. + * Otherwise 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); + // Size of the baseline grid in pixels + private float baselineGridSize; + private Drawable background; + private Drawable illustration; + private final Rect viewBounds = new Rect(); + private final Rect illustrationBounds = new Rect(); + private float scale = 1.0f; + private float aspectRatio = 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); + aspectRatio = a.getFloat(R.styleable.SuwIllustration_suwAspectRatio, 0.0f); + a.recycle(); } - - @TargetApi(VERSION_CODES.HONEYCOMB) - public Illustration(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs, defStyleAttr); + // Number of pixels of the 8dp baseline grid as defined in material design specs + baselineGridSize = 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 == this.background) { + return; } - - // 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(); + this.background = 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 == this.illustration) { + return; } - - /** - * 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(); + this.illustration = 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) { + this.aspectRatio = aspectRatio; + invalidate(); + requestLayout(); + } + + @Override + @Deprecated + public void setForeground(Drawable d) { + setIllustration(d); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (aspectRatio != 0.0f) { + int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + int illustrationHeight = (int) (parentWidth / aspectRatio); + illustrationHeight = (int) (illustrationHeight - (illustrationHeight % baselineGridSize)); + setPadding(0, illustrationHeight, 0, 0); } - - /** - * 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(); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + //noinspection AndroidLintInlinedApi + setOutlineProvider(ViewOutlineProvider.BOUNDS); } - - @Override - @Deprecated - public void setForeground(Drawable d) { - setIllustration(d); + 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 (illustration != null) { + int intrinsicWidth = illustration.getIntrinsicWidth(); + int intrinsicHeight = illustration.getIntrinsicHeight(); + + viewBounds.set(0, 0, layoutWidth, layoutHeight); + if (aspectRatio != 0f) { + scale = layoutWidth / (float) intrinsicWidth; + intrinsicWidth = layoutWidth; + intrinsicHeight = (int) (intrinsicHeight * scale); + } + Gravity.apply( + Gravity.FILL_HORIZONTAL | Gravity.TOP, + intrinsicWidth, + intrinsicHeight, + viewBounds, + illustrationBounds); + illustration.setBounds(illustrationBounds); } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mAspectRatio != 0.0f) { - int parentWidth = MeasureSpec.getSize(widthMeasureSpec); - int illustrationHeight = (int) (parentWidth / mAspectRatio); - illustrationHeight = - (int) (illustrationHeight - (illustrationHeight % mBaselineGridSize)); - setPadding(0, illustrationHeight, 0, 0); - } - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - //noinspection AndroidLintInlinedApi - setOutlineProvider(ViewOutlineProvider.BOUNDS); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (background != null) { + // Scale the background bounds by the same scale to compensate for the scale done to the + // canvas in onDraw. + background.setBounds( + 0, + 0, + (int) Math.ceil(layoutWidth / scale), + (int) Math.ceil((layoutHeight - illustrationBounds.height()) / scale)); } - - @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); + super.onLayout(changed, left, top, right, bottom); + } + + @Override + public void onDraw(Canvas canvas) { + if (background != null) { + // Draw the background filling parts not covered by the illustration + canvas.save(); + canvas.translate(0, illustrationBounds.height()); + // Scale the background so its size matches the foreground + canvas.scale(scale, scale, 0, 0); + if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1 + && shouldMirrorDrawable(background, getLayoutDirection())) { + // Flip the illustration for RTL layouts + canvas.scale(-1, 1); + canvas.translate(-background.getBounds().width(), 0); + } + background.draw(canvas); + canvas.restore(); } - - @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); + if (illustration != null) { + canvas.save(); + if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1 + && shouldMirrorDrawable(illustration, getLayoutDirection())) { + // Flip the illustration for RTL layouts + canvas.scale(-1, 1); + canvas.translate(-illustrationBounds.width(), 0); + } + // Draw the illustration + illustration.draw(canvas); + canvas.restore(); } - - 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; + 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/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java index 53149ea..375ee18 100644 --- a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java +++ b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java @@ -22,18 +22,23 @@ import android.content.res.TypedArray; import android.graphics.SurfaceTexture; import android.graphics.drawable.Animatable; import android.media.MediaPlayer; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnInfoListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.media.MediaPlayer.OnSeekCompleteListener; +import android.net.Uri; import android.os.Build.VERSION_CODES; +import androidx.annotation.Nullable; +import androidx.annotation.RawRes; +import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; import android.util.Log; import android.view.Surface; import android.view.TextureView; +import android.view.TextureView.SurfaceTextureListener; import android.view.View; - -import androidx.annotation.Nullable; -import androidx.annotation.RawRes; -import androidx.annotation.VisibleForTesting; - import com.android.setupwizardlib.R; +import java.io.IOException; /** * A view for displaying videos in a continuous loop (without audio). This is typically used for @@ -44,255 +49,295 @@ import com.android.setupwizardlib.R; * should loop back to * * <p>For optimal file size, use avconv or other video compression tool to remove the unused audio - * track and reduce the size of your video asset: - * avconv -i [input file] -vcodec h264 -crf 20 -an [output_file] + * track and reduce the size of your video asset: avconv -i [input file] -vcodec h264 -crf 20 -an + * [output_file] */ @TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) -public class IllustrationVideoView extends TextureView implements Animatable, - TextureView.SurfaceTextureListener, - MediaPlayer.OnPreparedListener, - MediaPlayer.OnSeekCompleteListener, - MediaPlayer.OnInfoListener { +public class IllustrationVideoView extends TextureView + implements Animatable, + SurfaceTextureListener, + OnPreparedListener, + OnSeekCompleteListener, + OnInfoListener, + OnErrorListener { - private static final String TAG = "IllustrationVideoView"; + private static final String TAG = "IllustrationVideoView"; - protected float mAspectRatio = 1.0f; // initial guess until we know + protected float mAspectRatio = 1.0f; // initial guess until we know - @Nullable // Can be null when media player fails to initialize - protected MediaPlayer mMediaPlayer; + @Nullable // Can be null when media player fails to initialize + protected MediaPlayer mMediaPlayer; - private @RawRes int mVideoResId = 0; + private @RawRes int videoResId = 0; - @VisibleForTesting Surface mSurface; + private String videoResPackageName; - protected int mWindowVisibility; + @VisibleForTesting Surface surface; - public IllustrationVideoView(Context context, AttributeSet attrs) { - super(context, attrs); - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SuwIllustrationVideoView); - mVideoResId = a.getResourceId(R.styleable.SuwIllustrationVideoView_suwVideo, 0); - a.recycle(); + private boolean prepared; - // By default the video scales without interpolation, resulting in jagged edges in the - // video. This works around it by making the view go through scaling, which will apply - // anti-aliasing effects. - setScaleX(0.9999999f); - setScaleX(0.9999999f); + public IllustrationVideoView(Context context, AttributeSet attrs) { + super(context, attrs); + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwIllustrationVideoView); + final int videoResId = a.getResourceId(R.styleable.SuwIllustrationVideoView_suwVideo, 0); + a.recycle(); + setVideoResource(videoResId); - setSurfaceTextureListener(this); - } + // By default the video scales without interpolation, resulting in jagged edges in the + // video. This works around it by making the view go through scaling, which will apply + // anti-aliasing effects. + setScaleX(0.9999999f); + setScaleX(0.9999999f); - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - - if (height < width * mAspectRatio) { - // Height constraint is tighter. Need to scale down the width to fit aspect ratio. - width = (int) (height / mAspectRatio); - } else { - // Width constraint is tighter. Need to scale down the height to fit aspect ratio. - height = (int) (width * mAspectRatio); - } - - super.onMeasure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); - } + setSurfaceTextureListener(this); + } - /** - * Set the video to be played by this view. - * - * @param resId Resource ID of the video, typically an MP4 under res/raw. - */ - public void setVideoResource(@RawRes int resId) { - if (resId != mVideoResId) { - mVideoResId = resId; - createMediaPlayer(); - } - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - if (hasWindowFocus) { - start(); - } else { - stop(); - } + if (height < width * mAspectRatio) { + // Height constraint is tighter. Need to scale down the width to fit aspect ratio. + width = (int) (height / mAspectRatio); + } else { + // Width constraint is tighter. Need to scale down the height to fit aspect ratio. + height = (int) (width * mAspectRatio); } - /** - * Creates a media player for the current URI. The media player will be started immediately if - * the view's window is visible. If there is an existing media player, it will be released. - */ - protected void createMediaPlayer() { - if (mMediaPlayer != null) { - mMediaPlayer.release(); - } - if (mSurface == null || mVideoResId == 0) { - return; - } - - mMediaPlayer = MediaPlayer.create(getContext(), mVideoResId); - - if (mMediaPlayer != null) { - mMediaPlayer.setSurface(mSurface); - mMediaPlayer.setOnPreparedListener(this); - mMediaPlayer.setOnSeekCompleteListener(this); - mMediaPlayer.setOnInfoListener(this); - - float aspectRatio = - (float) mMediaPlayer.getVideoHeight() / mMediaPlayer.getVideoWidth(); - if (mAspectRatio != aspectRatio) { - mAspectRatio = aspectRatio; - requestLayout(); - } - } else { - Log.wtf(TAG, "Unable to initialize media player for video view"); - } - if (mWindowVisibility == View.VISIBLE) { - start(); - } + super.onMeasure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + + /** + * Set the video and video package name to be played by this view. + * + * @param videoResId Resource ID of the video, typically an MP4 under res/raw. + * @param videoResPackageName The package name of videoResId. + */ + public void setVideoResource(@RawRes int videoResId, String videoResPackageName) { + if (videoResId != this.videoResId + || (videoResPackageName != null && !videoResPackageName.equals(this.videoResPackageName))) { + this.videoResId = videoResId; + this.videoResPackageName = videoResPackageName; + createMediaPlayer(); } - - protected void createSurface() { - if (mSurface != null) { - mSurface.release(); - mSurface = null; - } - // Reattach only if it has been previously released - SurfaceTexture surfaceTexture = getSurfaceTexture(); - if (surfaceTexture != null) { - setVisibility(View.INVISIBLE); - mSurface = new Surface(surfaceTexture); - } + } + + /** + * Set the video to be played by this view. + * + * @param resId Resource ID of the video, typically an MP4 under res/raw. + */ + public void setVideoResource(@RawRes int resId) { + setVideoResource(resId, getContext().getPackageName()); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (hasWindowFocus) { + start(); + } else { + stop(); } - - @Override - protected void onWindowVisibilityChanged(int visibility) { - super.onWindowVisibilityChanged(visibility); - mWindowVisibility = visibility; - if (visibility == View.VISIBLE) { - reattach(); - } else { - release(); - } + } + + /** + * Creates a media player for the current URI. The media player will be started immediately if the + * view's window is visible. If there is an existing media player, it will be released. + */ + protected void createMediaPlayer() { + if (mMediaPlayer != null) { + mMediaPlayer.release(); } - - /** - * Whether the media player should play the video in a continuous loop. The default value is - * true. - */ - protected boolean shouldLoop() { - return true; + if (surface == null || videoResId == 0) { + return; } - /** - * Release any resources used by this view. This is automatically called in - * onSurfaceTextureDestroyed so in most cases you don't have to call this. - */ - public void release() { - if (mMediaPlayer != null) { - mMediaPlayer.stop(); - mMediaPlayer.release(); - mMediaPlayer = null; - } - if (mSurface != null) { - mSurface.release(); - mSurface = null; - } + mMediaPlayer = new MediaPlayer(); + + mMediaPlayer.setSurface(surface); + mMediaPlayer.setOnPreparedListener(this); + mMediaPlayer.setOnSeekCompleteListener(this); + mMediaPlayer.setOnInfoListener(this); + mMediaPlayer.setOnErrorListener(this); + + setVideoResourceInternal(videoResId, videoResPackageName); + } + + private void setVideoResourceInternal(@RawRes int videoRes, String videoResPackageName) { + Uri uri = Uri.parse("android.resource://" + videoResPackageName + "/" + videoRes); + try { + mMediaPlayer.setDataSource(getContext(), uri, null); + mMediaPlayer.prepareAsync(); + } catch (IOException e) { + Log.wtf(TAG, "Unable to set data source", e); } + } - private void reattach() { - if (mSurface == null) { - initVideo(); - } + protected void createSurface() { + if (surface != null) { + surface.release(); + surface = null; + } + // Reattach only if it has been previously released + SurfaceTexture surfaceTexture = getSurfaceTexture(); + if (surfaceTexture != null) { + setVisibility(View.INVISIBLE); + surface = new Surface(surfaceTexture); } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + if (visibility == View.VISIBLE) { + reattach(); + } else { + release(); + } + } + + /** + * Whether the media player should play the video in a continuous loop. The default value is true. + */ + protected boolean shouldLoop() { + return true; + } + + /** + * Release any resources used by this view. This is automatically called in + * onSurfaceTextureDestroyed so in most cases you don't have to call this. + */ + public void release() { + if (mMediaPlayer != null) { + mMediaPlayer.release(); + mMediaPlayer = null; + prepared = false; + } + if (surface != null) { + surface.release(); + surface = null; + } + } - private void initVideo() { - if (mWindowVisibility != View.VISIBLE) { - return; - } - createSurface(); - if (mSurface != null) { - createMediaPlayer(); - } else { - Log.w("IllustrationVideoView", "Surface creation failed"); - } + private void reattach() { + if (surface == null) { + initVideo(); } + } - protected void onRenderingStart() { + private void initVideo() { + if (getWindowVisibility() != View.VISIBLE) { + return; } + createSurface(); + if (surface != null) { + createMediaPlayer(); + } else { + Log.w(TAG, "Surface creation failed"); + } + } - /* SurfaceTextureListener methods */ + protected void onRenderingStart() {} - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { - // Keep the view hidden until video starts - setVisibility(View.INVISIBLE); - initVideo(); - } + /* SurfaceTextureListener methods */ - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { - } + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { + // Keep the view hidden until video starts + setVisibility(View.INVISIBLE); + initVideo(); + } - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { - release(); - return true; - } + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {} - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { - } + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + release(); + return true; + } - /* Animatable methods */ + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {} - @Override - public void start() { - if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) { - mMediaPlayer.start(); - } - } + /* Animatable methods */ - @Override - public void stop() { - if (mMediaPlayer != null) { - mMediaPlayer.pause(); - } + @Override + public void start() { + if (prepared && mMediaPlayer != null && !mMediaPlayer.isPlaying()) { + mMediaPlayer.start(); } + } - @Override - public boolean isRunning() { - return mMediaPlayer != null && mMediaPlayer.isPlaying(); + @Override + public void stop() { + if (prepared && mMediaPlayer != null) { + mMediaPlayer.pause(); } + } - /* MediaPlayer callbacks */ + @Override + public boolean isRunning() { + return mMediaPlayer != null && mMediaPlayer.isPlaying(); + } - @Override - public boolean onInfo(MediaPlayer mp, int what, int extra) { - if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { - // Video available, show view now - setVisibility(View.VISIBLE); - onRenderingStart(); - } - return false; - } + /* MediaPlayer callbacks */ - @Override - public void onPrepared(MediaPlayer mp) { - mp.setLooping(shouldLoop()); + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { + // Video available, show view now + setVisibility(View.VISIBLE); + onRenderingStart(); } - - @Override - public void onSeekComplete(MediaPlayer mp) { - mp.start(); + return false; + } + + @Override + public void onPrepared(MediaPlayer mp) { + prepared = true; + mp.setLooping(shouldLoop()); + + float aspectRatio = 0.0f; + if (mp.getVideoWidth() > 0 && mp.getVideoHeight() > 0) { + aspectRatio = (float) mp.getVideoHeight() / mp.getVideoWidth(); + } else { + Log.w(TAG, "Unexpected video size=" + mp.getVideoWidth() + "x" + mp.getVideoHeight()); } - - public int getCurrentPosition() { - return mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition(); + if (Float.compare(mAspectRatio, aspectRatio) != 0) { + mAspectRatio = aspectRatio; + requestLayout(); + } + if (getWindowVisibility() == View.VISIBLE) { + start(); } + } + + @Override + public void onSeekComplete(MediaPlayer mp) { + if (isPrepared()) { + mp.start(); + } else { + Log.wtf(TAG, "Seek complete but media player not prepared"); + } + } + + public int getCurrentPosition() { + return mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition(); + } + + protected boolean isPrepared() { + return prepared; + } + + @Override + public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { + Log.w(TAG, "MediaPlayer error. what=" + what + " extra=" + extra); + return false; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java b/library/main/src/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java index 02fdcc7..c6a38f6 100644 --- a/library/main/src/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java @@ -22,71 +22,71 @@ 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. + * 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}. + * <p>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; + private int intrinsicHeight = 0; + private int intrinsicWidth = 0; - public IntrinsicSizeFrameLayout(Context context) { - super(context); - init(context, null, 0); - } + public IntrinsicSizeFrameLayout(Context context) { + super(context); + init(context, null, 0); + } - public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 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); - } + @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.SuwIntrinsicSizeFrameLayout, defStyleAttr, 0); - mIntrinsicHeight = - a.getDimensionPixelSize(R.styleable.SuwIntrinsicSizeFrameLayout_android_height, 0); - mIntrinsicWidth = - a.getDimensionPixelSize(R.styleable.SuwIntrinsicSizeFrameLayout_android_width, 0); - a.recycle(); - } + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = + context.obtainStyledAttributes( + attrs, R.styleable.SuwIntrinsicSizeFrameLayout, defStyleAttr, 0); + intrinsicHeight = + a.getDimensionPixelSize(R.styleable.SuwIntrinsicSizeFrameLayout_android_height, 0); + intrinsicWidth = + a.getDimensionPixelSize(R.styleable.SuwIntrinsicSizeFrameLayout_android_width, 0); + a.recycle(); + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(getIntrinsicMeasureSpec(widthMeasureSpec, mIntrinsicWidth), - getIntrinsicMeasureSpec(heightMeasureSpec, mIntrinsicHeight)); - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure( + getIntrinsicMeasureSpec(widthMeasureSpec, intrinsicWidth), + getIntrinsicMeasureSpec(heightMeasureSpec, intrinsicHeight)); + } - 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; + 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(intrinsicHeight, 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, intrinsicHeight), MeasureSpec.EXACTLY); } + // Parent specified EXACTLY, or in all other cases, just return the original spec + return measureSpec; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/NavigationBar.java b/library/main/src/com/android/setupwizardlib/view/NavigationBar.java index 9971bac..1044e56 100644 --- a/library/main/src/com/android/setupwizardlib/view/NavigationBar.java +++ b/library/main/src/com/android/setupwizardlib/view/NavigationBar.java @@ -21,14 +21,12 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build.VERSION_CODES; +import androidx.annotation.StyleableRes; import android.util.AttributeSet; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; - -import androidx.annotation.StyleableRes; - import com.android.setupwizardlib.R; /** @@ -40,104 +38,105 @@ import com.android.setupwizardlib.R; */ 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(); + /** + * 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 + }); + @StyleableRes int suwNavBarTheme = 0; + @StyleableRes int colorForeground = 1; + @StyleableRes int colorBackground = 2; + int theme = attributes.getResourceId(suwNavBarTheme, 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(colorForeground, 0), foregroundHsv); + Color.colorToHSV(attributes.getColor(colorBackground, 0), backgroundHsv); + boolean isDarkBg = foregroundHsv[2] > backgroundHsv[2]; + theme = isDarkBg ? R.style.SuwNavBarThemeDark : R.style.SuwNavBarThemeLight; } - - 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 }); - @StyleableRes int suwNavBarTheme = 0; - @StyleableRes int colorForeground = 1; - @StyleableRes int colorBackground = 2; - int theme = attributes.getResourceId(suwNavBarTheme, 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(colorForeground, 0), foregroundHsv); - Color.colorToHSV(attributes.getColor(colorBackground, 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); + attributes.recycle(); + return theme; + } + + private static Context getThemedContext(Context context) { + final int theme = getNavbarTheme(context); + return new ContextThemeWrapper(context, theme); + } + + private Button nextButton; + private Button backButton; + private Button moreButton; + private NavigationBarListener listener; + + 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); + nextButton = (Button) findViewById(R.id.suw_navbar_next); + backButton = (Button) findViewById(R.id.suw_navbar_back); + moreButton = (Button) findViewById(R.id.suw_navbar_more); + } + + public Button getBackButton() { + return backButton; + } + + public Button getNextButton() { + return nextButton; + } + + public Button getMoreButton() { + return moreButton; + } + + public void setNavigationBarListener(NavigationBarListener listener) { + this.listener = listener; + if (this.listener != null) { + getBackButton().setOnClickListener(this); + getNextButton().setOnClickListener(this); } - - 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(); - } - } + } + + @Override + public void onClick(View view) { + if (listener != null) { + if (view == getBackButton()) { + listener.onNavigateBack(); + } else if (view == getNextButton()) { + listener.onNavigateNext(); + } } + } } diff --git a/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java b/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java index fe0bc8f..dd8c27f 100644 --- a/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java @@ -26,7 +26,6 @@ import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.view.WindowInsets; import android.widget.FrameLayout; - import com.android.setupwizardlib.R; /** @@ -40,74 +39,75 @@ import com.android.setupwizardlib.R; */ public class StatusBarBackgroundLayout extends FrameLayout { - private Drawable mStatusBarBackground; - private Object mLastInsets; // Use generic Object type for compatibility + private Drawable statusBarBackground; + private Object lastInsets; // Use generic Object type for compatibility - public StatusBarBackgroundLayout(Context context) { - super(context); - init(context, null, 0); - } + public StatusBarBackgroundLayout(Context context) { + super(context); + init(context, null, 0); + } - public StatusBarBackgroundLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 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); - } + @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(); - } + 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 onAttachedToWindow() { + super.onAttachedToWindow(); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (lastInsets == 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); - } - } + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (lastInsets != null) { + final int insetTop = ((WindowInsets) lastInsets).getSystemWindowInsetTop(); + if (insetTop > 0) { + statusBarBackground.setBounds(0, 0, getWidth(), insetTop); + statusBarBackground.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 void setStatusBarBackground(Drawable background) { + statusBarBackground = background; + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + setWillNotDraw(background == null); + setFitsSystemWindows(background != null); + invalidate(); } + } - public Drawable getStatusBarBackground() { - return mStatusBarBackground; - } + public Drawable getStatusBarBackground() { + return statusBarBackground; + } - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mLastInsets = insets; - return super.onApplyWindowInsets(insets); - } + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + lastInsets = insets; + return super.onApplyWindowInsets(insets); + } } diff --git a/library/main/src/com/android/setupwizardlib/view/StickyHeaderListView.java b/library/main/src/com/android/setupwizardlib/view/StickyHeaderListView.java index 58274f6..c226f29 100644 --- a/library/main/src/com/android/setupwizardlib/view/StickyHeaderListView.java +++ b/library/main/src/com/android/setupwizardlib/view/StickyHeaderListView.java @@ -29,7 +29,6 @@ import android.view.View; import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.widget.ListView; - import com.android.setupwizardlib.R; /** @@ -39,125 +38,129 @@ import com.android.setupwizardlib.R; * when the sticky element hits the top of the view. * * <p>There are a few things to note: + * * <ol> * <li>The two supported scenarios are StickyHeaderListView -> Header (stickyContainer) -> sticky, - * and StickyHeaderListView -> Header (sticky). The arrow (->) represents parent/child - * relationship and must be immediate child. + * and StickyHeaderListView -> Header (sticky). The arrow (->) represents parent/child + * relationship and must be immediate child. * <li>The view does not work well with padding. b/16190933 * <li>If fitsSystemWindows is true, then this will offset the sticking position by the height of - * the system decorations at the top of the screen. + * the system decorations at the top of the screen. * </ol> * * @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); + private View sticky; + private View stickyContainer; + private int statusBarInset = 0; + private final RectF stickyRect = 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); } - - public StickyHeaderListView(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs, android.R.attr.listViewStyle); + a.recycle(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (sticky == null) { + updateStickyView(); } - - public StickyHeaderListView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs, defStyleAttr); + } + + public void updateStickyView() { + sticky = findViewWithTag("sticky"); + stickyContainer = findViewWithTag("stickyContainer"); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (stickyRect.contains(ev.getX(), ev.getY())) { + ev.offsetLocation(-stickyRect.left, -stickyRect.top); + return stickyContainer.dispatchTouchEvent(ev); + } else { + return super.dispatchTouchEvent(ev); } - - 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(); - } + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (sticky != null) { + final int saveCount = canvas.save(); + // The view to draw when sticking to the top + final View drawTarget = stickyContainer != null ? stickyContainer : sticky; + // The offset to draw the view at when sticky + final int drawOffset = stickyContainer != null ? sticky.getTop() : 0; + // Position of the draw target, relative to the outside of the scrollView + final int drawTop = drawTarget.getTop(); + if (drawTop + drawOffset < statusBarInset || !drawTarget.isShown()) { + // ListView does not translate the canvas, so we can simply draw at the top + stickyRect.set( + 0, + -drawOffset + statusBarInset, + drawTarget.getWidth(), + drawTarget.getHeight() - drawOffset + statusBarInset); + canvas.translate(0, stickyRect.top); + canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight()); + drawTarget.draw(canvas); + } else { + stickyRect.setEmpty(); + } + canvas.restoreToCount(saveCount); } - - public void updateStickyView() { - mSticky = findViewWithTag("sticky"); - mStickyContainer = findViewWithTag("stickyContainer"); + } + + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if (getFitsSystemWindows()) { + statusBarInset = insets.getSystemWindowInsetTop(); + insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + 0, /* top */ + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); } - - @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)); - } + 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 = sticky != 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/library/main/src/com/android/setupwizardlib/view/StickyHeaderScrollView.java b/library/main/src/com/android/setupwizardlib/view/StickyHeaderScrollView.java index ca47446..9fd7b0c 100644 --- a/library/main/src/com/android/setupwizardlib/view/StickyHeaderScrollView.java +++ b/library/main/src/com/android/setupwizardlib/view/StickyHeaderScrollView.java @@ -30,12 +30,13 @@ import android.view.WindowInsets; * drawn when the sticky element hits the top of the view. * * <p>There are a few things to note: + * * <ol> * <li>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. + * sticky, and StickyHeaderScrollView -> container -> subview (sticky). The arrow (->) + * represents parent/child relationship and must be immediate child. * <li>If fitsSystemWindows is true, then this will offset the sticking position by the height of - * the system decorations at the top of the screen. + * the system decorations at the top of the screen. * <li>For versions before Honeycomb, this will behave like a regular ScrollView. * </ol> * @@ -43,75 +44,75 @@ import android.view.WindowInsets; */ public class StickyHeaderScrollView extends BottomScrollView { - private View mSticky; - private View mStickyContainer; - private int mStatusBarInset = 0; + private View sticky; + private View stickyContainer; + private int statusBarInset = 0; - public StickyHeaderScrollView(Context context) { - super(context); - } + public StickyHeaderScrollView(Context context) { + super(context); + } - public StickyHeaderScrollView(Context context, AttributeSet attrs) { - super(context, attrs); - } + public StickyHeaderScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } - public StickyHeaderScrollView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + 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(); + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (sticky == null) { + updateStickyView(); } + updateStickyHeaderPosition(); + } - public void updateStickyView() { - mSticky = findViewWithTag("sticky"); - mStickyContainer = findViewWithTag("stickyContainer"); - } + public void updateStickyView() { + sticky = findViewWithTag("sticky"); + stickyContainer = 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); - } - } + 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 (sticky != null) { + // The view to draw when sticking to the top + final View drawTarget = stickyContainer != null ? stickyContainer : sticky; + // The offset to draw the view at when sticky + final int drawOffset = stickyContainer != null ? sticky.getTop() : 0; + // Position of the draw target, relative to the outside of the scrollView + final int drawTop = drawTarget.getTop() - getScrollY(); + if (drawTop + drawOffset < statusBarInset || !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 + 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; + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if (getFitsSystemWindows()) { + statusBarInset = insets.getSystemWindowInsetTop(); + insets = + insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + 0, /* top */ + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); } + return insets; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java b/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java index 10e91f4..526883c 100644 --- a/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java +++ b/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java @@ -24,60 +24,56 @@ import android.view.MotionEvent; import android.widget.TextView; /** - * A movement method that tracks the last result of whether touch events are handled. This is - * used to patch the return value of {@link TextView#onTouchEvent} so that it consumes the touch - * events only when the movement method says the event is consumed. + * A movement method that tracks the last result of whether touch events are handled. This is used + * to patch the return value of {@link TextView#onTouchEvent} so that it consumes the touch events + * only when the movement method says the event is consumed. */ public interface TouchableMovementMethod { - /** - * @return The last touch event received in {@link MovementMethod#onTouchEvent} - */ - MotionEvent getLastTouchEvent(); + /** @return The last touch event received in {@link MovementMethod#onTouchEvent} */ + MotionEvent getLastTouchEvent(); - /** - * @return The return value of the last {@link MovementMethod#onTouchEvent}, or whether the - * last touch event should be considered handled by the text view - */ - boolean isLastTouchEventHandled(); + /** + * @return The return value of the last {@link MovementMethod#onTouchEvent}, or whether the last + * touch event should be considered handled by the text view + */ + boolean isLastTouchEventHandled(); - /** - * An extension of LinkMovementMethod that tracks whether the event is handled when it is - * touched. - */ - class TouchableLinkMovementMethod extends LinkMovementMethod - implements TouchableMovementMethod { + /** + * An extension of LinkMovementMethod that tracks whether the event is handled when it is touched. + */ + class TouchableLinkMovementMethod extends LinkMovementMethod implements TouchableMovementMethod { - public static TouchableLinkMovementMethod getInstance() { - return new TouchableLinkMovementMethod(); - } + public static TouchableLinkMovementMethod getInstance() { + return new TouchableLinkMovementMethod(); + } - boolean mLastEventResult = false; - MotionEvent mLastEvent; + boolean lastEventResult = false; + MotionEvent lastEvent; - @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { - mLastEvent = event; - boolean result = super.onTouchEvent(widget, buffer, event); - if (event.getAction() == MotionEvent.ACTION_DOWN) { - // Unfortunately, LinkMovementMethod extends ScrollMovementMethod, and it always - // consume the down event. So here we use the selection instead as a hint of whether - // the down event landed on a link. - mLastEventResult = Selection.getSelectionStart(buffer) != -1; - } else { - mLastEventResult = result; - } - return result; - } + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + lastEvent = event; + boolean result = super.onTouchEvent(widget, buffer, event); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + // Unfortunately, LinkMovementMethod extends ScrollMovementMethod, and it always + // consume the down event. So here we use the selection instead as a hint of whether + // the down event landed on a link. + lastEventResult = Selection.getSelectionStart(buffer) != -1; + } else { + lastEventResult = result; + } + return result; + } - @Override - public MotionEvent getLastTouchEvent() { - return mLastEvent; - } + @Override + public MotionEvent getLastTouchEvent() { + return lastEvent; + } - @Override - public boolean isLastTouchEventHandled() { - return mLastEventResult; - } + @Override + public boolean isLastTouchEventHandled() { + return lastEventResult; } + } } diff --git a/navigationbar/res/drawable/setup_wizard_navbar_ic_back.xml b/library/platform/AndroidManifest.xml index 260e913..9aa24dc 100644 --- a/navigationbar/res/drawable/setup_wizard_navbar_ic_back.xml +++ b/library/platform/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + 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. @@ -14,15 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:autoMirrored="true" - android:width="@dimen/setup_wizard_navbar_ic_intrinsic_size" - android:height="@dimen/setup_wizard_navbar_ic_intrinsic_size" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="?attr/setup_wizard_navbar_text_color" - android:pathData="M15.4,7.4l-1.4,-1.4 -6,6 6,6 1.4,-1.4 -4.6,-4.6z" /> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.setupwizardlib"> -</vector> + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> + +</manifest> diff --git a/library/platform/res/values-v27/styles.xml b/library/platform/res/values-v27/styles.xml index 6e36919..70101c9 100644 --- a/library/platform/res/values-v27/styles.xml +++ b/library/platform/res/values-v27/styles.xml @@ -44,6 +44,7 @@ <item name="suwButtonAllCaps">true</item> <item name="suwButtonFontFamily">sans-serif</item> + <item name="suwButtonHighlightAlpha">0.24</item> <item name="suwCardBackground">@drawable/suw_card_bg</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> @@ -75,6 +76,7 @@ <item name="suwButtonAllCaps">true</item> <item name="suwButtonFontFamily">sans-serif</item> + <item name="suwButtonHighlightAlpha">0.24</item> <item name="suwCardBackground">@drawable/suw_card_bg</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> @@ -109,7 +111,8 @@ <item name="suwButtonAllCaps">true</item> <item name="suwButtonCornerRadius">@dimen/suw_glif_button_corner_radius</item> - <item name="suwButtonFontFamily">sans-serif</item> + <item name="suwButtonFontFamily">sans-serif-medium</item> + <item name="suwButtonHighlightAlpha">0.24</item> <item name="suwColorPrimary">?android:attr/colorPrimary</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> @@ -145,7 +148,8 @@ <item name="suwButtonAllCaps">true</item> <item name="suwButtonCornerRadius">@dimen/suw_glif_button_corner_radius</item> - <item name="suwButtonFontFamily">sans-serif</item> + <item name="suwButtonFontFamily">sans-serif-medium</item> + <item name="suwButtonHighlightAlpha">0.12</item> <item name="suwColorPrimary">?android:attr/colorPrimary</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> @@ -218,4 +222,11 @@ <item name="android:colorControlHighlight">@color/suw_flat_button_highlight</item> </style> + <!-- Ignore UnusedResources: used by clients --> + <style name="SuwGlifButton.Tertiary" + parent="SuwGlifButton.BaseTertiary" + tools:ignore="UnusedResources"> + <item name="android:fontFamily">sans-serif-medium</item> + </style> + </resources> diff --git a/library/platform/src/com/android/setupwizardlib/view/NavigationBarButton.java b/library/platform/src/com/android/setupwizardlib/view/NavigationBarButton.java index 45d3737..5d14aa7 100644 --- a/library/platform/src/com/android/setupwizardlib/view/NavigationBarButton.java +++ b/library/platform/src/com/android/setupwizardlib/view/NavigationBarButton.java @@ -22,11 +22,11 @@ import android.widget.Button; public class NavigationBarButton extends Button { - public NavigationBarButton(Context context) { - super(context); - } + public NavigationBarButton(Context context) { + super(context); + } - public NavigationBarButton(Context context, AttributeSet attrs) { - super(context, attrs); - } + public NavigationBarButton(Context context, AttributeSet attrs) { + super(context, attrs); + } } diff --git a/library/platform/src/com/android/setupwizardlib/view/RichTextView.java b/library/platform/src/com/android/setupwizardlib/view/RichTextView.java index fa68a68..7eb29c6 100644 --- a/library/platform/src/com/android/setupwizardlib/view/RichTextView.java +++ b/library/platform/src/com/android/setupwizardlib/view/RichTextView.java @@ -27,15 +27,14 @@ import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.TextView; - import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; import com.android.setupwizardlib.span.SpanHelper; import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod; /** - * An extension of TextView that automatically replaces the annotation tags as specified in - * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)} + * An extension of TextView that automatically replaces the annotation tags as specified in {@link + * SpanHelper#replaceSpan(android.text.Spannable, Object, Object)} * * <p>Note: The accessibility interaction for ClickableSpans (and therefore LinkSpans) are built * into platform in O, although the interaction paradigm is different. (See b/17726921). In this @@ -44,133 +43,133 @@ import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMove */ public class RichTextView extends TextView implements OnLinkClickListener { - /* static section */ - - private static final String TAG = "RichTextView"; - - private static final String ANNOTATION_LINK = "link"; - private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance"; - - /** - * Replace <annotation> tags in strings to become their respective types. Currently 2 - * types are supported: - * <ol> - * <li><annotation link="foobar"> will create a - * {@link com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key - * "foobar"</li> - * <li><annotation textAppearance="TextAppearance.FooBar"> will create a - * {@link android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar</li> - * </ol> - */ - public static CharSequence getRichText(Context context, CharSequence text) { - if (text instanceof Spanned) { - final SpannableString spannable = new SpannableString(text); - final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class); - for (Annotation span : spans) { - final String key = span.getKey(); - if (ANNOTATION_TEXT_APPEARANCE.equals(key)) { - String textAppearance = span.getValue(); - final int style = context.getResources() - .getIdentifier(textAppearance, "style", context.getPackageName()); - if (style == 0) { - Log.w(TAG, "Cannot find resource: " + style); - } - final TextAppearanceSpan textAppearanceSpan = - new TextAppearanceSpan(context, style); - SpanHelper.replaceSpan(spannable, span, textAppearanceSpan); - } else if (ANNOTATION_LINK.equals(key)) { - LinkSpan link = new LinkSpan(span.getValue()); - SpanHelper.replaceSpan(spannable, span, link); - } - } - return spannable; + /* static section */ + + private static final String TAG = "RichTextView"; + + private static final String ANNOTATION_LINK = "link"; + private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance"; + + /** + * Replace <annotation> tags in strings to become their respective types. Currently 2 types + * are supported: + * + * <ol> + * <li><annotation link="foobar"> will create a {@link + * com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key "foobar" + * <li><annotation textAppearance="TextAppearance.FooBar"> will create a {@link + * android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar + * </ol> + */ + public static CharSequence getRichText(Context context, CharSequence text) { + if (text instanceof Spanned) { + final SpannableString spannable = new SpannableString(text); + final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class); + for (Annotation span : spans) { + final String key = span.getKey(); + if (ANNOTATION_TEXT_APPEARANCE.equals(key)) { + String textAppearance = span.getValue(); + final int style = + context + .getResources() + .getIdentifier(textAppearance, "style", context.getPackageName()); + if (style == 0) { + Log.w(TAG, "Cannot find resource: " + style); + } + final TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(context, style); + SpanHelper.replaceSpan(spannable, span, textAppearanceSpan); + } else if (ANNOTATION_LINK.equals(key)) { + LinkSpan link = new LinkSpan(span.getValue()); + SpanHelper.replaceSpan(spannable, span, link); } - return text; - } - - /* non-static section */ - - private OnLinkClickListener mOnLinkClickListener; - - public RichTextView(Context context) { - super(context); - } - - public RichTextView(Context context, AttributeSet attrs) { - super(context, attrs); + } + return spannable; } - - @Override - public void setText(CharSequence text, BufferType type) { - text = getRichText(getContext(), text); - // Set text first before doing anything else because setMovementMethod internally calls - // setText. This in turn ends up calling this method with mText as the first parameter - super.setText(text, type); - boolean hasLinks = hasLinks(text); - - if (hasLinks) { - // When a TextView has a movement method, it will set the view to clickable. This makes - // View.onTouchEvent always return true and consumes the touch event, essentially - // nullifying any return values of MovementMethod.onTouchEvent. - // To still allow propagating touch events to the parent when this view doesn't have - // links, we only set the movement method here if the text contains links. - setMovementMethod(TouchableLinkMovementMethod.getInstance()); - } else { - setMovementMethod(null); - } - // ExploreByTouchHelper automatically enables focus for RichTextView - // even though it may not have any links. Causes problems during talkback - // as individual TextViews consume touch events and thereby reducing the focus window - // shown by Talkback. Disable focus if there are no links - setFocusable(hasLinks); - // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is - // focusable in touch mode, we may be focused when the screen is first shown, and starting - // a screen halfway scrolled down is confusing to the user. - setRevealOnFocusHint(false); - setFocusableInTouchMode(hasLinks); + return text; + } + + /* non-static section */ + + private OnLinkClickListener mOnLinkClickListener; + + public RichTextView(Context context) { + super(context); + } + + public RichTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setText(CharSequence text, BufferType type) { + text = getRichText(getContext(), text); + // Set text first before doing anything else because setMovementMethod internally calls + // setText. This in turn ends up calling this method with mText as the first parameter + super.setText(text, type); + boolean hasLinks = hasLinks(text); + + if (hasLinks) { + // When a TextView has a movement method, it will set the view to clickable. This makes + // View.onTouchEvent always return true and consumes the touch event, essentially + // nullifying any return values of MovementMethod.onTouchEvent. + // To still allow propagating touch events to the parent when this view doesn't have + // links, we only set the movement method here if the text contains links. + setMovementMethod(TouchableLinkMovementMethod.getInstance()); + } else { + setMovementMethod(null); } - - private boolean hasLinks(CharSequence text) { - if (text instanceof Spanned) { - final ClickableSpan[] spans = - ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); - return spans.length > 0; - } - return false; + // ExploreByTouchHelper automatically enables focus for RichTextView + // even though it may not have any links. Causes problems during talkback + // as individual TextViews consume touch events and thereby reducing the focus window + // shown by Talkback. Disable focus if there are no links + setFocusable(hasLinks); + // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is + // focusable in touch mode, we may be focused when the screen is first shown, and starting + // a screen halfway scrolled down is confusing to the user. + setRevealOnFocusHint(false); + setFocusableInTouchMode(hasLinks); + } + + private boolean hasLinks(CharSequence text) { + if (text instanceof Spanned) { + final ClickableSpan[] spans = + ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); + return spans.length > 0; } - - @Override - @SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called - public boolean onTouchEvent(MotionEvent event) { - // Since View#onTouchEvent always return true if the view is clickable (which is the case - // when a TextView has a movement method), override the implementation to allow the movement - // method, if it implements TouchableMovementMethod, to say that the touch is not handled, - // allowing the event to bubble up to the parent view. - boolean superResult = super.onTouchEvent(event); - MovementMethod movementMethod = getMovementMethod(); - if (movementMethod instanceof TouchableMovementMethod) { - TouchableMovementMethod touchableMovementMethod = - (TouchableMovementMethod) movementMethod; - if (touchableMovementMethod.getLastTouchEvent() == event) { - return touchableMovementMethod.isLastTouchEventHandled(); - } - } - return superResult; + return false; + } + + @Override + @SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called + public boolean onTouchEvent(MotionEvent event) { + // Since View#onTouchEvent always return true if the view is clickable (which is the case + // when a TextView has a movement method), override the implementation to allow the movement + // method, if it implements TouchableMovementMethod, to say that the touch is not handled, + // allowing the event to bubble up to the parent view. + boolean superResult = super.onTouchEvent(event); + MovementMethod movementMethod = getMovementMethod(); + if (movementMethod instanceof TouchableMovementMethod) { + TouchableMovementMethod touchableMovementMethod = (TouchableMovementMethod) movementMethod; + if (touchableMovementMethod.getLastTouchEvent() == event) { + return touchableMovementMethod.isLastTouchEventHandled(); + } } + return superResult; + } - public void setOnLinkClickListener(OnLinkClickListener listener) { - mOnLinkClickListener = listener; - } + public void setOnLinkClickListener(OnLinkClickListener listener) { + mOnLinkClickListener = listener; + } - public OnLinkClickListener getOnLinkClickListener() { - return mOnLinkClickListener; - } + public OnLinkClickListener getOnLinkClickListener() { + return mOnLinkClickListener; + } - @Override - public boolean onLinkClick(LinkSpan span) { - if (mOnLinkClickListener != null) { - return mOnLinkClickListener.onLinkClick(span); - } - return false; + @Override + public boolean onLinkClick(LinkSpan span) { + if (mOnLinkClickListener != null) { + return mOnLinkClickListener.onLinkClick(span); } + return false; + } } diff --git a/library/platform/test/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/library/platform/test/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java index 3d11e12..212b52c 100644 --- a/library/platform/test/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java +++ b/library/platform/test/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java @@ -25,5 +25,4 @@ import android.app.Activity; * * @see DrawingTestHelper */ -public class DrawingTestActivity extends Activity { -} +public class DrawingTestActivity extends Activity {} diff --git a/library/recyclerview/src/com/android/setupwizardlib/DividerItemDecoration.java b/library/recyclerview/src/com/android/setupwizardlib/DividerItemDecoration.java index 2db17f8..128ed6b 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/DividerItemDecoration.java +++ b/library/recyclerview/src/com/android/setupwizardlib/DividerItemDecoration.java @@ -21,223 +21,207 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.view.View; - import androidx.annotation.IntDef; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.RecyclerView; - +import android.view.View; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * An {@link androidx.recyclerview.widget.RecyclerView.ItemDecoration} for RecyclerView to draw - * dividers between items. This ItemDecoration will draw the drawable specified by - * {@link #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by - * default, and the behavior of whether the divider is shown can be customized by subclassing - * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. + * dividers between items. This ItemDecoration will draw the drawable specified by {@link + * #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by default, + * and the behavior of whether the divider is shown can be customized by subclassing {@link + * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. * * <p>Modified from v14 PreferenceFragment.DividerDecoration. */ public class DividerItemDecoration extends RecyclerView.ItemDecoration { - /* static section */ + /* static section */ + + /** + * An interface to be implemented by a {@link RecyclerView.ViewHolder} which controls whether + * dividers should be shown above and below that item. + */ + public interface DividedViewHolder { /** - * An interface to be implemented by a {@link RecyclerView.ViewHolder} which controls whether - * dividers should be shown above and below that item. + * Returns whether divider is allowed above this item. A divider will be shown only if both + * items immediately above and below it allows this divider. */ - public interface DividedViewHolder { - - /** - * Returns whether divider is allowed above this item. A divider will be shown only if both - * items immediately above and below it allows this divider. - */ - boolean isDividerAllowedAbove(); - - /** - * Returns whether divider is allowed below this item. A divider will be shown only if both - * items immediately above and below it allows this divider. - */ - boolean isDividerAllowedBelow(); - } - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - DIVIDER_CONDITION_EITHER, - DIVIDER_CONDITION_BOTH}) - public @interface DividerCondition {} - - public static final int DIVIDER_CONDITION_EITHER = 0; - public static final int DIVIDER_CONDITION_BOTH = 1; + boolean isDividerAllowedAbove(); /** - * @deprecated Use {@link #DividerItemDecoration(android.content.Context)} + * Returns whether divider is allowed below this item. A divider will be shown only if both + * items immediately above and below it allows this divider. */ - @Deprecated - public static DividerItemDecoration getDefault(Context context) { - return new DividerItemDecoration(context); - } - - /* non-static section */ - - private Drawable mDivider; - private int mDividerHeight; - private int mDividerIntrinsicHeight; - @DividerCondition - private int mDividerCondition; + boolean isDividerAllowedBelow(); + } - public DividerItemDecoration() { - } + @Retention(RetentionPolicy.SOURCE) + @IntDef({DIVIDER_CONDITION_EITHER, DIVIDER_CONDITION_BOTH}) + public @interface DividerCondition {} - public DividerItemDecoration(Context context) { - final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration); - final Drawable divider = a.getDrawable( - R.styleable.SuwDividerItemDecoration_android_listDivider); - final int dividerHeight = a.getDimensionPixelSize( - R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0); - @DividerCondition final int dividerCondition = a.getInt( - R.styleable.SuwDividerItemDecoration_suwDividerCondition, - DIVIDER_CONDITION_EITHER); - a.recycle(); - - setDivider(divider); - setDividerHeight(dividerHeight); - setDividerCondition(dividerCondition); - } + public static final int DIVIDER_CONDITION_EITHER = 0; + public static final int DIVIDER_CONDITION_BOTH = 1; - @Override - public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { - if (mDivider == null) { - return; - } - final int childCount = parent.getChildCount(); - final int width = parent.getWidth(); - final int dividerHeight = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight; - for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { - final View view = parent.getChildAt(childViewIndex); - if (shouldDrawDividerBelow(view, parent)) { - final int top = (int) ViewCompat.getY(view) + view.getHeight(); - mDivider.setBounds(0, top, width, top + dividerHeight); - mDivider.draw(c); - } - } - } + /** @deprecated Use {@link #DividerItemDecoration(android.content.Context)} */ + @Deprecated + public static DividerItemDecoration getDefault(Context context) { + return new DividerItemDecoration(context); + } - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, - RecyclerView.State state) { - if (shouldDrawDividerBelow(view, parent)) { - outRect.bottom = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight; - } - } + /* non-static section */ - private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { - final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); - final int index = holder.getLayoutPosition(); - final int lastItemIndex = parent.getAdapter().getItemCount() - 1; - if (isDividerAllowedBelow(holder)) { - if (mDividerCondition == DIVIDER_CONDITION_EITHER) { - // Draw the divider without consulting the next item if we only - // need permission for either above or below. - return true; - } - } else if (mDividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) { - // Don't draw if the current view holder doesn't allow drawing below - // and the current theme requires permission for both the item below and above. - // Also, if this is the last item, there is no item below to ask permission - // for whether to draw a divider above, so don't draw it. - return false; - } - // Require permission from index below to draw the divider. - if (index < lastItemIndex) { - final RecyclerView.ViewHolder nextHolder = - parent.findViewHolderForLayoutPosition(index + 1); - if (!isDividerAllowedAbove(nextHolder)) { - // Don't draw if the next view holder doesn't allow drawing above - return false; - } - } - return true; - } - - /** - * Whether a divider is allowed above the view holder. The allowed values will be combined - * according to {@link #getDividerCondition()}. The default implementation delegates to - * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows - * the divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can - * override this to give more information to decide whether a divider should be drawn. - * - * @return True if divider is allowed above this view holder. - */ - protected boolean isDividerAllowedAbove(RecyclerView.ViewHolder viewHolder) { - return !(viewHolder instanceof DividedViewHolder) - || ((DividedViewHolder) viewHolder).isDividerAllowedAbove(); - } + private Drawable divider; + private int dividerHeight; + private int dividerIntrinsicHeight; + @DividerCondition private int dividerCondition; - /** - * Whether a divider is allowed below the view holder. The allowed values will be combined - * according to {@link #getDividerCondition()}. The default implementation delegates to - * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows - * the divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can - * override this to give more information to decide whether a divider should be drawn. - * - * @return True if divider is allowed below this view holder. - */ - protected boolean isDividerAllowedBelow(RecyclerView.ViewHolder viewHolder) { - return !(viewHolder instanceof DividedViewHolder) - || ((DividedViewHolder) viewHolder).isDividerAllowedBelow(); - } + public DividerItemDecoration() {} - /** - * Sets the drawable to be used as the divider. - */ - public void setDivider(Drawable divider) { - if (divider != null) { - mDividerIntrinsicHeight = divider.getIntrinsicHeight(); - } else { - mDividerIntrinsicHeight = 0; - } - mDivider = divider; + public DividerItemDecoration(Context context) { + final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration); + final Drawable divider = + a.getDrawable(R.styleable.SuwDividerItemDecoration_android_listDivider); + final int dividerHeight = + a.getDimensionPixelSize(R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0); + @DividerCondition + final int dividerCondition = + a.getInt( + R.styleable.SuwDividerItemDecoration_suwDividerCondition, DIVIDER_CONDITION_EITHER); + a.recycle(); + + setDivider(divider); + setDividerHeight(dividerHeight); + setDividerCondition(dividerCondition); + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (divider == null) { + return; } - - /** - * Gets the drawable currently used as the divider. - */ - public Drawable getDivider() { - return mDivider; + final int childCount = parent.getChildCount(); + final int width = parent.getWidth(); + final int dividerHeight = this.dividerHeight != 0 ? this.dividerHeight : dividerIntrinsicHeight; + for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { + final View view = parent.getChildAt(childViewIndex); + if (shouldDrawDividerBelow(view, parent)) { + final int top = (int) ViewCompat.getY(view) + view.getHeight(); + divider.setBounds(0, top, width, top + dividerHeight); + divider.draw(c); + } } + } - /** - * Sets the divider height, in pixels. - */ - public void setDividerHeight(int dividerHeight) { - mDividerHeight = dividerHeight; + @Override + public void getItemOffsets( + Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + if (shouldDrawDividerBelow(view, parent)) { + outRect.bottom = dividerHeight != 0 ? dividerHeight : dividerIntrinsicHeight; } - - /** - * Gets the divider height, in pixels. - */ - public int getDividerHeight() { - return mDividerHeight; + } + + private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { + final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); + final int index = holder.getLayoutPosition(); + final int lastItemIndex = parent.getAdapter().getItemCount() - 1; + if (isDividerAllowedBelow(holder)) { + if (dividerCondition == DIVIDER_CONDITION_EITHER) { + // Draw the divider without consulting the next item if we only + // need permission for either above or below. + return true; + } + } else if (dividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) { + // Don't draw if the current view holder doesn't allow drawing below + // and the current theme requires permission for both the item below and above. + // Also, if this is the last item, there is no item below to ask permission + // for whether to draw a divider above, so don't draw it. + return false; } - - /** - * Sets whether the divider needs permission from both the item view holder below - * and above from where the divider would draw itself or just needs permission from - * one or the other before drawing itself. - */ - public void setDividerCondition(@DividerCondition int dividerCondition) { - mDividerCondition = dividerCondition; + // Require permission from index below to draw the divider. + if (index < lastItemIndex) { + final RecyclerView.ViewHolder nextHolder = parent.findViewHolderForLayoutPosition(index + 1); + if (!isDividerAllowedAbove(nextHolder)) { + // Don't draw if the next view holder doesn't allow drawing above + return false; + } } - - /** - * Gets whether the divider needs permission from both the item view holder below - * and above from where the divider would draw itself or just needs permission from - * one or the other before drawing itself. - */ - @DividerCondition - public int getDividerCondition() { - return mDividerCondition; + return true; + } + + /** + * Whether a divider is allowed above the view holder. The allowed values will be combined + * according to {@link #getDividerCondition()}. The default implementation delegates to {@link + * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows the + * divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can override + * this to give more information to decide whether a divider should be drawn. + * + * @return True if divider is allowed above this view holder. + */ + protected boolean isDividerAllowedAbove(RecyclerView.ViewHolder viewHolder) { + return !(viewHolder instanceof DividedViewHolder) + || ((DividedViewHolder) viewHolder).isDividerAllowedAbove(); + } + + /** + * Whether a divider is allowed below the view holder. The allowed values will be combined + * according to {@link #getDividerCondition()}. The default implementation delegates to {@link + * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows the + * divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can override + * this to give more information to decide whether a divider should be drawn. + * + * @return True if divider is allowed below this view holder. + */ + protected boolean isDividerAllowedBelow(RecyclerView.ViewHolder viewHolder) { + return !(viewHolder instanceof DividedViewHolder) + || ((DividedViewHolder) viewHolder).isDividerAllowedBelow(); + } + + /** Sets the drawable to be used as the divider. */ + public void setDivider(Drawable divider) { + if (divider != null) { + dividerIntrinsicHeight = divider.getIntrinsicHeight(); + } else { + dividerIntrinsicHeight = 0; } + this.divider = divider; + } + + /** Gets the drawable currently used as the divider. */ + public Drawable getDivider() { + return divider; + } + + /** Sets the divider height, in pixels. */ + public void setDividerHeight(int dividerHeight) { + this.dividerHeight = dividerHeight; + } + + /** Gets the divider height, in pixels. */ + public int getDividerHeight() { + return dividerHeight; + } + + /** + * Sets whether the divider needs permission from both the item view holder below and above from + * where the divider would draw itself or just needs permission from one or the other before + * drawing itself. + */ + public void setDividerCondition(@DividerCondition int dividerCondition) { + this.dividerCondition = dividerCondition; + } + + /** + * Gets whether the divider needs permission from both the item view holder below and above from + * where the divider would draw itself or just needs permission from one or the other before + * drawing itself. + */ + @DividerCondition + public int getDividerCondition() { + return dividerCondition; + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/GlifPreferenceLayout.java b/library/recyclerview/src/com/android/setupwizardlib/GlifPreferenceLayout.java index af1a739..795fdad 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/GlifPreferenceLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/GlifPreferenceLayout.java @@ -18,21 +18,20 @@ package com.android.setupwizardlib; import android.content.Context; import android.os.Bundle; +import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.template.RecyclerMixin; /** * A layout to be used with {@code PreferenceFragment} in v14 support library. This can be specified - * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in - * {@code app:preferenceTheme}. + * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in {@code + * app:preferenceTheme}. + * + * <p>Example: * - * <p />Example: * <pre>{@code * <style android:name="MyActivityTheme"> * <item android:name="preferenceTheme">@style/MyPreferenceTheme</item> @@ -47,10 +46,11 @@ import com.android.setupwizardlib.template.RecyclerMixin; * </style> * }</pre> * - * where {@code my_preference_layout} is a layout that contains - * {@link com.android.setupwizardlib.GlifPreferenceLayout}. + * where {@code my_preference_layout} is a layout that contains {@link + * com.android.setupwizardlib.GlifPreferenceLayout}. + * + * <p>Example: * - * <p />Example: * <pre>{@code * <com.android.setupwizardlib.GlifPreferenceLayout * xmlns:android="http://schemas.android.com/apk/res/android" @@ -59,60 +59,57 @@ import com.android.setupwizardlib.template.RecyclerMixin; * android:layout_height="match_parent" /> * }</pre> * - * <p />Fragments using this layout <em>must</em> delegate {@code onCreateRecyclerView} to the - * implementation in this class: - * {@link #onCreateRecyclerView(android.view.LayoutInflater, android.view.ViewGroup, - * android.os.Bundle)} + * <p>Fragments using this layout <em>must</em> delegate {@code onCreateRecyclerView} to the + * implementation in this class: {@link #onCreateRecyclerView(android.view.LayoutInflater, + * android.view.ViewGroup, android.os.Bundle)} */ public class GlifPreferenceLayout extends GlifRecyclerLayout { - public GlifPreferenceLayout(Context context) { - super(context); - } + public GlifPreferenceLayout(Context context) { + super(context); + } - public GlifPreferenceLayout(Context context, int template, int containerId) { - super(context, template, containerId); - } + public GlifPreferenceLayout(Context context, int template, int containerId) { + super(context, template, containerId); + } - public GlifPreferenceLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } + public GlifPreferenceLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } - public GlifPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public GlifPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_layout_content; - } - return super.findContainer(containerId); + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_layout_content; } + return super.findContainer(containerId); + } - /** - * This method must be called in {@code PreferenceFragment#onCreateRecyclerView}. - */ - public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, - Bundle savedInstanceState) { - return mRecyclerMixin.getRecyclerView(); - } + /** This method must be called in {@code PreferenceFragment#onCreateRecyclerView}. */ + public RecyclerView onCreateRecyclerView( + LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + return mRecyclerMixin.getRecyclerView(); + } - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_glif_preference_template; - } - return super.onInflateTemplate(inflater, template); + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_glif_preference_template; } + return super.onInflateTemplate(inflater, template); + } - @Override - protected void onTemplateInflated() { - // Inflate the recycler view here, so attributes on the decoration views can be applied - // immediately. - final LayoutInflater inflater = LayoutInflater.from(getContext()); - RecyclerView recyclerView = (RecyclerView) inflater.inflate( - R.layout.suw_glif_preference_recycler_view, this, false); - mRecyclerMixin = new RecyclerMixin(this, recyclerView); - } + @Override + protected void onTemplateInflated() { + // Inflate the recycler view here, so attributes on the decoration views can be applied + // immediately. + final LayoutInflater inflater = LayoutInflater.from(getContext()); + RecyclerView recyclerView = + (RecyclerView) inflater.inflate(R.layout.suw_glif_preference_recycler_view, this, false); + mRecyclerMixin = new RecyclerMixin(this, recyclerView); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java b/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java index 7e0b1b7..2595b79 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java @@ -20,15 +20,13 @@ import android.annotation.TargetApi; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Build.VERSION_CODES; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - import com.android.setupwizardlib.template.RecyclerMixin; import com.android.setupwizardlib.template.RecyclerViewScrollHandlingDelegate; import com.android.setupwizardlib.template.RequireScrollMixin; @@ -39,157 +37,137 @@ import com.android.setupwizardlib.template.RequireScrollMixin; */ public class GlifRecyclerLayout extends GlifLayout { - protected RecyclerMixin mRecyclerMixin; - - public GlifRecyclerLayout(Context context) { - this(context, 0, 0); - } - - public GlifRecyclerLayout(Context context, int template) { - this(context, template, 0); - } - - public GlifRecyclerLayout(Context context, int template, int containerId) { - super(context, template, containerId); - init(context, null, 0); - } - - public GlifRecyclerLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - @TargetApi(VERSION_CODES.HONEYCOMB) - public GlifRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - mRecyclerMixin.parseAttributes(attrs, defStyleAttr); - registerMixin(RecyclerMixin.class, mRecyclerMixin); - - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - requireScrollMixin.setScrollHandlingDelegate( - new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mRecyclerMixin.onLayout(); - } - - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_glif_recycler_template; - } - return super.onInflateTemplate(inflater, template); - } - - @Override - protected void onTemplateInflated() { - final View recyclerView = findViewById(R.id.suw_recycler_view); - if (recyclerView instanceof RecyclerView) { - mRecyclerMixin = new RecyclerMixin(this, (RecyclerView) recyclerView); - } else { - throw new IllegalStateException( - "GlifRecyclerLayout should use a template with recycler view"); - } - } - - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_recycler_view; - } - return super.findContainer(containerId); - } - - @Override - // Returning generic type is the common pattern used for findViewBy* methods - @SuppressWarnings("TypeParameterUnusedInFormals") - public <T extends View> T findManagedViewById(int id) { - final View header = mRecyclerMixin.getHeader(); - if (header != null) { - final T view = header.findViewById(id); - if (view != null) { - return view; - } - } - return super.findViewById(id); - } - - /** - * @see RecyclerMixin#setDividerItemDecoration(DividerItemDecoration) - */ - public void setDividerItemDecoration(DividerItemDecoration decoration) { - mRecyclerMixin.setDividerItemDecoration(decoration); - } - - /** - * @see RecyclerMixin#getRecyclerView() - */ - public RecyclerView getRecyclerView() { - return mRecyclerMixin.getRecyclerView(); - } - - /** - * @see RecyclerMixin#setAdapter(Adapter) - */ - public void setAdapter(Adapter<? extends ViewHolder> adapter) { - mRecyclerMixin.setAdapter(adapter); - } - - /** - * @see RecyclerMixin#getAdapter() - */ - public Adapter<? extends ViewHolder> getAdapter() { - return mRecyclerMixin.getAdapter(); - } - - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - mRecyclerMixin.setDividerInset(inset); - } - - /** - * @see RecyclerMixin#setDividerInset(int) - */ - public void setDividerInsets(int start, int end) { - mRecyclerMixin.setDividerInsets(start, end); - } - - /** - * @deprecated Use {@link #getDividerInsetStart()} instead. - */ - @Deprecated - public int getDividerInset() { - return mRecyclerMixin.getDividerInset(); - } - - /** - * @see RecyclerMixin#getDividerInsetStart() - */ - public int getDividerInsetStart() { - return mRecyclerMixin.getDividerInsetStart(); - } - - /** - * @see RecyclerMixin#getDividerInsetEnd() - */ - public int getDividerInsetEnd() { - return mRecyclerMixin.getDividerInsetEnd(); - } - - /** - * @see RecyclerMixin#getDivider() - */ - public Drawable getDivider() { - return mRecyclerMixin.getDivider(); - } + protected RecyclerMixin mRecyclerMixin; + + public GlifRecyclerLayout(Context context) { + this(context, 0, 0); + } + + public GlifRecyclerLayout(Context context, int template) { + this(context, template, 0); + } + + public GlifRecyclerLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, 0); + } + + public GlifRecyclerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public GlifRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + mRecyclerMixin.parseAttributes(attrs, defStyleAttr); + registerMixin(RecyclerMixin.class, mRecyclerMixin); + + final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); + requireScrollMixin.setScrollHandlingDelegate( + new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mRecyclerMixin.onLayout(); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_glif_recycler_template; + } + return super.onInflateTemplate(inflater, template); + } + + @Override + protected void onTemplateInflated() { + final View recyclerView = findViewById(R.id.suw_recycler_view); + if (recyclerView instanceof RecyclerView) { + mRecyclerMixin = new RecyclerMixin(this, (RecyclerView) recyclerView); + } else { + throw new IllegalStateException( + "GlifRecyclerLayout should use a template with recycler view"); + } + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_recycler_view; + } + return super.findContainer(containerId); + } + + @Override + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public <T extends View> T findManagedViewById(int id) { + final View header = mRecyclerMixin.getHeader(); + if (header != null) { + final T view = header.findViewById(id); + if (view != null) { + return view; + } + } + return super.findViewById(id); + } + + /** @see RecyclerMixin#setDividerItemDecoration(DividerItemDecoration) */ + public void setDividerItemDecoration(DividerItemDecoration decoration) { + mRecyclerMixin.setDividerItemDecoration(decoration); + } + + /** @see RecyclerMixin#getRecyclerView() */ + public RecyclerView getRecyclerView() { + return mRecyclerMixin.getRecyclerView(); + } + + /** @see RecyclerMixin#setAdapter(Adapter) */ + public void setAdapter(Adapter<? extends ViewHolder> adapter) { + mRecyclerMixin.setAdapter(adapter); + } + + /** @see RecyclerMixin#getAdapter() */ + public Adapter<? extends ViewHolder> getAdapter() { + return mRecyclerMixin.getAdapter(); + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + mRecyclerMixin.setDividerInset(inset); + } + + /** @see RecyclerMixin#setDividerInset(int) */ + public void setDividerInsets(int start, int end) { + mRecyclerMixin.setDividerInsets(start, end); + } + + /** @deprecated Use {@link #getDividerInsetStart()} instead. */ + @Deprecated + public int getDividerInset() { + return mRecyclerMixin.getDividerInset(); + } + + /** @see RecyclerMixin#getDividerInsetStart() */ + public int getDividerInsetStart() { + return mRecyclerMixin.getDividerInsetStart(); + } + + /** @see RecyclerMixin#getDividerInsetEnd() */ + public int getDividerInsetEnd() { + return mRecyclerMixin.getDividerInsetEnd(); + } + + /** @see RecyclerMixin#getDivider() */ + public Drawable getDivider() { + return mRecyclerMixin.getDivider(); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java index 670c309..e9aa329 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java @@ -18,21 +18,20 @@ package com.android.setupwizardlib; import android.content.Context; import android.os.Bundle; +import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.template.RecyclerMixin; /** * A layout to be used with {@code PreferenceFragment} in v14 support library. This can be specified - * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in - * {@code app:preferenceTheme}. + * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in {@code + * app:preferenceTheme}. + * + * <p>Example: * - * <p />Example: * <pre>{@code * <style android:name="MyActivityTheme"> * <item android:name="preferenceTheme">@style/MyPreferenceTheme</item> @@ -47,10 +46,11 @@ import com.android.setupwizardlib.template.RecyclerMixin; * </style> * }</pre> * - * where {@code my_preference_layout} is a layout that contains - * {@link com.android.setupwizardlib.SetupWizardPreferenceLayout}. + * where {@code my_preference_layout} is a layout that contains {@link + * com.android.setupwizardlib.SetupWizardPreferenceLayout}. + * + * <p>Example: * - * <p />Example: * <pre>{@code * <com.android.setupwizardlib.SetupWizardPreferenceLayout * xmlns:android="http://schemas.android.com/apk/res/android" @@ -59,58 +59,56 @@ import com.android.setupwizardlib.template.RecyclerMixin; * android:layout_height="match_parent" /> * }</pre> * - * <p />Fragments using this layout <em>must</em> delegate {@code onCreateRecyclerView} to the + * <p>Fragments using this layout <em>must</em> delegate {@code onCreateRecyclerView} to the * implementation in this class: {@link #onCreateRecyclerView} */ public class SetupWizardPreferenceLayout extends SetupWizardRecyclerLayout { - public SetupWizardPreferenceLayout(Context context) { - super(context); - } + public SetupWizardPreferenceLayout(Context context) { + super(context); + } - public SetupWizardPreferenceLayout(Context context, int template, int containerId) { - super(context, template, containerId); - } + public SetupWizardPreferenceLayout(Context context, int template, int containerId) { + super(context, template, containerId); + } - public SetupWizardPreferenceLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } + public SetupWizardPreferenceLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } - public SetupWizardPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public SetupWizardPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_layout_content; - } - return super.findContainer(containerId); + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_layout_content; } + return super.findContainer(containerId); + } - /** - * This method must be called in {@code PreferenceFragment#onCreateRecyclerView}. - */ - public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, - Bundle savedInstanceState) { - return mRecyclerMixin.getRecyclerView(); - } + /** This method must be called in {@code PreferenceFragment#onCreateRecyclerView}. */ + public RecyclerView onCreateRecyclerView( + LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + return mRecyclerMixin.getRecyclerView(); + } - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_preference_template; - } - return super.onInflateTemplate(inflater, template); + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_preference_template; } + return super.onInflateTemplate(inflater, template); + } - @Override - protected void onTemplateInflated() { - // Inflate the recycler view here, so attributes on the decoration views can be applied - // immediately. - final LayoutInflater inflater = LayoutInflater.from(getContext()); - RecyclerView recyclerView = (RecyclerView) inflater.inflate( - R.layout.suw_preference_recycler_view, this, false); - mRecyclerMixin = new RecyclerMixin(this, recyclerView); - } + @Override + protected void onTemplateInflated() { + // Inflate the recycler view here, so attributes on the decoration views can be applied + // immediately. + final LayoutInflater inflater = LayoutInflater.from(getContext()); + RecyclerView recyclerView = + (RecyclerView) inflater.inflate(R.layout.suw_preference_recycler_view, this, false); + mRecyclerMixin = new RecyclerMixin(this, recyclerView); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java index 5d3f1a5..ba0b598 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java @@ -18,178 +18,156 @@ package com.android.setupwizardlib; import android.content.Context; import android.graphics.drawable.Drawable; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - import com.android.setupwizardlib.template.RecyclerMixin; import com.android.setupwizardlib.template.RecyclerViewScrollHandlingDelegate; import com.android.setupwizardlib.template.RequireScrollMixin; /** - * A setup wizard layout for use with {@link androidx.recyclerview.widget.RecyclerView}. - * {@code android:entries} can also be used to specify an - * {@link com.android.setupwizardlib.items.ItemHierarchy} to be used with this layout in XML. + * A setup wizard layout for use with {@link androidx.recyclerview.widget.RecyclerView}. {@code + * android:entries} can also be used to specify an {@link + * com.android.setupwizardlib.items.ItemHierarchy} to be used with this layout in XML. * * @see SetupWizardListLayout */ public class SetupWizardRecyclerLayout extends SetupWizardLayout { - private static final String TAG = "RecyclerLayout"; - - protected RecyclerMixin mRecyclerMixin; - - public SetupWizardRecyclerLayout(Context context) { - this(context, 0, 0); - } - - public SetupWizardRecyclerLayout(Context context, int template, int containerId) { - super(context, template, containerId); - init(context, null, 0); - } - - public SetupWizardRecyclerLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - public SetupWizardRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - mRecyclerMixin.parseAttributes(attrs, defStyleAttr); - registerMixin(RecyclerMixin.class, mRecyclerMixin); - - - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - requireScrollMixin.setScrollHandlingDelegate( - new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mRecyclerMixin.onLayout(); - } - - /** - * @see RecyclerMixin#getAdapter() - */ - public Adapter<? extends ViewHolder> getAdapter() { - return mRecyclerMixin.getAdapter(); - } - - /** - * @see RecyclerMixin#setAdapter(Adapter) - */ - public void setAdapter(Adapter<? extends ViewHolder> adapter) { - mRecyclerMixin.setAdapter(adapter); - } - - /** - * @see RecyclerMixin#getRecyclerView() - */ - public RecyclerView getRecyclerView() { - return mRecyclerMixin.getRecyclerView(); - } - - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_recycler_view; - } - return super.findContainer(containerId); - } - - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_recycler_template; - } - return super.onInflateTemplate(inflater, template); - } - - @Override - protected void onTemplateInflated() { - final View recyclerView = findViewById(R.id.suw_recycler_view); - if (recyclerView instanceof RecyclerView) { - mRecyclerMixin = new RecyclerMixin(this, (RecyclerView) recyclerView); - } else { - throw new IllegalStateException( - "SetupWizardRecyclerLayout should use a template with recycler view"); - } - } - - @Override - // Returning generic type is the common pattern used for findViewBy* methods - @SuppressWarnings("TypeParameterUnusedInFormals") - public <T extends View> T findManagedViewById(int id) { - final View header = mRecyclerMixin.getHeader(); - if (header != null) { - final T view = header.findViewById(id); - if (view != null) { - return view; - } - } - return super.findViewById(id); - } - - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - mRecyclerMixin.setDividerInset(inset); - } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start 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}. - * @param end The number of pixels to inset on the "end" side of the list divider. - * - * @see RecyclerMixin#setDividerInsets(int, int) - */ - public void setDividerInsets(int start, int end) { - mRecyclerMixin.setDividerInsets(start, end); - } - - /** - * @deprecated Use {@link #getDividerInsetStart()} instead. - */ - @Deprecated - public int getDividerInset() { - return mRecyclerMixin.getDividerInset(); - } - - /** - * @see RecyclerMixin#getDividerInsetStart() - */ - public int getDividerInsetStart() { - return mRecyclerMixin.getDividerInsetStart(); - } - - /** - * @see RecyclerMixin#getDividerInsetEnd() - */ - public int getDividerInsetEnd() { - return mRecyclerMixin.getDividerInsetEnd(); - } - - /** - * @see RecyclerMixin#getDivider() - */ - public Drawable getDivider() { - return mRecyclerMixin.getDivider(); - } + protected RecyclerMixin mRecyclerMixin; + + public SetupWizardRecyclerLayout(Context context) { + this(context, 0, 0); + } + + public SetupWizardRecyclerLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, 0); + } + + public SetupWizardRecyclerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + public SetupWizardRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + mRecyclerMixin.parseAttributes(attrs, defStyleAttr); + registerMixin(RecyclerMixin.class, mRecyclerMixin); + + final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); + requireScrollMixin.setScrollHandlingDelegate( + new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mRecyclerMixin.onLayout(); + } + + /** @see RecyclerMixin#getAdapter() */ + public Adapter<? extends ViewHolder> getAdapter() { + return mRecyclerMixin.getAdapter(); + } + + /** @see RecyclerMixin#setAdapter(Adapter) */ + public void setAdapter(Adapter<? extends ViewHolder> adapter) { + mRecyclerMixin.setAdapter(adapter); + } + + /** @see RecyclerMixin#getRecyclerView() */ + public RecyclerView getRecyclerView() { + return mRecyclerMixin.getRecyclerView(); + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_recycler_view; + } + return super.findContainer(containerId); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_recycler_template; + } + return super.onInflateTemplate(inflater, template); + } + + @Override + protected void onTemplateInflated() { + final View recyclerView = findViewById(R.id.suw_recycler_view); + if (recyclerView instanceof RecyclerView) { + mRecyclerMixin = new RecyclerMixin(this, (RecyclerView) recyclerView); + } else { + throw new IllegalStateException( + "SetupWizardRecyclerLayout should use a template with recycler view"); + } + } + + @Override + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public <T extends View> T findManagedViewById(int id) { + final View header = mRecyclerMixin.getHeader(); + if (header != null) { + final T view = header.findViewById(id); + if (view != null) { + return view; + } + } + return super.findViewById(id); + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + mRecyclerMixin.setDividerInset(inset); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start 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}. + * @param end The number of pixels to inset on the "end" side of the list divider. + * @see RecyclerMixin#setDividerInsets(int, int) + */ + public void setDividerInsets(int start, int end) { + mRecyclerMixin.setDividerInsets(start, end); + } + + /** @deprecated Use {@link #getDividerInsetStart()} instead. */ + @Deprecated + public int getDividerInset() { + return mRecyclerMixin.getDividerInset(); + } + + /** @see RecyclerMixin#getDividerInsetStart() */ + public int getDividerInsetStart() { + return mRecyclerMixin.getDividerInsetStart(); + } + + /** @see RecyclerMixin#getDividerInsetEnd() */ + public int getDividerInsetEnd() { + return mRecyclerMixin.getDividerInsetEnd(); + } + + /** @see RecyclerMixin#getDivider() */ + public Drawable getDivider() { + return mRecyclerMixin.getDivider(); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/items/ItemViewHolder.java b/library/recyclerview/src/com/android/setupwizardlib/items/ItemViewHolder.java index aeaba68..419e2aa 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/items/ItemViewHolder.java +++ b/library/recyclerview/src/com/android/setupwizardlib/items/ItemViewHolder.java @@ -16,44 +16,42 @@ package com.android.setupwizardlib.items; -import android.view.View; - import androidx.recyclerview.widget.RecyclerView; - +import android.view.View; import com.android.setupwizardlib.DividerItemDecoration; class ItemViewHolder extends RecyclerView.ViewHolder - implements DividerItemDecoration.DividedViewHolder { - - private boolean mIsEnabled; - private IItem mItem; - - ItemViewHolder(View itemView) { - super(itemView); - } - - @Override - public boolean isDividerAllowedAbove() { - return mIsEnabled; - } - - @Override - public boolean isDividerAllowedBelow() { - return mIsEnabled; - } - - public void setEnabled(boolean isEnabled) { - mIsEnabled = isEnabled; - itemView.setClickable(isEnabled); - itemView.setEnabled(isEnabled); - itemView.setFocusable(isEnabled); - } - - public void setItem(IItem item) { - mItem = item; - } - - public IItem getItem() { - return mItem; - } + implements DividerItemDecoration.DividedViewHolder { + + private boolean isEnabled; + private IItem item; + + ItemViewHolder(View itemView) { + super(itemView); + } + + @Override + public boolean isDividerAllowedAbove() { + return isEnabled; + } + + @Override + public boolean isDividerAllowedBelow() { + return isEnabled; + } + + public void setEnabled(boolean isEnabled) { + this.isEnabled = isEnabled; + itemView.setClickable(isEnabled); + itemView.setEnabled(isEnabled); + itemView.setFocusable(isEnabled); + } + + public void setItem(IItem item) { + this.item = item; + } + + public IItem getItem() { + return item; + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java b/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java index 56c60e7..ee753b8 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java +++ b/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java @@ -20,14 +20,12 @@ import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.R; /** @@ -36,217 +34,214 @@ import com.android.setupwizardlib.R; * XML. */ public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder> - implements ItemHierarchy.Observer { + implements ItemHierarchy.Observer { - private static final String TAG = "RecyclerItemAdapter"; + private static final String TAG = "RecyclerItemAdapter"; - /** - * A view tag set by {@link View#setTag(Object)}. If set on the root view of a layout, it will - * not create the default background for the list item. This means the item will not have ripple - * touch feedback by default. - */ - public static final String TAG_NO_BACKGROUND = "noBackground"; + /** + * A view tag set by {@link View#setTag(Object)}. If set on the root view of a layout, it will not + * create the default background for the list item. This means the item will not have ripple touch + * feedback by default. + */ + public static final String TAG_NO_BACKGROUND = "noBackground"; - /** - * Listener for item selection in this adapter. - */ - public interface OnItemSelectedListener { - - /** - * Called when an item in this adapter is clicked. - * - * @param item The Item corresponding to the position being clicked. - */ - void onItemSelected(IItem item); - } - - private final ItemHierarchy mItemHierarchy; - private OnItemSelectedListener mListener; - - public RecyclerItemAdapter(ItemHierarchy hierarchy) { - mItemHierarchy = hierarchy; - mItemHierarchy.registerObserver(this); - } + /** Listener for item selection in this adapter. */ + public interface OnItemSelectedListener { /** - * Gets the item at the given position. + * Called when an item in this adapter is clicked. * - * @see ItemHierarchy#getItemAt(int) + * @param item The Item corresponding to the position being clicked. */ - public IItem getItem(int position) { - return mItemHierarchy.getItemAt(position); - } - - @Override - public long getItemId(int position) { - IItem mItem = getItem(position); - if (mItem instanceof AbstractItem) { - final int id = ((AbstractItem) mItem).getId(); - return id > 0 ? id : RecyclerView.NO_ID; - } else { - return RecyclerView.NO_ID; - } - } - - @Override - public int getItemCount() { - return mItemHierarchy.getCount(); - } - - @Override - public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - final View view = inflater.inflate(viewType, parent, false); - final ItemViewHolder viewHolder = new ItemViewHolder(view); - - final Object viewTag = view.getTag(); - if (!TAG_NO_BACKGROUND.equals(viewTag)) { - final TypedArray typedArray = parent.getContext() - .obtainStyledAttributes(R.styleable.SuwRecyclerItemAdapter); - Drawable selectableItemBackground = typedArray.getDrawable( - R.styleable.SuwRecyclerItemAdapter_android_selectableItemBackground); - if (selectableItemBackground == null) { - selectableItemBackground = typedArray.getDrawable( - R.styleable.SuwRecyclerItemAdapter_selectableItemBackground); - } - - Drawable background = view.getBackground(); - if (background == null) { - background = typedArray.getDrawable( - R.styleable.SuwRecyclerItemAdapter_android_colorBackground); - } - - if (selectableItemBackground == null || background == null) { - Log.e(TAG, "Cannot resolve required attributes." - + " selectableItemBackground=" + selectableItemBackground - + " background=" + background); - } else { - final Drawable[] layers = {background, selectableItemBackground}; - view.setBackgroundDrawable(new PatchedLayerDrawable(layers)); - } - - typedArray.recycle(); - } - - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - final IItem item = viewHolder.getItem(); - if (mListener != null && item != null && item.isEnabled()) { - mListener.onItemSelected(item); - } + void onItemSelected(IItem item); + } + + private final ItemHierarchy itemHierarchy; + private OnItemSelectedListener listener; + + public RecyclerItemAdapter(ItemHierarchy hierarchy) { + itemHierarchy = hierarchy; + itemHierarchy.registerObserver(this); + } + + /** + * Gets the item at the given position. + * + * @see ItemHierarchy#getItemAt(int) + */ + public IItem getItem(int position) { + return itemHierarchy.getItemAt(position); + } + + @Override + public long getItemId(int position) { + IItem mItem = getItem(position); + if (mItem instanceof AbstractItem) { + final int id = ((AbstractItem) mItem).getId(); + return id > 0 ? id : RecyclerView.NO_ID; + } else { + return RecyclerView.NO_ID; + } + } + + @Override + public int getItemCount() { + return itemHierarchy.getCount(); + } + + @Override + public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + final View view = inflater.inflate(viewType, parent, false); + final ItemViewHolder viewHolder = new ItemViewHolder(view); + + final Object viewTag = view.getTag(); + if (!TAG_NO_BACKGROUND.equals(viewTag)) { + final TypedArray typedArray = + parent.getContext().obtainStyledAttributes(R.styleable.SuwRecyclerItemAdapter); + Drawable selectableItemBackground = + typedArray.getDrawable( + R.styleable.SuwRecyclerItemAdapter_android_selectableItemBackground); + if (selectableItemBackground == null) { + selectableItemBackground = + typedArray.getDrawable(R.styleable.SuwRecyclerItemAdapter_selectableItemBackground); + } + + Drawable background = view.getBackground(); + if (background == null) { + background = + typedArray.getDrawable(R.styleable.SuwRecyclerItemAdapter_android_colorBackground); + } + + if (selectableItemBackground == null || background == null) { + Log.e( + TAG, + "Cannot resolve required attributes." + + " selectableItemBackground=" + + selectableItemBackground + + " background=" + + background); + } else { + final Drawable[] layers = {background, selectableItemBackground}; + view.setBackgroundDrawable(new PatchedLayerDrawable(layers)); + } + + typedArray.recycle(); + } + + view.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + final IItem item = viewHolder.getItem(); + if (listener != null && item != null && item.isEnabled()) { + listener.onItemSelected(item); } + } }); - return viewHolder; + return viewHolder; + } + + @Override + public void onBindViewHolder(ItemViewHolder holder, int position) { + final IItem item = getItem(position); + holder.setEnabled(item.isEnabled()); + holder.setItem(item); + item.onBindView(holder.itemView); + } + + @Override + public int getItemViewType(int position) { + // Use layout resource as item view type. RecyclerView item type does not have to be + // contiguous. + IItem item = getItem(position); + return item.getLayoutResource(); + } + + @Override + public void onChanged(ItemHierarchy hierarchy) { + notifyDataSetChanged(); + } + + @Override + public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + notifyItemRangeChanged(positionStart, itemCount); + } + + @Override + public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + notifyItemRangeInserted(positionStart, itemCount); + } + + @Override + public void onItemRangeMoved( + ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount) { + // There is no notifyItemRangeMoved + // https://code.google.com/p/android/issues/detail?id=125984 + if (itemCount == 1) { + notifyItemMoved(fromPosition, toPosition); + } else { + // If more than one, degenerate into the catch-all data set changed callback, since I'm + // not sure how recycler view handles multiple calls to notifyItemMoved (if the result + // is committed after every notification then naively calling + // notifyItemMoved(from + i, to + i) is wrong). + // Logging this in case this is a more common occurrence than expected. + Log.i(TAG, "onItemRangeMoved with more than one item"); + notifyDataSetChanged(); + } + } + + @Override + public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + notifyItemRangeRemoved(positionStart, itemCount); + } + + /** + * Find an item hierarchy within the root hierarchy. + * + * @see ItemHierarchy#findItemById(int) + */ + public ItemHierarchy findItemById(int id) { + return itemHierarchy.findItemById(id); + } + + /** Gets the root item hierarchy in this adapter. */ + public ItemHierarchy getRootItemHierarchy() { + return itemHierarchy; + } + + /** + * Sets the listener to listen for when user clicks on a item. + * + * @see OnItemSelectedListener + */ + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + this.listener = listener; + } + + /** + * Before Lollipop, LayerDrawable always return true in getPadding, even if the children layers do + * not have any padding. Patch the implementation so that getPadding returns false if the padding + * is empty. + * + * <p>When getPadding is true, the padding of the view will be replaced by the padding of the + * drawable when {@link View#setBackgroundDrawable(Drawable)} is called. This patched class makes + * sure layer drawables without padding does not clear out original padding on the view. + */ + @VisibleForTesting + static class PatchedLayerDrawable extends LayerDrawable { + + /** {@inheritDoc} */ + PatchedLayerDrawable(Drawable[] layers) { + super(layers); } @Override - public void onBindViewHolder(ItemViewHolder holder, int position) { - final IItem item = getItem(position); - holder.setEnabled(item.isEnabled()); - holder.setItem(item); - item.onBindView(holder.itemView); - } - - @Override - public int getItemViewType(int position) { - // Use layout resource as item view type. RecyclerView item type does not have to be - // contiguous. - IItem item = getItem(position); - return item.getLayoutResource(); - } - - @Override - public void onChanged(ItemHierarchy hierarchy) { - notifyDataSetChanged(); - } - - @Override - public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - notifyItemRangeChanged(positionStart, itemCount); - } - - @Override - public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - notifyItemRangeInserted(positionStart, itemCount); - } - - @Override - public void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition, - int itemCount) { - // There is no notifyItemRangeMoved - // https://code.google.com/p/android/issues/detail?id=125984 - if (itemCount == 1) { - notifyItemMoved(fromPosition, toPosition); - } else { - // If more than one, degenerate into the catch-all data set changed callback, since I'm - // not sure how recycler view handles multiple calls to notifyItemMoved (if the result - // is committed after every notification then naively calling - // notifyItemMoved(from + i, to + i) is wrong). - // Logging this in case this is a more common occurrence than expected. - Log.i(TAG, "onItemRangeMoved with more than one item"); - notifyDataSetChanged(); - } - } - - @Override - public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - notifyItemRangeRemoved(positionStart, itemCount); - } - - /** - * Find an item hierarchy within the root hierarchy. - * - * @see ItemHierarchy#findItemById(int) - */ - public ItemHierarchy findItemById(int id) { - return mItemHierarchy.findItemById(id); - } - - /** - * Gets the root item hierarchy in this adapter. - */ - public ItemHierarchy getRootItemHierarchy() { - return mItemHierarchy; - } - - /** - * Sets the listener to listen for when user clicks on a item. - * - * @see OnItemSelectedListener - */ - public void setOnItemSelectedListener(OnItemSelectedListener listener) { - mListener = listener; - } - - /** - * Before Lollipop, LayerDrawable always return true in getPadding, even if the children layers - * do not have any padding. Patch the implementation so that getPadding returns false if the - * padding is empty. - * - * When getPadding is true, the padding of the view will be replaced by the padding of the - * drawable when {@link View#setBackgroundDrawable(Drawable)} is called. This patched class - * makes sure layer drawables without padding does not clear out original padding on the view. - */ - @VisibleForTesting - static class PatchedLayerDrawable extends LayerDrawable { - - /** - * {@inheritDoc} - */ - PatchedLayerDrawable(Drawable[] layers) { - super(layers); - } - - @Override - public boolean getPadding(Rect padding) { - final boolean superHasPadding = super.getPadding(padding); - return superHasPadding - && !(padding.left == 0 - && padding.top == 0 - && padding.right == 0 - && padding.bottom == 0); - } + public boolean getPadding(Rect padding) { + final boolean superHasPadding = super.getPadding(padding); + return superHasPadding + && !(padding.left == 0 && padding.top == 0 && padding.right == 0 && padding.bottom == 0); } + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java index 32e7bd8..a6c6526 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java +++ b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java @@ -21,16 +21,14 @@ 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.View; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.ViewHolder; - +import android.util.AttributeSet; +import android.view.View; import com.android.setupwizardlib.DividerItemDecoration; import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; @@ -51,223 +49,208 @@ import com.android.setupwizardlib.view.HeaderRecyclerView.HeaderAdapter; */ public class RecyclerMixin implements Mixin { - private TemplateLayout mTemplateLayout; + private final TemplateLayout templateLayout; - @NonNull - private final RecyclerView mRecyclerView; + @NonNull private final RecyclerView recyclerView; - @Nullable - private View mHeader; + @Nullable private View header; - @NonNull - private DividerItemDecoration mDividerDecoration; + @NonNull private DividerItemDecoration dividerDecoration; - private Drawable mDefaultDivider; - private Drawable mDivider; + private Drawable defaultDivider; + private Drawable divider; - private int mDividerInsetStart; - private int mDividerInsetEnd; + private int dividerInsetStart; + private int dividerInsetEnd; - /** - * Creates the RecyclerMixin. Unlike typical mixins which are created in the constructor, this - * mixin should be called in {@link TemplateLayout#onTemplateInflated()}, which is called by - * the super constructor, because the recycler view and the header needs to be made available - * before other mixins from the super class. - * - * @param layout The layout this mixin belongs to. - */ - public RecyclerMixin(@NonNull TemplateLayout layout, @NonNull RecyclerView recyclerView) { - mTemplateLayout = layout; + /** + * Creates the RecyclerMixin. Unlike typical mixins which are created in the constructor, this + * mixin should be called in {@link TemplateLayout#onTemplateInflated()}, which is called by the + * super constructor, because the recycler view and the header needs to be made available before + * other mixins from the super class. + * + * @param layout The layout this mixin belongs to. + */ + public RecyclerMixin(@NonNull TemplateLayout layout, @NonNull RecyclerView recyclerView) { + templateLayout = layout; - mDividerDecoration = new DividerItemDecoration(mTemplateLayout.getContext()); + dividerDecoration = new DividerItemDecoration(templateLayout.getContext()); - // The recycler view needs to be available - mRecyclerView = recyclerView; - mRecyclerView.setLayoutManager(new LinearLayoutManager(mTemplateLayout.getContext())); + // The recycler view needs to be available + this.recyclerView = recyclerView; + this.recyclerView.setLayoutManager(new LinearLayoutManager(templateLayout.getContext())); - if (recyclerView instanceof HeaderRecyclerView) { - mHeader = ((HeaderRecyclerView) recyclerView).getHeader(); - } - - mRecyclerView.addItemDecoration(mDividerDecoration); + if (recyclerView instanceof HeaderRecyclerView) { + header = ((HeaderRecyclerView) recyclerView).getHeader(); } - /** - * Parse XML attributes and configures this mixin and the recycler view accordingly. This should - * be called from the constructor of the layout. - * - * @param attrs The {@link AttributeSet} as passed into the constructor. Can be null if the - * layout was not created from XML. - * @param defStyleAttr The default style attribute as passed into the layout constructor. Can be - * 0 if it is not needed. - */ - public void parseAttributes(@Nullable AttributeSet attrs, int defStyleAttr) { - final Context context = mTemplateLayout.getContext(); - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.SuwRecyclerMixin, defStyleAttr, 0); - - final int entries = a.getResourceId(R.styleable.SuwRecyclerMixin_android_entries, 0); - if (entries != 0) { - final ItemHierarchy inflated = new ItemInflater(context).inflate(entries); - final RecyclerItemAdapter adapter = new RecyclerItemAdapter(inflated); - adapter.setHasStableIds(a.getBoolean( - R.styleable.SuwRecyclerMixin_suwHasStableIds, false)); - setAdapter(adapter); - } - int dividerInset = - a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInset, -1); - if (dividerInset != -1) { - setDividerInset(dividerInset); - } else { - int dividerInsetStart = - a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetStart, 0); - int dividerInsetEnd = - a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetEnd, 0); - setDividerInsets(dividerInsetStart, dividerInsetEnd); - } - - a.recycle(); + this.recyclerView.addItemDecoration(dividerDecoration); + } + + /** + * Parse XML attributes and configures this mixin and the recycler view accordingly. This should + * be called from the constructor of the layout. + * + * @param attrs The {@link AttributeSet} as passed into the constructor. Can be null if the layout + * was not created from XML. + * @param defStyleAttr The default style attribute as passed into the layout constructor. Can be 0 + * if it is not needed. + */ + public void parseAttributes(@Nullable AttributeSet attrs, int defStyleAttr) { + final Context context = templateLayout.getContext(); + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwRecyclerMixin, defStyleAttr, 0); + + final int entries = a.getResourceId(R.styleable.SuwRecyclerMixin_android_entries, 0); + if (entries != 0) { + final ItemHierarchy inflated = new ItemInflater(context).inflate(entries); + final RecyclerItemAdapter adapter = new RecyclerItemAdapter(inflated); + adapter.setHasStableIds(a.getBoolean(R.styleable.SuwRecyclerMixin_suwHasStableIds, false)); + setAdapter(adapter); } - - /** - * @return The recycler view contained in the layout, as marked by - * {@code @id/suw_recycler_view}. This will return {@code null} if the recycler view - * doesn't exist in the layout. - */ - @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a recycler - // view, and call this after the template is inflated, - // this will not return null. - public RecyclerView getRecyclerView() { - return mRecyclerView; + int dividerInset = a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInset, -1); + if (dividerInset != -1) { + setDividerInset(dividerInset); + } else { + int dividerInsetStart = + a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetStart, 0); + int dividerInsetEnd = + a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetEnd, 0); + setDividerInsets(dividerInsetStart, dividerInsetEnd); } - /** - * Gets the header view of the recycler layout. This is useful for other mixins if they need to - * access views within the header, usually via {@link TemplateLayout#findManagedViewById(int)}. - */ - @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a header, - // this call will not return null. - public View getHeader() { - return mHeader; + a.recycle(); + } + + /** + * @return The recycler view contained in the layout, as marked by {@code @id/suw_recycler_view}. + * This will return {@code null} if the recycler view doesn't exist in the layout. + */ + @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a recycler + // view, and call this after the template is inflated, + // this will not return null. + public RecyclerView getRecyclerView() { + return recyclerView; + } + + /** + * Gets the header view of the recycler layout. This is useful for other mixins if they need to + * access views within the header, usually via {@link TemplateLayout#findManagedViewById(int)}. + */ + @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a header, + // this call will not return null. + public View getHeader() { + return header; + } + + /** + * Recycler mixin needs to update the dividers if the layout direction has changed. This method + * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template is + * called. + */ + public void onLayout() { + if (divider == null) { + // Update divider in case layout direction has just been resolved + updateDivider(); } - - /** - * Recycler mixin needs to update the dividers if the layout direction has changed. This method - * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template - * is called. - */ - public void onLayout() { - if (mDivider == null) { - // Update divider in case layout direction has just been resolved - updateDivider(); - } + } + + /** + * Gets the adapter of the recycler view in this layout. If the adapter includes a header, this + * method will unwrap it and return the underlying adapter. + * + * @return The adapter, or {@code null} if the recycler view has no adapter. + */ + public Adapter<? extends ViewHolder> getAdapter() { + @SuppressWarnings("unchecked") // RecyclerView.getAdapter returns raw type :( + final RecyclerView.Adapter<? extends ViewHolder> adapter = recyclerView.getAdapter(); + if (adapter instanceof HeaderAdapter) { + return ((HeaderAdapter<? extends ViewHolder>) adapter).getWrappedAdapter(); } - - /** - * Gets the adapter of the recycler view in this layout. If the adapter includes a header, - * this method will unwrap it and return the underlying adapter. - * - * @return The adapter, or {@code null} if the recycler view has no adapter. - */ - public Adapter<? extends ViewHolder> getAdapter() { - @SuppressWarnings("unchecked") // RecyclerView.getAdapter returns raw type :( - final RecyclerView.Adapter<? extends ViewHolder> adapter = mRecyclerView.getAdapter(); - if (adapter instanceof HeaderAdapter) { - return ((HeaderAdapter<? extends ViewHolder>) adapter).getWrappedAdapter(); - } - return adapter; + return adapter; + } + + /** Sets the adapter on the recycler view in this layout. */ + public void setAdapter(Adapter<? extends ViewHolder> adapter) { + recyclerView.setAdapter(adapter); + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + setDividerInsets(inset, 0); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start 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}. + * @param end The number of pixels to inset on the "end" side of the list divider. + */ + public void setDividerInsets(int start, int end) { + dividerInsetStart = start; + dividerInsetEnd = end; + updateDivider(); + } + + /** + * @return The number of pixels inset on the start side of the divider. + * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. + */ + @Deprecated + public int getDividerInset() { + return getDividerInsetStart(); + } + + /** @return The number of pixels inset on the start side of the divider. */ + public int getDividerInsetStart() { + return dividerInsetStart; + } + + /** @return The number of pixels inset on the end side of the divider. */ + public int getDividerInsetEnd() { + return dividerInsetEnd; + } + + private void updateDivider() { + boolean shouldUpdate = true; + if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + shouldUpdate = templateLayout.isLayoutDirectionResolved(); } - - /** - * Sets the adapter on the recycler view in this layout. - */ - public void setAdapter(Adapter<? extends ViewHolder> adapter) { - mRecyclerView.setAdapter(adapter); - } - - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - setDividerInsets(inset, 0); - } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start 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}. - * @param end The number of pixels to inset on the "end" side of the list divider. - */ - public void setDividerInsets(int start, int end) { - mDividerInsetStart = start; - mDividerInsetEnd = end; - updateDivider(); - } - - /** - * @return The number of pixels inset on the start side of the divider. - * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. - */ - @Deprecated - public int getDividerInset() { - return getDividerInsetStart(); - } - - /** - * @return The number of pixels inset on the start side of the divider. - */ - public int getDividerInsetStart() { - return mDividerInsetStart; - } - - /** - * @return The number of pixels inset on the end side of the divider. - */ - public int getDividerInsetEnd() { - return mDividerInsetEnd; - } - - private void updateDivider() { - boolean shouldUpdate = true; - if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - shouldUpdate = mTemplateLayout.isLayoutDirectionResolved(); - } - if (shouldUpdate) { - if (mDefaultDivider == null) { - mDefaultDivider = mDividerDecoration.getDivider(); - } - mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable( - mDefaultDivider, - mDividerInsetStart /* start */, - 0 /* top */, - mDividerInsetEnd /* end */, - 0 /* bottom */, - mTemplateLayout); - mDividerDecoration.setDivider(mDivider); - } - } - - /** - * @return The drawable used as the divider. - */ - public Drawable getDivider() { - return mDivider; - } - - /** - * Sets the divider item decoration directly. This is a low level method which should be used - * only if custom divider behavior is needed, for example if the divider should be shown / - * hidden in some specific cases for view holders that cannot implement - * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. - */ - public void setDividerItemDecoration(@NonNull DividerItemDecoration decoration) { - mRecyclerView.removeItemDecoration(mDividerDecoration); - mDividerDecoration = decoration; - mRecyclerView.addItemDecoration(mDividerDecoration); - updateDivider(); + if (shouldUpdate) { + if (defaultDivider == null) { + defaultDivider = dividerDecoration.getDivider(); + } + divider = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + defaultDivider, + dividerInsetStart /* start */, + 0 /* top */, + dividerInsetEnd /* end */, + 0 /* bottom */, + templateLayout); + dividerDecoration.setDivider(divider); } + } + + /** @return The drawable used as the divider. */ + public Drawable getDivider() { + return divider; + } + + /** + * Sets the divider item decoration directly. This is a low level method which should be used only + * if custom divider behavior is needed, for example if the divider should be shown / hidden in + * some specific cases for view holders that cannot implement {@link + * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. + */ + public void setDividerItemDecoration(@NonNull DividerItemDecoration decoration) { + recyclerView.removeItemDecoration(dividerDecoration); + dividerDecoration = decoration; + recyclerView.addItemDecoration(dividerDecoration); + updateDivider(); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java index bfe8df2..8838c44 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java +++ b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java @@ -16,12 +16,10 @@ package com.android.setupwizardlib.template; -import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; - +import android.util.Log; import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate; /** @@ -30,55 +28,53 @@ import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDele */ public class RecyclerViewScrollHandlingDelegate implements ScrollHandlingDelegate { - private static final String TAG = "RVRequireScrollMixin"; + private static final String TAG = "RVRequireScrollMixin"; - @Nullable - private final RecyclerView mRecyclerView; + @Nullable private final RecyclerView recyclerView; - @NonNull - private final RequireScrollMixin mRequireScrollMixin; + @NonNull private final RequireScrollMixin requireScrollMixin; - public RecyclerViewScrollHandlingDelegate( - @NonNull RequireScrollMixin requireScrollMixin, - @Nullable RecyclerView recyclerView) { - mRequireScrollMixin = requireScrollMixin; - mRecyclerView = recyclerView; - } + public RecyclerViewScrollHandlingDelegate( + @NonNull RequireScrollMixin requireScrollMixin, @Nullable RecyclerView recyclerView) { + this.requireScrollMixin = requireScrollMixin; + this.recyclerView = recyclerView; + } - private boolean canScrollDown() { - if (mRecyclerView != null) { - // Compatibility implementation of View#canScrollVertically - final int offset = mRecyclerView.computeVerticalScrollOffset(); - final int range = mRecyclerView.computeVerticalScrollRange() - - mRecyclerView.computeVerticalScrollExtent(); - return range != 0 && offset < range - 1; - } - return false; + private boolean canScrollDown() { + if (recyclerView != null) { + // Compatibility implementation of View#canScrollVertically + final int offset = recyclerView.computeVerticalScrollOffset(); + final int range = + recyclerView.computeVerticalScrollRange() - recyclerView.computeVerticalScrollExtent(); + return range != 0 && offset < range - 1; } + return false; + } - @Override - public void startListening() { - if (mRecyclerView != null) { - mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - mRequireScrollMixin.notifyScrollabilityChange(canScrollDown()); - } - }); - - if (canScrollDown()) { - mRequireScrollMixin.notifyScrollabilityChange(true); + @Override + public void startListening() { + if (this.recyclerView != null) { + this.recyclerView.addOnScrollListener( + new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + requireScrollMixin.notifyScrollabilityChange(canScrollDown()); } - } else { - Log.w(TAG, "Cannot require scroll. Recycler view is null."); - } + }); + + if (canScrollDown()) { + requireScrollMixin.notifyScrollabilityChange(true); + } + } else { + Log.w(TAG, "Cannot require scroll. Recycler view is null."); } + } - @Override - public void pageScrollDown() { - if (mRecyclerView != null) { - final int height = mRecyclerView.getHeight(); - mRecyclerView.smoothScrollBy(0, height); - } + @Override + public void pageScrollDown() { + if (recyclerView != null) { + final int height = recyclerView.getHeight(); + recyclerView.smoothScrollBy(0, height); } + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/view/HeaderRecyclerView.java b/library/recyclerview/src/com/android/setupwizardlib/view/HeaderRecyclerView.java index 0304b65..3808e11 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/view/HeaderRecyclerView.java +++ b/library/recyclerview/src/com/android/setupwizardlib/view/HeaderRecyclerView.java @@ -19,259 +19,257 @@ package com.android.setupwizardlib.view; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; +import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; - -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.DividerItemDecoration; import com.android.setupwizardlib.R; /** * A RecyclerView that can display a header item at the start of the list. The header can be set by - * {@code app:suwHeader} in XML. Note that the header will not be inflated until a layout manager - * is set. + * {@code app:suwHeader} in XML. Note that the header will not be inflated until a layout manager is + * set. */ public class HeaderRecyclerView extends RecyclerView { - private static class HeaderViewHolder extends ViewHolder - implements DividerItemDecoration.DividedViewHolder { - - HeaderViewHolder(View itemView) { - super(itemView); - } - - @Override - public boolean isDividerAllowedAbove() { - return false; - } + private static class HeaderViewHolder extends ViewHolder + implements DividerItemDecoration.DividedViewHolder { - @Override - public boolean isDividerAllowedBelow() { - return false; - } + HeaderViewHolder(View itemView) { + super(itemView); } - /** - * An adapter that can optionally add one header item to the RecyclerView. - * - * @param <CVH> Type of the content view holder. i.e. view holder type of the wrapped adapter. - */ - public static class HeaderAdapter<CVH extends ViewHolder> - extends RecyclerView.Adapter<ViewHolder> { - - private static final int HEADER_VIEW_TYPE = Integer.MAX_VALUE; + @Override + public boolean isDividerAllowedAbove() { + return false; + } - private RecyclerView.Adapter<CVH> mAdapter; - private View mHeader; + @Override + public boolean isDividerAllowedBelow() { + return false; + } + } - private final AdapterDataObserver mObserver = new AdapterDataObserver() { + /** + * An adapter that can optionally add one header item to the RecyclerView. + * + * @param <CVH> Type of the content view holder. i.e. view holder type of the wrapped adapter. + */ + public static class HeaderAdapter<CVH extends ViewHolder> + extends RecyclerView.Adapter<ViewHolder> { - @Override - public void onChanged() { - notifyDataSetChanged(); - } + private static final int HEADER_VIEW_TYPE = Integer.MAX_VALUE; - @Override - public void onItemRangeChanged(int positionStart, int itemCount) { - if (mHeader != null) { - positionStart++; - } - notifyItemRangeChanged(positionStart, itemCount); - } + private final RecyclerView.Adapter<CVH> adapter; + private View header; - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - if (mHeader != null) { - positionStart++; - } - notifyItemRangeInserted(positionStart, itemCount); - } + private final AdapterDataObserver observer = + new AdapterDataObserver() { - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - if (mHeader != null) { - fromPosition++; - toPosition++; - } - // Why is there no notifyItemRangeMoved? - for (int i = 0; i < itemCount; i++) { - notifyItemMoved(fromPosition + i, toPosition + i); - } - } + @Override + public void onChanged() { + notifyDataSetChanged(); + } - @Override - public void onItemRangeRemoved(int positionStart, int itemCount) { - if (mHeader != null) { - positionStart++; - } - notifyItemRangeRemoved(positionStart, itemCount); + @Override + public void onItemRangeChanged(int positionStart, int itemCount) { + if (header != null) { + positionStart++; } - }; - - public HeaderAdapter(RecyclerView.Adapter<CVH> adapter) { - mAdapter = adapter; - mAdapter.registerAdapterDataObserver(mObserver); - setHasStableIds(mAdapter.hasStableIds()); - } + notifyItemRangeChanged(positionStart, itemCount); + } - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - // Returning the same view (mHeader) results in crash ".. but view is not a real child." - // The framework creates more than one instance of header because of "disappear" - // animations applied on the header and this necessitates creation of another header - // view to use after the animation. We work around this restriction by returning an - // empty FrameLayout to which the header is attached using #onBindViewHolder method. - if (viewType == HEADER_VIEW_TYPE) { - FrameLayout frameLayout = new FrameLayout(parent.getContext()); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT); - frameLayout.setLayoutParams(params); - return new HeaderViewHolder(frameLayout); - } else { - return mAdapter.onCreateViewHolder(parent, viewType); + @Override + public void onItemRangeInserted(int positionStart, int itemCount) { + if (header != null) { + positionStart++; } - } - - @Override - @SuppressWarnings("unchecked") // Non-header position always return type CVH - public void onBindViewHolder(ViewHolder holder, int position) { - if (mHeader != null) { - position--; + notifyItemRangeInserted(positionStart, itemCount); + } + + @Override + public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + if (header != null) { + fromPosition++; + toPosition++; } - - if (holder instanceof HeaderViewHolder) { - if (mHeader == null) { - throw new IllegalStateException("HeaderViewHolder cannot find mHeader"); - } - if (mHeader.getParent() != null) { - ((ViewGroup) mHeader.getParent()).removeView(mHeader); - } - FrameLayout mHeaderParent = (FrameLayout) holder.itemView; - mHeaderParent.addView(mHeader); - } else { - mAdapter.onBindViewHolder((CVH) holder, position); + // Why is there no notifyItemRangeMoved? + for (int i = 0; i < itemCount; i++) { + notifyItemMoved(fromPosition + i, toPosition + i); } - } + } - @Override - public int getItemViewType(int position) { - if (mHeader != null) { - position--; + @Override + public void onItemRangeRemoved(int positionStart, int itemCount) { + if (header != null) { + positionStart++; } - if (position < 0) { - return HEADER_VIEW_TYPE; - } - return mAdapter.getItemViewType(position); - } - - @Override - public int getItemCount() { - int count = mAdapter.getItemCount(); - if (mHeader != null) { - count++; - } - return count; - } - - @Override - public long getItemId(int position) { - if (mHeader != null) { - position--; - } - if (position < 0) { - return Long.MAX_VALUE; - } - return mAdapter.getItemId(position); - } - - public void setHeader(View header) { - mHeader = header; - } - - public RecyclerView.Adapter<CVH> getWrappedAdapter() { - return mAdapter; - } - } - - private View mHeader; - private int mHeaderRes; + notifyItemRangeRemoved(positionStart, itemCount); + } + }; - public HeaderRecyclerView(Context context) { - super(context); - init(null, 0); + public HeaderAdapter(RecyclerView.Adapter<CVH> adapter) { + this.adapter = adapter; + this.adapter.registerAdapterDataObserver(observer); + setHasStableIds(this.adapter.hasStableIds()); } - public HeaderRecyclerView(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs, 0); + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + // Returning the same view (header) results in crash ".. but view is not a real child." + // The framework creates more than one instance of header because of "disappear" + // animations applied on the header and this necessitates creation of another header + // view to use after the animation. We work around this restriction by returning an + // empty FrameLayout to which the header is attached using #onBindViewHolder method. + if (viewType == HEADER_VIEW_TYPE) { + FrameLayout frameLayout = new FrameLayout(parent.getContext()); + FrameLayout.LayoutParams params = + new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + frameLayout.setLayoutParams(params); + return new HeaderViewHolder(frameLayout); + } else { + return adapter.onCreateViewHolder(parent, viewType); + } } - public HeaderRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs, defStyleAttr); + @Override + @SuppressWarnings("unchecked") // Non-header position always return type CVH + public void onBindViewHolder(ViewHolder holder, int position) { + if (header != null) { + position--; + } + + if (holder instanceof HeaderViewHolder) { + if (header == null) { + throw new IllegalStateException("HeaderViewHolder cannot find mHeader"); + } + if (header.getParent() != null) { + ((ViewGroup) header.getParent()).removeView(header); + } + FrameLayout mHeaderParent = (FrameLayout) holder.itemView; + mHeaderParent.addView(header); + } else { + adapter.onBindViewHolder((CVH) holder, position); + } } - private void init(AttributeSet attrs, int defStyleAttr) { - final TypedArray a = getContext().obtainStyledAttributes(attrs, - R.styleable.SuwHeaderRecyclerView, defStyleAttr, 0); - mHeaderRes = a.getResourceId(R.styleable.SuwHeaderRecyclerView_suwHeader, 0); - a.recycle(); + @Override + public int getItemViewType(int position) { + if (header != null) { + position--; + } + if (position < 0) { + return HEADER_VIEW_TYPE; + } + return adapter.getItemViewType(position); } @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 = mHeader != 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)); - } + public int getItemCount() { + int count = adapter.getItemCount(); + if (header != null) { + count++; + } + return count; } - /** - * Gets the header view of this RecyclerView, or {@code null} if there are no headers. - */ - public View getHeader() { - return mHeader; + @Override + public long getItemId(int position) { + if (header != null) { + position--; + } + if (position < 0) { + return Long.MAX_VALUE; + } + return adapter.getItemId(position); } - /** - * Set the view to use as the header of this recycler view. - * Note: This must be called before setAdapter. - */ public void setHeader(View header) { - mHeader = header; + this.header = header; } - @Override - public void setLayoutManager(LayoutManager layout) { - super.setLayoutManager(layout); - if (layout != null && mHeader == null && mHeaderRes != 0) { - // Inflating a child view requires the layout manager to be set. Check here to see if - // any header item is specified in XML and inflate them. - final LayoutInflater inflater = LayoutInflater.from(getContext()); - mHeader = inflater.inflate(mHeaderRes, this, false); - } + public RecyclerView.Adapter<CVH> getWrappedAdapter() { + return adapter; } - - @Override - @SuppressWarnings("rawtypes,unchecked") // RecyclerView.setAdapter uses raw type :( - public void setAdapter(Adapter adapter) { - if (mHeader != null && adapter != null) { - final HeaderAdapter headerAdapter = new HeaderAdapter(adapter); - headerAdapter.setHeader(mHeader); - adapter = headerAdapter; - } - super.setAdapter(adapter); + } + + private View header; + private int headerRes; + + public HeaderRecyclerView(Context context) { + super(context); + init(null, 0); + } + + public HeaderRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + public HeaderRecyclerView(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.SuwHeaderRecyclerView, defStyleAttr, 0); + headerRes = a.getResourceId(R.styleable.SuwHeaderRecyclerView_suwHeader, 0); + a.recycle(); + } + + @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 = header != 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)); + } + } + + /** Gets the header view of this RecyclerView, or {@code null} if there are no headers. */ + public View getHeader() { + return header; + } + + /** + * Set the view to use as the header of this recycler view. Note: This must be called before + * setAdapter. + */ + public void setHeader(View header) { + this.header = header; + } + + @Override + public void setLayoutManager(LayoutManager layout) { + super.setLayoutManager(layout); + if (layout != null && header == null && headerRes != 0) { + // Inflating a child view requires the layout manager to be set. Check here to see if + // any header item is specified in XML and inflate them. + final LayoutInflater inflater = LayoutInflater.from(getContext()); + header = inflater.inflate(headerRes, this, false); + } + } + + @Override + @SuppressWarnings("rawtypes,unchecked") // RecyclerView.setAdapter uses raw type :( + public void setAdapter(Adapter adapter) { + if (header != null && adapter != null) { + final HeaderAdapter headerAdapter = new HeaderAdapter(adapter); + headerAdapter.setHeader(header); + adapter = headerAdapter; } + super.setAdapter(adapter); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/view/StickyHeaderRecyclerView.java b/library/recyclerview/src/com/android/setupwizardlib/view/StickyHeaderRecyclerView.java index d51ea56..a5fa69c 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/view/StickyHeaderRecyclerView.java +++ b/library/recyclerview/src/com/android/setupwizardlib/view/StickyHeaderRecyclerView.java @@ -32,112 +32,114 @@ import android.view.WindowInsets; * to be drawn when the sticky element hits the top of the view. * * <p>There are a few things to note: + * * <ol> * <li>The view does not work well with padding. b/16190933 * <li>If fitsSystemWindows is true, then this will offset the sticking position by the height of - * the system decorations at the top of the screen. + * the system decorations at the top of the screen. * </ol> */ public class StickyHeaderRecyclerView extends HeaderRecyclerView { - private View mSticky; - private int mStatusBarInset = 0; - private RectF mStickyRect = new RectF(); + private View sticky; + private int statusBarInset = 0; + private final RectF stickyRect = new RectF(); - public StickyHeaderRecyclerView(Context context) { - super(context); - } + public StickyHeaderRecyclerView(Context context) { + super(context); + } - public StickyHeaderRecyclerView(Context context, AttributeSet attrs) { - super(context, attrs); - } + public StickyHeaderRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + } - public StickyHeaderRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public StickyHeaderRecyclerView(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(); - } - if (mSticky != null) { - final View headerView = getHeader(); - if (headerView != null && headerView.getHeight() == 0) { - headerView.layout(0, -headerView.getMeasuredHeight(), - headerView.getMeasuredWidth(), 0); - } - } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (sticky == null) { + updateStickyView(); + } + if (sticky != null) { + final View headerView = getHeader(); + if (headerView != null && headerView.getHeight() == 0) { + headerView.layout(0, -headerView.getMeasuredHeight(), headerView.getMeasuredWidth(), 0); + } } + } - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - super.onMeasure(widthSpec, heightSpec); - if (mSticky != null) { - measureChild(getHeader(), widthSpec, heightSpec); - } + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + super.onMeasure(widthSpec, heightSpec); + if (sticky != null) { + measureChild(getHeader(), widthSpec, heightSpec); } + } - /** - * Call this method when the "sticky" view has changed, so this view can update its internal - * states as well. - */ - public void updateStickyView() { - final View header = getHeader(); - if (header != null) { - mSticky = header.findViewWithTag("sticky"); - } + /** + * Call this method when the "sticky" view has changed, so this view can update its internal + * states as well. + */ + public void updateStickyView() { + final View header = getHeader(); + if (header != null) { + sticky = header.findViewWithTag("sticky"); } + } - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - if (mSticky != null) { - final View headerView = getHeader(); - final int saveCount = canvas.save(); - // The view to draw when sticking to the top - final View drawTarget = headerView != null ? headerView : mSticky; - // The offset to draw the view at when sticky - final int drawOffset = headerView != 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()) { - // RecyclerView 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 + public void draw(Canvas canvas) { + super.draw(canvas); + if (sticky != null) { + final View headerView = getHeader(); + final int saveCount = canvas.save(); + // The view to draw when sticking to the top + final View drawTarget = headerView != null ? headerView : sticky; + // The offset to draw the view at when sticky + final int drawOffset = headerView != null ? sticky.getTop() : 0; + // Position of the draw target, relative to the outside of the scrollView + final int drawTop = drawTarget.getTop(); + if (drawTop + drawOffset < statusBarInset || !drawTarget.isShown()) { + // RecyclerView does not translate the canvas, so we can simply draw at the top + stickyRect.set( + 0, + -drawOffset + statusBarInset, + drawTarget.getWidth(), + drawTarget.getHeight() - drawOffset + statusBarInset); + canvas.translate(0, stickyRect.top); + canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight()); + drawTarget.draw(canvas); + } else { + stickyRect.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 + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if (getFitsSystemWindows()) { + statusBarInset = insets.getSystemWindowInsetTop(); + insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + 0, /* top */ + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); } + return insets; + } - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (mStickyRect.contains(ev.getX(), ev.getY())) { - ev.offsetLocation(-mStickyRect.left, -mStickyRect.top); - return getHeader().dispatchTouchEvent(ev); - } else { - return super.dispatchTouchEvent(ev); - } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (stickyRect.contains(ev.getX(), ev.getY())) { + ev.offsetLocation(-stickyRect.left, -stickyRect.top); + return getHeader().dispatchTouchEvent(ev); + } else { + return super.dispatchTouchEvent(ev); } + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/items/RecyclerItemAdapterTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/items/RecyclerItemAdapterTest.java index 6f42e84..bed736e 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/items/RecyclerItemAdapterTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/items/RecyclerItemAdapterTest.java @@ -32,16 +32,13 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RectShape; +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; +import android.widget.FrameLayout; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.widget.FrameLayout; - -import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; - import com.android.setupwizardlib.items.RecyclerItemAdapter.PatchedLayerDrawable; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,111 +47,110 @@ import org.junit.runner.RunWith; @SmallTest public class RecyclerItemAdapterTest { - private Item[] mItems = new Item[5]; - private ItemGroup mItemGroup = new ItemGroup(); - - @Before - public void setUp() throws Exception { - for (int i = 0; i < 5; i++) { - Item item = new Item(); - item.setTitle("TestTitle" + i); - item.setId(i); - // Layout resource: 0 -> 1, 1 -> 11, 2 -> 21, 3 -> 1, 4 -> 11. - // (Resource IDs cannot be 0) - item.setLayoutResource((i % 3) * 10 + 1); - mItems[i] = item; - mItemGroup.addChild(item); - } - } - - @Test - public void testAdapter() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - assertEquals("Adapter should have 5 items", 5, adapter.getItemCount()); - assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); - assertEquals("ID should be same as position", 2, adapter.getItemId(2)); - - // ViewType is same as layout resource for RecyclerItemAdapter - assertEquals("Second item should have view type 21", 21, adapter.getItemViewType(2)); - } - - @Test - public void testGetRootItemHierarchy() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - ItemHierarchy root = adapter.getRootItemHierarchy(); - assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); - } - - @Test - public void testPatchedLayerDrawableNoPadding() { - ShapeDrawable child = new ShapeDrawable(new RectShape()); - child.setPadding(0, 0, 0, 0); - PatchedLayerDrawable drawable = new PatchedLayerDrawable(new Drawable[] { child }); - - Rect padding = new Rect(); - assertFalse("Patched layer drawable should not have padding", drawable.getPadding(padding)); - assertEquals(new Rect(0, 0, 0, 0), padding); - } - - @Test - public void testPatchedLayerDrawableWithPadding() { - ShapeDrawable child = new ShapeDrawable(new RectShape()); - child.setPadding(10, 10, 10, 10); - PatchedLayerDrawable drawable = new PatchedLayerDrawable(new Drawable[] { child }); - - Rect padding = new Rect(); - assertTrue("Patched layer drawable should have padding", drawable.getPadding(padding)); - assertEquals(new Rect(10, 10, 10, 10), padding); - } - - @Test - public void testAdapterNotifications() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - final AdapterDataObserver observer = mock(AdapterDataObserver.class); - adapter.registerAdapterDataObserver(observer); - - mItems[0].setTitle("Child 1"); - verify(observer).onItemRangeChanged(eq(0), eq(1), anyObject()); - - mItemGroup.removeChild(mItems[1]); - verify(observer).onItemRangeRemoved(eq(1), eq(1)); - - mItemGroup.addChild(mItems[1]); - verify(observer).onItemRangeInserted(eq(4), eq(1)); - } - - @Test - public void testCreateViewHolder() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); - - final ItemViewHolder viewHolder = - adapter.onCreateViewHolder(parent, R.layout.test_list_item); - assertNotNull("Background should be set", viewHolder.itemView.getBackground()); - assertEquals("foobar", viewHolder.itemView.getTag()); - } - - @Test - public void testCreateViewHolderNoBackground() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); - - final ItemViewHolder viewHolder = - adapter.onCreateViewHolder(parent, R.layout.test_list_item_no_background); - assertNull("Background should be null", viewHolder.itemView.getBackground()); - } - - @Test - public void testCreateViewHolderWithExistingBackground() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); - - final ItemViewHolder viewHolder = - adapter.onCreateViewHolder(parent, R.layout.test_existing_background); - Drawable background = viewHolder.itemView.getBackground(); - assertTrue(background instanceof PatchedLayerDrawable); - - PatchedLayerDrawable layerDrawable = (PatchedLayerDrawable) background; - assertTrue(layerDrawable.getDrawable(0) instanceof GradientDrawable); + private Item[] mItems = new Item[5]; + private ItemGroup mItemGroup = new ItemGroup(); + + @Before + public void setUp() throws Exception { + for (int i = 0; i < 5; i++) { + Item item = new Item(); + item.setTitle("TestTitle" + i); + item.setId(i); + // Layout resource: 0 -> 1, 1 -> 11, 2 -> 21, 3 -> 1, 4 -> 11. + // (Resource IDs cannot be 0) + item.setLayoutResource((i % 3) * 10 + 1); + mItems[i] = item; + mItemGroup.addChild(item); } + } + + @Test + public void testAdapter() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + assertEquals("Adapter should have 5 items", 5, adapter.getItemCount()); + assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); + assertEquals("ID should be same as position", 2, adapter.getItemId(2)); + + // ViewType is same as layout resource for RecyclerItemAdapter + assertEquals("Second item should have view type 21", 21, adapter.getItemViewType(2)); + } + + @Test + public void testGetRootItemHierarchy() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + ItemHierarchy root = adapter.getRootItemHierarchy(); + assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); + } + + @Test + public void testPatchedLayerDrawableNoPadding() { + ShapeDrawable child = new ShapeDrawable(new RectShape()); + child.setPadding(0, 0, 0, 0); + PatchedLayerDrawable drawable = new PatchedLayerDrawable(new Drawable[] {child}); + + Rect padding = new Rect(); + assertFalse("Patched layer drawable should not have padding", drawable.getPadding(padding)); + assertEquals(new Rect(0, 0, 0, 0), padding); + } + + @Test + public void testPatchedLayerDrawableWithPadding() { + ShapeDrawable child = new ShapeDrawable(new RectShape()); + child.setPadding(10, 10, 10, 10); + PatchedLayerDrawable drawable = new PatchedLayerDrawable(new Drawable[] {child}); + + Rect padding = new Rect(); + assertTrue("Patched layer drawable should have padding", drawable.getPadding(padding)); + assertEquals(new Rect(10, 10, 10, 10), padding); + } + + @Test + public void testAdapterNotifications() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + final AdapterDataObserver observer = mock(AdapterDataObserver.class); + adapter.registerAdapterDataObserver(observer); + + mItems[0].setTitle("Child 1"); + verify(observer).onItemRangeChanged(eq(0), eq(1), anyObject()); + + mItemGroup.removeChild(mItems[1]); + verify(observer).onItemRangeRemoved(eq(1), eq(1)); + + mItemGroup.addChild(mItems[1]); + verify(observer).onItemRangeInserted(eq(4), eq(1)); + } + + @Test + public void testCreateViewHolder() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); + + final ItemViewHolder viewHolder = adapter.onCreateViewHolder(parent, R.layout.test_list_item); + assertNotNull("Background should be set", viewHolder.itemView.getBackground()); + assertEquals("foobar", viewHolder.itemView.getTag()); + } + + @Test + public void testCreateViewHolderNoBackground() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); + + final ItemViewHolder viewHolder = + adapter.onCreateViewHolder(parent, R.layout.test_list_item_no_background); + assertNull("Background should be null", viewHolder.itemView.getBackground()); + } + + @Test + public void testCreateViewHolderWithExistingBackground() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); + + final ItemViewHolder viewHolder = + adapter.onCreateViewHolder(parent, R.layout.test_existing_background); + Drawable background = viewHolder.itemView.getBackground(); + assertTrue(background instanceof PatchedLayerDrawable); + + PatchedLayerDrawable layerDrawable = (PatchedLayerDrawable) background; + assertTrue(layerDrawable.getDrawable(0) instanceof GradientDrawable); + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/template/RecyclerMixinTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/template/RecyclerMixinTest.java index ece4bf9..f295b91 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/template/RecyclerMixinTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/template/RecyclerMixinTest.java @@ -30,17 +30,14 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import android.view.View; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,120 +48,119 @@ import org.mockito.MockitoAnnotations; @SmallTest public class RecyclerMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; + private Context mContext; + private TemplateLayout mTemplateLayout; - private RecyclerView mRecyclerView; + private RecyclerView mRecyclerView; - @Mock - private Adapter mAdapter; + @Mock private Adapter mAdapter; - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); - mRecyclerView = mock(RecyclerView.class, delegatesTo(new RecyclerView(mContext))); + mRecyclerView = mock(RecyclerView.class, delegatesTo(new RecyclerView(mContext))); - doReturn(true).when(mTemplateLayout).isLayoutDirectionResolved(); - } + doReturn(true).when(mTemplateLayout).isLayoutDirectionResolved(); + } - @Test - public void testGetRecyclerView() { - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - assertSame(mRecyclerView, mixin.getRecyclerView()); - } + @Test + public void testGetRecyclerView() { + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + assertSame(mRecyclerView, mixin.getRecyclerView()); + } - @Test - public void testGetAdapter() { - mRecyclerView.setAdapter(mAdapter); + @Test + public void testGetAdapter() { + mRecyclerView.setAdapter(mAdapter); - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - assertSame(mAdapter, mixin.getAdapter()); - } + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + assertSame(mAdapter, mixin.getAdapter()); + } - @Test - public void testSetAdapter() { - assertNull(mRecyclerView.getAdapter()); + @Test + public void testSetAdapter() { + assertNull(mRecyclerView.getAdapter()); - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setAdapter(mAdapter); + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setAdapter(mAdapter); - assertSame(mAdapter, mRecyclerView.getAdapter()); - } + assertSame(mAdapter, mRecyclerView.getAdapter()); + } - @Test - public void testDividerLegacyInset() { - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setDividerInset(123); + @Test + public void testDividerLegacyInset() { + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setDividerInset(123); - assertEquals(123, mixin.getDividerInset()); + assertEquals(123, mixin.getDividerInset()); - final Drawable divider = mixin.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mixin.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(123, 0, 0, 0), rect); - } + assertEquals(new Rect(123, 0, 0, 0), rect); + } - @Test - public void testDividerInsets() { - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setDividerInsets(123, 456); + @Test + public void testDividerInsets() { + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setDividerInsets(123, 456); - assertEquals(123, mixin.getDividerInsetStart()); - assertEquals(456, mixin.getDividerInsetEnd()); + assertEquals(123, mixin.getDividerInsetStart()); + assertEquals(456, mixin.getDividerInsetEnd()); - final Drawable divider = mixin.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mixin.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(123, 0, 456, 0), rect); - } + assertEquals(new Rect(123, 0, 456, 0), rect); + } - @Test - public void testDividerInsetLegacyRtl() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); + @Test + public void testDividerInsetLegacyRtl() { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setDividerInset(123); + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setDividerInset(123); - assertEquals(123, mixin.getDividerInset()); + assertEquals(123, mixin.getDividerInset()); - final Drawable divider = mixin.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mixin.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(0, 0, 123, 0), rect); - } - // else the test passes + assertEquals(new Rect(0, 0, 123, 0), rect); } + // else the test passes + } - @Test - public void testDividerInsetsRtl() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); + @Test + public void testDividerInsetsRtl() { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setDividerInsets(123, 456); + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setDividerInsets(123, 456); - assertEquals(123, mixin.getDividerInsetStart()); - assertEquals(456, mixin.getDividerInsetEnd()); + assertEquals(123, mixin.getDividerInsetStart()); + assertEquals(456, mixin.getDividerInsetEnd()); - final Drawable divider = mixin.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mixin.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(456, 0, 123, 0), rect); - } - // else the test passes + assertEquals(new Rect(456, 0, 123, 0), rect); } + // else the test passes + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java index 9cf33b9..a3a0cfe 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java @@ -28,17 +28,14 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; -import android.view.ViewGroup; - -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.DividerItemDecoration; - import org.junit.Test; import org.junit.runner.RunWith; @@ -46,176 +43,179 @@ import org.junit.runner.RunWith; @SmallTest public class DividerItemDecorationTest { - @Test - public void testDivider() { - final DividerItemDecoration decoration = new DividerItemDecoration(); - Drawable divider = new ColorDrawable(); - decoration.setDivider(divider); - assertSame("Divider should be same as set", divider, decoration.getDivider()); - } + @Test + public void testDivider() { + final DividerItemDecoration decoration = new DividerItemDecoration(); + Drawable divider = new ColorDrawable(); + decoration.setDivider(divider); + assertSame("Divider should be same as set", divider, decoration.getDivider()); + } + + @Test + public void testDividerHeight() { + final DividerItemDecoration decoration = new DividerItemDecoration(); + decoration.setDividerHeight(123); + assertEquals("Divider height should be 123", 123, decoration.getDividerHeight()); + } + + @Test + public void testShouldDrawDividerBelowWithEitherCondition() { + // Set up the item decoration, with 1px red divider line + final DividerItemDecoration decoration = new DividerItemDecoration(); + Drawable divider = new ColorDrawable(Color.RED); + decoration.setDivider(divider); + decoration.setDividerHeight(1); + + Bitmap bitmap = drawDecoration(decoration, true, true); + + // Draw the expected result on a bitmap + Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); + Canvas expectedCanvas = new Canvas(expectedBitmap); + Paint paint = new Paint(); + paint.setColor(Color.RED); + expectedCanvas.drawRect(0, 5, 20, 6, paint); + expectedCanvas.drawRect(0, 10, 20, 11, paint); + expectedCanvas.drawRect(0, 15, 20, 16, paint); + // Compare the two bitmaps + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, false, true); + // should still be the same. + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, true, false); + // last item should not have a divider below it now + paint.setColor(Color.TRANSPARENT); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + expectedCanvas.drawRect(0, 15, 20, 16, paint); + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, false, false); + // everything should be transparent now + expectedCanvas.drawRect(0, 5, 20, 6, paint); + expectedCanvas.drawRect(0, 10, 20, 11, paint); + assertBitmapEquals(expectedBitmap, bitmap); + } + + @Test + public void testShouldDrawDividerBelowWithBothCondition() { + // Set up the item decoration, with 1px green divider line + final DividerItemDecoration decoration = new DividerItemDecoration(); + Drawable divider = new ColorDrawable(Color.GREEN); + decoration.setDivider(divider); + decoration.setDividerHeight(1); + decoration.setDividerCondition(DividerItemDecoration.DIVIDER_CONDITION_BOTH); + + Bitmap bitmap = drawDecoration(decoration, true, true); + Paint paint = new Paint(); + paint.setColor(Color.GREEN); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); + Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); + Canvas expectedCanvas = new Canvas(expectedBitmap); + expectedCanvas.drawRect(0, 5, 20, 6, paint); + expectedCanvas.drawRect(0, 10, 20, 11, paint); + expectedCanvas.drawRect(0, 15, 20, 16, paint); + // Should have all the dividers + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, false, true); + paint.setColor(Color.TRANSPARENT); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + expectedCanvas.drawRect(0, 5, 20, 6, paint); + expectedCanvas.drawRect(0, 10, 20, 11, paint); + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, true, false); + // nothing should be drawn now. + expectedCanvas.drawRect(0, 15, 20, 16, paint); + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, false, false); + assertBitmapEquals(expectedBitmap, bitmap); + } + + private Bitmap drawDecoration( + DividerItemDecoration decoration, + final boolean allowDividerAbove, + final boolean allowDividerBelow) { + // Set up the canvas to be drawn + Bitmap bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); + Canvas canvas = new Canvas(bitmap); + + final Context context = InstrumentationRegistry.getContext(); + // Set up recycler view with vertical linear layout manager + RecyclerView testRecyclerView = new RecyclerView(context); + testRecyclerView.setLayoutManager(new LinearLayoutManager(context)); + + // Set up adapter with 3 items, each 5px tall + testRecyclerView.setAdapter( + new RecyclerView.Adapter() { + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + final View itemView = new View(context); + itemView.setMinimumWidth(20); + itemView.setMinimumHeight(5); + return ViewHolder.createInstance(itemView, allowDividerAbove, allowDividerBelow); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {} + + @Override + public int getItemCount() { + return 3; + } + }); - @Test - public void testDividerHeight() { - final DividerItemDecoration decoration = new DividerItemDecoration(); - decoration.setDividerHeight(123); - assertEquals("Divider height should be 123", 123, decoration.getDividerHeight()); + testRecyclerView.layout(0, 0, 20, 20); + decoration.onDraw(canvas, testRecyclerView, null); + return bitmap; + } + + private void assertBitmapEquals(Bitmap expected, Bitmap actual) { + assertEquals("Width should be the same", expected.getWidth(), actual.getWidth()); + assertEquals("Height should be the same", expected.getHeight(), actual.getHeight()); + for (int x = 0; x < expected.getWidth(); x++) { + for (int y = 0; y < expected.getHeight(); y++) { + assertEquals( + "Pixel at (" + x + ", " + y + ") should be the same", + expected.getPixel(x, y), + actual.getPixel(x, y)); + } } + } - @Test - public void testShouldDrawDividerBelowWithEitherCondition() { - // Set up the item decoration, with 1px red divider line - final DividerItemDecoration decoration = new DividerItemDecoration(); - Drawable divider = new ColorDrawable(Color.RED); - decoration.setDivider(divider); - decoration.setDividerHeight(1); - - Bitmap bitmap = drawDecoration(decoration, true, true); - - // Draw the expected result on a bitmap - Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); - Canvas expectedCanvas = new Canvas(expectedBitmap); - Paint paint = new Paint(); - paint.setColor(Color.RED); - expectedCanvas.drawRect(0, 5, 20, 6, paint); - expectedCanvas.drawRect(0, 10, 20, 11, paint); - expectedCanvas.drawRect(0, 15, 20, 16, paint); - // Compare the two bitmaps - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, false, true); - // should still be the same. - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, true, false); - // last item should not have a divider below it now - paint.setColor(Color.TRANSPARENT); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - expectedCanvas.drawRect(0, 15, 20, 16, paint); - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, false, false); - // everything should be transparent now - expectedCanvas.drawRect(0, 5, 20, 6, paint); - expectedCanvas.drawRect(0, 10, 20, 11, paint); - assertBitmapEquals(expectedBitmap, bitmap); + private static class ViewHolder extends RecyclerView.ViewHolder + implements DividerItemDecoration.DividedViewHolder { - } + private boolean mAllowDividerAbove; + private boolean mAllowDividerBelow; - @Test - public void testShouldDrawDividerBelowWithBothCondition() { - // Set up the item decoration, with 1px green divider line - final DividerItemDecoration decoration = new DividerItemDecoration(); - Drawable divider = new ColorDrawable(Color.GREEN); - decoration.setDivider(divider); - decoration.setDividerHeight(1); - decoration.setDividerCondition(DividerItemDecoration.DIVIDER_CONDITION_BOTH); - - Bitmap bitmap = drawDecoration(decoration, true, true); - Paint paint = new Paint(); - paint.setColor(Color.GREEN); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); - Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); - Canvas expectedCanvas = new Canvas(expectedBitmap); - expectedCanvas.drawRect(0, 5, 20, 6, paint); - expectedCanvas.drawRect(0, 10, 20, 11, paint); - expectedCanvas.drawRect(0, 15, 20, 16, paint); - // Should have all the dividers - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, false, true); - paint.setColor(Color.TRANSPARENT); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - expectedCanvas.drawRect(0, 5, 20, 6, paint); - expectedCanvas.drawRect(0, 10, 20, 11, paint); - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, true, false); - // nothing should be drawn now. - expectedCanvas.drawRect(0, 15, 20, 16, paint); - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, false, false); - assertBitmapEquals(expectedBitmap, bitmap); + public static ViewHolder createInstance( + View itemView, boolean allowDividerAbove, boolean allowDividerBelow) { + return new ViewHolder(itemView, allowDividerAbove, allowDividerBelow); } - private Bitmap drawDecoration(DividerItemDecoration decoration, final boolean allowDividerAbove, - final boolean allowDividerBelow) { - // Set up the canvas to be drawn - Bitmap bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); - Canvas canvas = new Canvas(bitmap); - - final Context context = InstrumentationRegistry.getContext(); - // Set up recycler view with vertical linear layout manager - RecyclerView testRecyclerView = new RecyclerView(context); - testRecyclerView.setLayoutManager(new LinearLayoutManager(context)); - - // Set up adapter with 3 items, each 5px tall - testRecyclerView.setAdapter(new RecyclerView.Adapter() { - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { - final View itemView = new View(context); - itemView.setMinimumWidth(20); - itemView.setMinimumHeight(5); - return ViewHolder.createInstance(itemView, allowDividerAbove, allowDividerBelow); - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { - } - - @Override - public int getItemCount() { - return 3; - } - }); - - testRecyclerView.layout(0, 0, 20, 20); - decoration.onDraw(canvas, testRecyclerView, null); - return bitmap; + private ViewHolder(View itemView, boolean allowDividerAbove, boolean allowDividerBelow) { + super(itemView); + mAllowDividerAbove = allowDividerAbove; + mAllowDividerBelow = allowDividerBelow; } - private void assertBitmapEquals(Bitmap expected, Bitmap actual) { - assertEquals("Width should be the same", expected.getWidth(), actual.getWidth()); - assertEquals("Height should be the same", expected.getHeight(), actual.getHeight()); - for (int x = 0; x < expected.getWidth(); x++) { - for (int y = 0; y < expected.getHeight(); y++) { - assertEquals("Pixel at (" + x + ", " + y + ") should be the same", - expected.getPixel(x, y), actual.getPixel(x, y)); - } - } + @Override + public boolean isDividerAllowedAbove() { + return mAllowDividerAbove; } - private static class ViewHolder extends RecyclerView.ViewHolder - implements DividerItemDecoration.DividedViewHolder { - - private boolean mAllowDividerAbove; - private boolean mAllowDividerBelow; - - public static ViewHolder createInstance(View itemView, boolean allowDividerAbove, - boolean allowDividerBelow) { - return new ViewHolder(itemView, allowDividerAbove, allowDividerBelow); - } - - private ViewHolder(View itemView, boolean allowDividerAbove, boolean allowDividerBelow) { - super(itemView); - mAllowDividerAbove = allowDividerAbove; - mAllowDividerBelow = allowDividerBelow; - } - - @Override - public boolean isDividerAllowedAbove() { - return mAllowDividerAbove; - } - - @Override - public boolean isDividerAllowedBelow() { - return mAllowDividerBelow; - } + @Override + public boolean isDividerAllowedBelow() { + return mAllowDividerBelow; } + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java index 4d2876d..d55ba23 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java @@ -24,18 +24,15 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.widget.RecyclerView; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.GlifPreferenceLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,62 +41,63 @@ import org.junit.runner.RunWith; @SmallTest public class GlifPreferenceLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); - } - - @Test - public void testDefaultTemplate() { - GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - } - - @Test - public void testGetRecyclerView() { - GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + } + + @Test + public void testDefaultTemplate() { + GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + } + + @Test + public void testGetRecyclerView() { + GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + } + + @Test + public void testOnCreateRecyclerView() { + GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + final RecyclerView recyclerView = + layout.onCreateRecyclerView( + LayoutInflater.from(mContext), layout, null /* savedInstanceState */); + assertNotNull("RecyclerView created should not be null", recyclerView); + } + + @Test + public void testDividerInset() { + GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertPreferenceTemplateInflated(layout); - @Test - public void testOnCreateRecyclerView() { - GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - final RecyclerView recyclerView = layout.onCreateRecyclerView(LayoutInflater.from(mContext), - layout, null /* savedInstanceState */); - assertNotNull("RecyclerView created should not be null", recyclerView); - } - - @Test - public void testDividerInset() { - GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertPreferenceTemplateInflated(layout); + layout.addView( + layout.onCreateRecyclerView( + LayoutInflater.from(mContext), layout, null /* savedInstanceState */)); - layout.addView(layout.onCreateRecyclerView(LayoutInflater.from(mContext), layout, - null /* savedInstanceState */)); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } + private void assertPreferenceTemplateInflated(GlifPreferenceLayout layout) { + View contentContainer = layout.findViewById(R.id.suw_layout_content); + assertTrue( + "@id/suw_layout_content should be a ViewGroup", contentContainer instanceof ViewGroup); - private void assertPreferenceTemplateInflated(GlifPreferenceLayout layout) { - View contentContainer = layout.findViewById(R.id.suw_layout_content); - assertTrue("@id/suw_layout_content should be a ViewGroup", - contentContainer instanceof ViewGroup); - - assertNotNull("Header text view should not be null", - layout.findManagedViewById(R.id.suw_layout_title)); - assertNotNull("Icon view should not be null", - layout.findManagedViewById(R.id.suw_layout_icon)); - } + assertNotNull( + "Header text view should not be null", layout.findManagedViewById(R.id.suw_layout_title)); + assertNotNull("Icon view should not be null", layout.findManagedViewById(R.id.suw_layout_icon)); + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java index a68faf0..5db7db5 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java @@ -26,20 +26,17 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.GlifRecyclerLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,130 +45,127 @@ import org.junit.runner.RunWith; @SmallTest public class GlifRecyclerLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); - } - - @Test - public void testDefaultTemplate() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - GlifRecyclerLayout layout = (GlifRecyclerLayout) - inflater.inflate(R.layout.test_glif_recycler_layout, null); - assertRecyclerTemplateInflated(layout); - } - - @Test - public void testGetRecyclerView() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); - } - - @Test - public void testAdapter() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - - final RecyclerView.Adapter adapter = createTestAdapter(1); - layout.setAdapter(adapter); - - final RecyclerView.Adapter gotAdapter = layout.getAdapter(); - // Note: The wrapped adapter should be returned, not the HeaderAdapter. - assertSame("Adapter got from GlifRecyclerLayout should be same as set", - adapter, gotAdapter); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + } + + @Test + public void testDefaultTemplate() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + GlifRecyclerLayout layout = + (GlifRecyclerLayout) inflater.inflate(R.layout.test_glif_recycler_layout, null); + assertRecyclerTemplateInflated(layout); + } + + @Test + public void testGetRecyclerView() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + } + + @Test + public void testAdapter() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + + final RecyclerView.Adapter adapter = createTestAdapter(1); + layout.setAdapter(adapter); + + final RecyclerView.Adapter gotAdapter = layout.getAdapter(); + // Note: The wrapped adapter should be returned, not the HeaderAdapter. + assertSame("Adapter got from GlifRecyclerLayout should be same as set", adapter, gotAdapter); + } + + @Test + public void testLayout() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + + layout.setAdapter(createTestAdapter(3)); + + layout.measure( + MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY)); + layout.layout(0, 0, 500, 500); + // Test that the layout code doesn't crash. + } + + @Test + public void testDividerInsetLegacy() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertRecyclerTemplateInflated(layout); - @Test - public void testLayout() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - layout.setAdapter(createTestAdapter(3)); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - layout.measure( - MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY)); - layout.layout(0, 0, 500, 500); - // Test that the layout code doesn't crash. + @Test + public void testDividerInsets() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } - - @Test - public void testDividerInsetLegacy() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertRecyclerTemplateInflated(layout); - - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testDividerInsets() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertRecyclerTemplateInflated(layout); - - layout.setDividerInsets(10, 15); - assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); - assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testTemplateWithNoRecyclerView() { - try { - new GlifRecyclerLayout(mContext, R.layout.suw_glif_template); - fail("Creating GlifRecyclerLayout with no recycler view should throw exception"); - } catch (Exception e) { - // pass - } - } - - private void assertRecyclerTemplateInflated(GlifRecyclerLayout layout) { - View recyclerView = layout.findViewById(R.id.suw_recycler_view); - assertTrue("@id/suw_recycler_view should be a RecyclerView", - recyclerView instanceof RecyclerView); - - assertNotNull("Header text view should not be null", - layout.findManagedViewById(R.id.suw_layout_title)); - assertNotNull("Icon view should not be null", - layout.findManagedViewById(R.id.suw_layout_icon)); - } - - private Adapter createTestAdapter(final int itemCount) { - return new RecyclerView.Adapter() { - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int position) { - return new RecyclerView.ViewHolder(new View(parent.getContext())) {}; - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { - } - - @Override - public int getItemCount() { - return itemCount; - } - }; + assertRecyclerTemplateInflated(layout); + + layout.setDividerInsets(10, 15); + assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); + assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); + + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } + + @Test + public void testTemplateWithNoRecyclerView() { + try { + new GlifRecyclerLayout(mContext, R.layout.suw_glif_template); + fail("Creating GlifRecyclerLayout with no recycler view should throw exception"); + } catch (Exception e) { + // pass } + } + + private void assertRecyclerTemplateInflated(GlifRecyclerLayout layout) { + View recyclerView = layout.findViewById(R.id.suw_recycler_view); + assertTrue( + "@id/suw_recycler_view should be a RecyclerView", recyclerView instanceof RecyclerView); + + assertNotNull( + "Header text view should not be null", layout.findManagedViewById(R.id.suw_layout_title)); + assertNotNull("Icon view should not be null", layout.findManagedViewById(R.id.suw_layout_icon)); + } + + private Adapter createTestAdapter(final int itemCount) { + return new RecyclerView.Adapter() { + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int position) { + return new RecyclerView.ViewHolder(new View(parent.getContext())) {}; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {} + + @Override + public int getItemCount() { + return itemCount; + } + }; + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/HeaderRecyclerViewTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/HeaderRecyclerViewTest.java index 9af68a7..3d0f9da 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/HeaderRecyclerViewTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/HeaderRecyclerViewTest.java @@ -19,160 +19,143 @@ package com.android.setupwizardlib.test; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; -import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.view.HeaderRecyclerView.HeaderAdapter; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * Test for {@link com.android.setupwizardlib.view.HeaderRecyclerView} - */ +/** Test for {@link com.android.setupwizardlib.view.HeaderRecyclerView} */ @RunWith(AndroidJUnit4.class) @SmallTest public class HeaderRecyclerViewTest { - private TestAdapter mWrappedAdapter; - private HeaderAdapter mHeaderAdapter; - - @Mock - private RecyclerView.AdapterDataObserver mObserver; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mWrappedAdapter = new TestAdapter(); - - mHeaderAdapter = new HeaderAdapter(mWrappedAdapter); - mHeaderAdapter.registerAdapterDataObserver(mObserver); - } - - /** - * Test that notifyDataSetChanged gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyChanged() { - mWrappedAdapter.notifyDataSetChanged(); - - verify(mObserver).onChanged(); + private TestAdapter mWrappedAdapter; + private HeaderAdapter mHeaderAdapter; + + @Mock private RecyclerView.AdapterDataObserver mObserver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mWrappedAdapter = new TestAdapter(); + + mHeaderAdapter = new HeaderAdapter(mWrappedAdapter); + mHeaderAdapter.registerAdapterDataObserver(mObserver); + } + + /** Test that notifyDataSetChanged gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyChanged() { + mWrappedAdapter.notifyDataSetChanged(); + + verify(mObserver).onChanged(); + } + + /** Test that notifyItemChanged gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyItemChangedNoHeader() { + mWrappedAdapter.notifyItemChanged(12); + + verify(mObserver).onItemRangeChanged(eq(12), eq(1), eq(null)); + } + + /** + * Test that notifyItemChanged gets propagated by HeaderRecyclerView's adapter and adds 1 to the + * position for the extra header items. + */ + @Test + public void testNotifyItemChangedWithHeader() { + mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); + mWrappedAdapter.notifyItemChanged(12); + + verify(mObserver).onItemRangeChanged(eq(13), eq(1), eq(null)); + } + + /** Test that notifyItemInserted gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyItemInsertedNoHeader() { + mWrappedAdapter.notifyItemInserted(12); + + verify(mObserver).onItemRangeInserted(eq(12), eq(1)); + } + + /** + * Test that notifyItemInserted gets propagated by HeaderRecyclerView's adapter and adds 1 to the + * position for the extra header item. + */ + @Test + public void testNotifyItemInsertedWithHeader() { + mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); + mWrappedAdapter.notifyItemInserted(12); + + verify(mObserver).onItemRangeInserted(eq(13), eq(1)); + } + + /** Test that notifyItemRemoved gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyItemRemovedNoHeader() { + mWrappedAdapter.notifyItemRemoved(12); + + verify(mObserver).onItemRangeRemoved(eq(12), eq(1)); + } + + /** + * Test that notifyItemRemoved gets propagated by HeaderRecyclerView's adapter and adds 1 to the + * position for the extra header item. + */ + @Test + public void testNotifyItemRemovedWithHeader() { + mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); + mWrappedAdapter.notifyItemRemoved(12); + + verify(mObserver).onItemRangeRemoved(eq(13), eq(1)); + } + + /** Test that notifyItemMoved gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyItemMovedNoHeader() { + mWrappedAdapter.notifyItemMoved(12, 18); + + verify(mObserver).onItemRangeMoved(eq(12), eq(18), eq(1)); + } + + /** + * Test that notifyItemMoved gets propagated by HeaderRecyclerView's adapter and adds 1 to the + * position for the extra header item. + */ + @Test + public void testNotifyItemMovedWithHeader() { + mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); + mWrappedAdapter.notifyItemMoved(12, 18); + + verify(mObserver).onItemRangeMoved(eq(13), eq(19), eq(1)); + } + + /** + * Test adapter to be wrapped inside {@link HeaderAdapter} to to send item change notifications. + */ + public static class TestAdapter extends RecyclerView.Adapter { + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + return null; } - /** - * Test that notifyItemChanged gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyItemChangedNoHeader() { - mWrappedAdapter.notifyItemChanged(12); - - verify(mObserver).onItemRangeChanged(eq(12), eq(1), eq(null)); - } - - /** - * Test that notifyItemChanged gets propagated by HeaderRecyclerView's adapter and adds 1 to the - * position for the extra header items. - */ - @Test - public void testNotifyItemChangedWithHeader() { - mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); - mWrappedAdapter.notifyItemChanged(12); - - verify(mObserver).onItemRangeChanged(eq(13), eq(1), eq(null)); - } - - /** - * Test that notifyItemInserted gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyItemInsertedNoHeader() { - mWrappedAdapter.notifyItemInserted(12); - - verify(mObserver).onItemRangeInserted(eq(12), eq(1)); - } - - /** - * Test that notifyItemInserted gets propagated by HeaderRecyclerView's adapter and adds 1 to - * the position for the extra header item. - */ - @Test - public void testNotifyItemInsertedWithHeader() { - mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); - mWrappedAdapter.notifyItemInserted(12); - - verify(mObserver).onItemRangeInserted(eq(13), eq(1)); - } - - /** - * Test that notifyItemRemoved gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyItemRemovedNoHeader() { - mWrappedAdapter.notifyItemRemoved(12); - - verify(mObserver).onItemRangeRemoved(eq(12), eq(1)); - } - - /** - * Test that notifyItemRemoved gets propagated by HeaderRecyclerView's adapter and adds 1 to - * the position for the extra header item. - */ - @Test - public void testNotifyItemRemovedWithHeader() { - mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); - mWrappedAdapter.notifyItemRemoved(12); - - verify(mObserver).onItemRangeRemoved(eq(13), eq(1)); - } - - /** - * Test that notifyItemMoved gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyItemMovedNoHeader() { - mWrappedAdapter.notifyItemMoved(12, 18); - - verify(mObserver).onItemRangeMoved(eq(12), eq(18), eq(1)); - } - - /** - * Test that notifyItemMoved gets propagated by HeaderRecyclerView's adapter and adds 1 to - * the position for the extra header item. - */ - @Test - public void testNotifyItemMovedWithHeader() { - mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); - mWrappedAdapter.notifyItemMoved(12, 18); - - verify(mObserver).onItemRangeMoved(eq(13), eq(19), eq(1)); - } - - /** - * Test adapter to be wrapped inside {@link HeaderAdapter} to to send item change notifications. - */ - public static class TestAdapter extends RecyclerView.Adapter { - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { - return null; - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { - } + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {} - @Override - public int getItemCount() { - return 0; - } + @Override + public int getItemCount() { + return 0; } + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java index 316793f..39929dc 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java @@ -24,18 +24,15 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.widget.RecyclerView; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.SetupWizardPreferenceLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,62 +41,65 @@ import org.junit.runner.RunWith; @SmallTest public class SetupWizardPreferenceLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - } - - @Test - public void testDefaultTemplate() { - SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - } - - @Test - public void testGetRecyclerView() { - SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + } + + @Test + public void testDefaultTemplate() { + SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + } + + @Test + public void testGetRecyclerView() { + SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + } + + @Test + public void testOnCreateRecyclerView() { + SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + final RecyclerView recyclerView = + layout.onCreateRecyclerView( + LayoutInflater.from(mContext), layout, null /* savedInstanceState */); + assertNotNull("RecyclerView created should not be null", recyclerView); + } + + @Test + public void testDividerInset() { + SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertPreferenceTemplateInflated(layout); - @Test - public void testOnCreateRecyclerView() { - SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - final RecyclerView recyclerView = layout.onCreateRecyclerView(LayoutInflater.from(mContext), - layout, null /* savedInstanceState */); - assertNotNull("RecyclerView created should not be null", recyclerView); - } - - @Test - public void testDividerInset() { - SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertPreferenceTemplateInflated(layout); + layout.addView( + layout.onCreateRecyclerView( + LayoutInflater.from(mContext), layout, null /* savedInstanceState */)); - layout.addView(layout.onCreateRecyclerView(LayoutInflater.from(mContext), layout, - null /* savedInstanceState */)); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } + private void assertPreferenceTemplateInflated(SetupWizardPreferenceLayout layout) { + View contentContainer = layout.findViewById(R.id.suw_layout_content); + assertTrue( + "@id/suw_layout_content should be a ViewGroup", contentContainer instanceof ViewGroup); - private void assertPreferenceTemplateInflated(SetupWizardPreferenceLayout layout) { - View contentContainer = layout.findViewById(R.id.suw_layout_content); - assertTrue("@id/suw_layout_content should be a ViewGroup", - contentContainer instanceof ViewGroup); - - assertNotNull("Header text view should not be null", - layout.findManagedViewById(R.id.suw_layout_title)); - assertNotNull("Decoration view should not be null", - layout.findManagedViewById(R.id.suw_layout_decor)); - } + assertNotNull( + "Header text view should not be null", layout.findManagedViewById(R.id.suw_layout_title)); + assertNotNull( + "Decoration view should not be null", layout.findManagedViewById(R.id.suw_layout_decor)); + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java index bbe773b..46a665d 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java @@ -26,21 +26,18 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.SetupWizardRecyclerLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,133 +46,129 @@ import org.junit.runner.RunWith; @SmallTest public class SetupWizardRecyclerLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - } - - @Test - public void testDefaultTemplate() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - SetupWizardRecyclerLayout layout = (SetupWizardRecyclerLayout) - inflater.inflate(R.layout.test_recycler_layout, null); - assertRecyclerTemplateInflated(layout); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + } + + @Test + public void testDefaultTemplate() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + SetupWizardRecyclerLayout layout = + (SetupWizardRecyclerLayout) inflater.inflate(R.layout.test_recycler_layout, null); + assertRecyclerTemplateInflated(layout); + } + + @Test + public void testGetRecyclerView() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + } + + @Test + public void testAdapter() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + + final Adapter adapter = createTestAdapter(1); + layout.setAdapter(adapter); + + final Adapter gotAdapter = layout.getAdapter(); + // Note: The wrapped adapter should be returned, not the HeaderAdapter. + assertSame("Adapter got from SetupWizardLayout should be same as set", adapter, gotAdapter); + } + + @Test + public void testLayout() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + + layout.setAdapter(createTestAdapter(3)); + + layout.measure( + MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY)); + layout.layout(0, 0, 500, 500); + // Test that the layout code doesn't crash. + } + + @Test + public void testDividerInsetLegacy() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertRecyclerTemplateInflated(layout); - @Test - public void testGetRecyclerView() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); - } - - @Test - public void testAdapter() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - - final Adapter adapter = createTestAdapter(1); - layout.setAdapter(adapter); - - final Adapter gotAdapter = layout.getAdapter(); - // Note: The wrapped adapter should be returned, not the HeaderAdapter. - assertSame("Adapter got from SetupWizardLayout should be same as set", - adapter, gotAdapter); - } - - @Test - public void testLayout() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - - layout.setAdapter(createTestAdapter(3)); - - layout.measure( - MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY)); - layout.layout(0, 0, 500, 500); - // Test that the layout code doesn't crash. - } - - @Test - public void testDividerInsetLegacy() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertRecyclerTemplateInflated(layout); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + @Test + public void testDividerInsets() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } - - @Test - public void testDividerInsets() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertRecyclerTemplateInflated(layout); - - layout.setDividerInsets(10, 15); - assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); - assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testTemplateWithNoRecyclerView() { - try { - new SetupWizardRecyclerLayout( - mContext, - R.layout.suw_glif_template, - R.id.suw_recycler_view); - fail("Creating SetupWizardRecyclerLayout with no recycler view should throw exception"); - } catch (Exception e) { - // pass - } - } - - private void assertRecyclerTemplateInflated(SetupWizardRecyclerLayout layout) { - View recyclerView = layout.findViewById(R.id.suw_recycler_view); - assertTrue("@id/suw_recycler_view should be a RecyclerView", - recyclerView instanceof RecyclerView); - - assertNotNull("Header text view should not be null", - layout.findManagedViewById(R.id.suw_layout_title)); - assertNotNull("Decoration view should not be null", - layout.findManagedViewById(R.id.suw_layout_decor)); - } - - private Adapter createTestAdapter(final int itemCount) { - return new Adapter() { - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int position) { - return new ViewHolder(new View(parent.getContext())) {}; - } - - @Override - public void onBindViewHolder(ViewHolder viewHolder, int position) { - } - - @Override - public int getItemCount() { - return itemCount; - } - }; + assertRecyclerTemplateInflated(layout); + + layout.setDividerInsets(10, 15); + assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); + assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); + + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } + + @Test + public void testTemplateWithNoRecyclerView() { + try { + new SetupWizardRecyclerLayout(mContext, R.layout.suw_glif_template, R.id.suw_recycler_view); + fail("Creating SetupWizardRecyclerLayout with no recycler view should throw exception"); + } catch (Exception e) { + // pass } + } + + private void assertRecyclerTemplateInflated(SetupWizardRecyclerLayout layout) { + View recyclerView = layout.findViewById(R.id.suw_recycler_view); + assertTrue( + "@id/suw_recycler_view should be a RecyclerView", recyclerView instanceof RecyclerView); + + assertNotNull( + "Header text view should not be null", layout.findManagedViewById(R.id.suw_layout_title)); + assertNotNull( + "Decoration view should not be null", layout.findManagedViewById(R.id.suw_layout_decor)); + } + + private Adapter createTestAdapter(final int itemCount) { + return new Adapter() { + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int position) { + return new ViewHolder(new View(parent.getContext())) {}; + } + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int position) {} + + @Override + public int getItemCount() { + return itemCount; + } + }; + } } diff --git a/library/recyclerview/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java b/library/recyclerview/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java index 6fa4c54..bcb51a8 100644 --- a/library/recyclerview/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java +++ b/library/recyclerview/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java @@ -26,63 +26,60 @@ import static org.robolectric.RuntimeEnvironment.application; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.OnScrollListener; - -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) -@RunWith(SuwLibRobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) +@RunWith(RobolectricTestRunner.class) public class RecyclerViewScrollHandlingDelegateTest { - @Mock - private RequireScrollMixin mRequireScrollMixin; - - private RecyclerView mRecyclerView; - private RecyclerViewScrollHandlingDelegate mDelegate; - private ArgumentCaptor<OnScrollListener> mListenerCaptor; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mRecyclerView = spy(new RecyclerView(application)); - doReturn(20).when(mRecyclerView).computeVerticalScrollRange(); - doReturn(0).when(mRecyclerView).computeVerticalScrollExtent(); - doReturn(0).when(mRecyclerView).computeVerticalScrollOffset(); - mListenerCaptor = ArgumentCaptor.forClass(OnScrollListener.class); - doNothing().when(mRecyclerView).addOnScrollListener(mListenerCaptor.capture()); - - mDelegate = new RecyclerViewScrollHandlingDelegate(mRequireScrollMixin, mRecyclerView); - mRecyclerView.layout(0, 0, 50, 50); - } - - @Test - public void testRequireScroll() { - mDelegate.startListening(); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); - } - - @Test - public void testScrolledToBottom() { - mDelegate.startListening(); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); - - doReturn(20).when(mRecyclerView).computeVerticalScrollOffset(); - mListenerCaptor.getValue().onScrolled(mRecyclerView, 0, 20); - - verify(mRequireScrollMixin).notifyScrollabilityChange(false); - } - - @Test - public void testClickScrollButton() { - mDelegate.pageScrollDown(); - verify(mRecyclerView).smoothScrollBy(anyInt(), eq(50)); - } + @Mock private RequireScrollMixin mRequireScrollMixin; + + private RecyclerView mRecyclerView; + private RecyclerViewScrollHandlingDelegate mDelegate; + private ArgumentCaptor<OnScrollListener> mListenerCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mRecyclerView = spy(new RecyclerView(application)); + doReturn(20).when(mRecyclerView).computeVerticalScrollRange(); + doReturn(0).when(mRecyclerView).computeVerticalScrollExtent(); + doReturn(0).when(mRecyclerView).computeVerticalScrollOffset(); + mListenerCaptor = ArgumentCaptor.forClass(OnScrollListener.class); + doNothing().when(mRecyclerView).addOnScrollListener(mListenerCaptor.capture()); + + mDelegate = new RecyclerViewScrollHandlingDelegate(mRequireScrollMixin, mRecyclerView); + mRecyclerView.layout(0, 0, 50, 50); + } + + @Test + public void testRequireScroll() { + mDelegate.startListening(); + verify(mRequireScrollMixin).notifyScrollabilityChange(true); + } + + @Test + public void testScrolledToBottom() { + mDelegate.startListening(); + verify(mRequireScrollMixin).notifyScrollabilityChange(true); + + doReturn(20).when(mRecyclerView).computeVerticalScrollOffset(); + mListenerCaptor.getValue().onScrolled(mRecyclerView, 0, 20); + + verify(mRequireScrollMixin).notifyScrollabilityChange(false); + } + + @Test + public void testClickScrollButton() { + mDelegate.pageScrollDown(); + verify(mRecyclerView).smoothScrollBy(anyInt(), eq(50)); + } } diff --git a/library/rules.gradle b/library/rules.gradle index 9baa390..4e815ce 100644 --- a/library/rules.gradle +++ b/library/rules.gradle @@ -26,7 +26,7 @@ android { // Provides backwards compatibility for Gingerbread or above, using support libraries. gingerbreadCompat { dimension 'compat' - minSdkVersion 9 + minSdkVersion 14 } } diff --git a/library/self.gradle b/library/self.gradle index a4bff4e..f649581 100644 --- a/library/self.gradle +++ b/library/self.gradle @@ -25,8 +25,8 @@ android.sourceSets { res.srcDirs = ['test/instrumentation/res'] dependencies { - androidTestImplementation 'com.android.support.test:rules:1.0.0' - androidTestImplementation 'com.android.support.test:runner:1.0.0' + androidTestImplementation 'com.android.support.test:rules:1.0.1' + androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestImplementation 'com.google.truth:truth:0.31' @@ -51,8 +51,8 @@ android.sourceSets { java.srcDirs = ['test/robotest/src'] dependencies { - testImplementation 'org.robolectric:robolectric:3.6.1' - testImplementation 'org.robolectric:shadows-framework:3.6.1' + testImplementation 'org.robolectric:robolectric:4.0-alpha-3' + testImplementation 'org.robolectric:shadows-framework:4.0-alpha-3' testImplementation 'junit:junit:4.+' testImplementation 'com.google.truth:truth:0.31' testImplementation 'org.mockito:mockito-core:1.9.5' diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/TemplateLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/TemplateLayoutTest.java index ddce677..a5026ee 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/TemplateLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/TemplateLayoutTest.java @@ -23,16 +23,14 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.template.HeaderMixin; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,65 +39,64 @@ import org.junit.runner.RunWith; @SmallTest public class TemplateLayoutTest { - private Context mContext; + private Context mContext; - @Before - public void setUp() throws Exception { - mContext = InstrumentationRegistry.getContext(); - } + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + } - @Test - public void testAddView() { - TemplateLayout layout = new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content); - TextView tv = new TextView(mContext); - tv.setId(R.id.test_view_id); - layout.addView(tv); - View view = layout.findViewById(R.id.test_view_id); - assertSame("The view added should be the same text view", tv, view); - } + @Test + public void testAddView() { + TemplateLayout layout = + new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content); + TextView tv = new TextView(mContext); + tv.setId(R.id.test_view_id); + layout.addView(tv); + View view = layout.findViewById(R.id.test_view_id); + assertSame("The view added should be the same text view", tv, view); + } - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - TemplateLayout layout = - (TemplateLayout) inflater.inflate(R.layout.test_template_layout, null); - View content = layout.findViewById(R.id.test_content); - assertTrue("@id/test_content should be a TextView", content instanceof TextView); - } + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + TemplateLayout layout = (TemplateLayout) inflater.inflate(R.layout.test_template_layout, null); + View content = layout.findViewById(R.id.test_content); + assertTrue("@id/test_content should be a TextView", content instanceof TextView); + } - @Test - public void testTemplate() { - TemplateLayout layout = new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content); - View templateView = layout.findViewById(R.id.test_template_view); - assertNotNull("@id/test_template_view should exist in template", templateView); + @Test + public void testTemplate() { + TemplateLayout layout = + new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content); + View templateView = layout.findViewById(R.id.test_template_view); + assertNotNull("@id/test_template_view should exist in template", templateView); - TextView tv = new TextView(mContext); - tv.setId(R.id.test_view_id); - layout.addView(tv); + TextView tv = new TextView(mContext); + tv.setId(R.id.test_view_id); + layout.addView(tv); - templateView = layout.findViewById(R.id.test_template_view); - assertNotNull("@id/test_template_view should exist in template", templateView); - View contentView = layout.findViewById(R.id.test_view_id); - assertSame("The view added should be the same text view", tv, contentView); - } + templateView = layout.findViewById(R.id.test_template_view); + assertNotNull("@id/test_template_view should exist in template", templateView); + View contentView = layout.findViewById(R.id.test_view_id); + assertSame("The view added should be the same text view", tv, contentView); + } - @Test - public void testNoTemplate() { - try { - new TemplateLayout(mContext, 0, 0); - fail("Inflating TemplateLayout without template should throw exception"); - } catch (IllegalArgumentException e) { - // Expected IllegalArgumentException - } + @Test + public void testNoTemplate() { + try { + new TemplateLayout(mContext, 0, 0); + fail("Inflating TemplateLayout without template should throw exception"); + } catch (IllegalArgumentException e) { + // Expected IllegalArgumentException } + } - @Test - public void testGetMixin() { - TemplateLayout layout = new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content); - final HeaderMixin mixin = layout.getMixin(HeaderMixin.class); - assertNull("getMixin for a mixin that doesn't exist should return null", mixin); - } + @Test + public void testGetMixin() { + TemplateLayout layout = + new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content); + final HeaderMixin mixin = layout.getMixin(HeaderMixin.class); + assertNull("getMixin for a mixin that doesn't exist should return null", mixin); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/ButtonFooterMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/ButtonFooterMixinTest.java index 08f5958..971211b 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/ButtonFooterMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/ButtonFooterMixinTest.java @@ -24,9 +24,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.annotation.IdRes; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; @@ -34,12 +32,11 @@ import android.view.ViewStub; import android.widget.Button; import android.widget.FrameLayout; import android.widget.LinearLayout; - -import androidx.annotation.IdRes; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,125 +45,128 @@ import org.junit.runner.RunWith; @SmallTest public class ButtonFooterMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - - // The parent view to contain the view stub and views it inflates. - private FrameLayout mStubParent; - private ViewStub mFooterStub; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mFooterStub = new ViewStub(mContext, R.layout.suw_glif_footer_button_bar); - mStubParent = new FrameLayout(mContext); - mStubParent.addView(mFooterStub); - doReturn(mFooterStub).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_footer)); - } - - @Test - public void testAddButton() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button button = mixin.addButton("foobar", R.style.SuwGlifButton_Primary); - - assertNotNull(button); - @IdRes final int id = 12345; - button.setId(id); - assertNotNull(mStubParent.findViewById(id)); - - assertEquals("foobar", button.getText()); - - // Make sure the style is applied by checking the paddings - assertEquals(dp2Px(16), button.getPaddingLeft()); - assertEquals(dp2Px(16), button.getPaddingRight()); - } - - @Test - public void testAddButtonTextRes() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button button = mixin.addButton(R.string.suw_next_button_label, - R.style.SuwGlifButton_Primary); - - assertNotNull(button); - button.setTag("button"); - assertNotNull(mStubParent.findViewWithTag("button")); - - assertEquals("Next", button.getText()); - - // Make sure the style is applied by checking the paddings - assertEquals(dp2Px(16), button.getPaddingLeft()); - assertEquals(dp2Px(16), button.getPaddingRight()); - } - - @Test - public void testAddSpace() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - mixin.addButton("foo", R.style.SuwGlifButton_Secondary); - final View space = mixin.addSpace(); - mixin.addButton("bar", R.style.SuwGlifButton_Primary); - - space.setTag("space"); - assertNotNull(mStubParent.findViewWithTag("space")); - assertEquals("Space should have weight of 1", - 1f, ((LinearLayout.LayoutParams) space.getLayoutParams()).weight, 0.001); - } - - @Test - public void testRemoveButton() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); - final Button barButton = mixin.addButton("bar", R.style.SuwGlifButton_Secondary); - - fooButton.setTag("foo"); - barButton.setTag("bar"); - assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); - assertNotNull("Bar button should exist", mStubParent.findViewWithTag("bar")); - - mixin.removeButton(fooButton); - - assertNull("Foo button should be removed", mStubParent.findViewWithTag("foo")); - assertNotNull("Bar button should not be removed", mStubParent.findViewWithTag("bar")); - } - - @Test - public void testRemoveSpace() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); - final View space = mixin.addSpace(); - - fooButton.setTag("foo"); - space.setTag("space"); - assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); - assertNotNull("space should exist", mStubParent.findViewWithTag("space")); - - mixin.removeSpace(space); - - assertNotNull("Foo button should not be removed", mStubParent.findViewWithTag("foo")); - assertNull("Space should be removed", mStubParent.findViewWithTag("space")); - } - - @Test - public void testRemoveAllViews() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); - final View space = mixin.addSpace(); - - fooButton.setTag("foo"); - space.setTag("space"); - assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); - assertNotNull("space should exist", mStubParent.findViewWithTag("space")); - - mixin.removeAllViews(); - - assertNull("Foo button should be removed", mStubParent.findViewWithTag("foo")); - assertNull("Space should be removed", mStubParent.findViewWithTag("space")); - } - - private int dp2Px(float dp) { - DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); - } + private Context mContext; + private TemplateLayout mTemplateLayout; + + // The parent view to contain the view stub and views it inflates. + private FrameLayout mStubParent; + private ViewStub mFooterStub; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mFooterStub = new ViewStub(mContext, R.layout.suw_glif_footer_button_bar); + mStubParent = new FrameLayout(mContext); + mStubParent.addView(mFooterStub); + doReturn(mFooterStub).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_footer)); + } + + @Test + public void testAddButton() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button button = mixin.addButton("foobar", R.style.SuwGlifButton_Primary); + + assertNotNull(button); + @IdRes final int id = 12345; + button.setId(id); + assertNotNull(mStubParent.findViewById(id)); + + assertEquals("foobar", button.getText()); + + // Make sure the style is applied by checking the paddings + assertEquals(dp2Px(16), button.getPaddingLeft()); + assertEquals(dp2Px(16), button.getPaddingRight()); + } + + @Test + public void testAddButtonTextRes() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button button = + mixin.addButton(R.string.suw_next_button_label, R.style.SuwGlifButton_Primary); + + assertNotNull(button); + button.setTag("button"); + assertNotNull(mStubParent.findViewWithTag("button")); + + assertEquals("Next", button.getText()); + + // Make sure the style is applied by checking the paddings + assertEquals(dp2Px(16), button.getPaddingLeft()); + assertEquals(dp2Px(16), button.getPaddingRight()); + } + + @Test + public void testAddSpace() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + mixin.addButton("foo", R.style.SuwGlifButton_Secondary); + final View space = mixin.addSpace(); + mixin.addButton("bar", R.style.SuwGlifButton_Primary); + + space.setTag("space"); + assertNotNull(mStubParent.findViewWithTag("space")); + assertEquals( + "Space should have weight of 1", + 1f, + ((LinearLayout.LayoutParams) space.getLayoutParams()).weight, + 0.001); + } + + @Test + public void testRemoveButton() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); + final Button barButton = mixin.addButton("bar", R.style.SuwGlifButton_Secondary); + + fooButton.setTag("foo"); + barButton.setTag("bar"); + assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); + assertNotNull("Bar button should exist", mStubParent.findViewWithTag("bar")); + + mixin.removeButton(fooButton); + + assertNull("Foo button should be removed", mStubParent.findViewWithTag("foo")); + assertNotNull("Bar button should not be removed", mStubParent.findViewWithTag("bar")); + } + + @Test + public void testRemoveSpace() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); + final View space = mixin.addSpace(); + + fooButton.setTag("foo"); + space.setTag("space"); + assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); + assertNotNull("space should exist", mStubParent.findViewWithTag("space")); + + mixin.removeSpace(space); + + assertNotNull("Foo button should not be removed", mStubParent.findViewWithTag("foo")); + assertNull("Space should be removed", mStubParent.findViewWithTag("space")); + } + + @Test + public void testRemoveAllViews() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); + final View space = mixin.addSpace(); + + fooButton.setTag("foo"); + space.setTag("space"); + assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); + assertNotNull("space should exist", mStubParent.findViewWithTag("space")); + + mixin.removeAllViews(); + + assertNull("Foo button should be removed", mStubParent.findViewWithTag("foo")); + assertNull("Space should be removed", mStubParent.findViewWithTag("space")); + } + + private int dp2Px(float dp) { + DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/ColoredHeaderMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/ColoredHeaderMixinTest.java index 1c86af1..3ea8f6e 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/ColoredHeaderMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/ColoredHeaderMixinTest.java @@ -25,67 +25,62 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.XmlResourceParser; import android.graphics.Color; +import android.util.Xml; +import android.widget.TextView; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.Xml; -import android.widget.TextView; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; - @RunWith(AndroidJUnit4.class) @SmallTest public class ColoredHeaderMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - private TextView mHeaderTextView; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mHeaderTextView = new TextView(mContext); - doReturn(mHeaderTextView).when(mTemplateLayout) - .findManagedViewById(eq(R.id.suw_layout_title)); + private Context mContext; + private TemplateLayout mTemplateLayout; + private TextView mHeaderTextView; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mHeaderTextView = new TextView(mContext); + doReturn(mHeaderTextView).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_title)); + } + + @Test + public void testSetColor() { + ColoredHeaderMixin mixin = new ColoredHeaderMixin(mTemplateLayout, null, 0); + mixin.setColor(ColorStateList.valueOf(Color.MAGENTA)); + + assertEquals(ColorStateList.valueOf(Color.MAGENTA), mHeaderTextView.getTextColors()); + } + + @Test + public void testGetColor() { + ColoredHeaderMixin mixin = new ColoredHeaderMixin(mTemplateLayout, null, 0); + mHeaderTextView.setTextColor(ColorStateList.valueOf(Color.GREEN)); + + assertEquals(ColorStateList.valueOf(Color.GREEN), mixin.getColor()); + } + + @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. + @Test + public void testSetColorFromXml() throws IOException, XmlPullParserException { + final XmlResourceParser parser = mContext.getResources().getXml(R.layout.test_mixin_attributes); + while (!TemplateLayout.class.getName().equals(parser.getName())) { + parser.next(); } + new ColoredHeaderMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - @Test - public void testSetColor() { - ColoredHeaderMixin mixin = new ColoredHeaderMixin(mTemplateLayout, null, 0); - mixin.setColor(ColorStateList.valueOf(Color.MAGENTA)); - - assertEquals(ColorStateList.valueOf(Color.MAGENTA), mHeaderTextView.getTextColors()); - } - - @Test - public void testGetColor() { - ColoredHeaderMixin mixin = new ColoredHeaderMixin(mTemplateLayout, null, 0); - mHeaderTextView.setTextColor(ColorStateList.valueOf(Color.GREEN)); - - assertEquals(ColorStateList.valueOf(Color.GREEN), mixin.getColor()); - } - - @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. - @Test - public void testSetColorFromXml() throws IOException, XmlPullParserException { - final XmlResourceParser parser = - mContext.getResources().getXml(R.layout.test_mixin_attributes); - while (!TemplateLayout.class.getName().equals(parser.getName())) { - parser.next(); - } - new ColoredHeaderMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - - assertEquals(ColorStateList.valueOf(Color.RED), mHeaderTextView.getTextColors()); - } + assertEquals(ColorStateList.valueOf(Color.RED), mHeaderTextView.getTextColors()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/HeaderMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/HeaderMixinTest.java index a1b4b59..211f95f 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/HeaderMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/HeaderMixinTest.java @@ -25,82 +25,77 @@ import static org.mockito.Mockito.spy; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.XmlResourceParser; +import android.util.Xml; +import android.widget.TextView; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.Xml; -import android.widget.TextView; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; - @RunWith(AndroidJUnit4.class) @SmallTest public class HeaderMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - private TextView mHeaderTextView; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mHeaderTextView = new TextView(mContext); - doReturn(mHeaderTextView).when(mTemplateLayout) - .findManagedViewById(eq(R.id.suw_layout_title)); + private Context mContext; + private TemplateLayout mTemplateLayout; + private TextView mHeaderTextView; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mHeaderTextView = new TextView(mContext); + doReturn(mHeaderTextView).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_title)); + } + + @Test + public void testGetTextView() { + HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); + assertSame(mHeaderTextView, mixin.getTextView()); + } + + @Test + public void testSetTextId() { + HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); + mixin.setText(R.string.suw_next_button_label); + + assertEquals("Next", mHeaderTextView.getText()); + } + + @Test + public void testSetText() { + HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); + mixin.setText("Foobar"); + + assertEquals("Foobar", mHeaderTextView.getText()); + } + + @SuppressLint("SetTextI18n") // It's OK, this is a test + @Test + public void testGetText() { + mHeaderTextView.setText("Lorem ipsum"); + + HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); + assertEquals("Lorem ipsum", mixin.getText()); + } + + @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. + @Test + public void testSetTextFromXml() throws IOException, XmlPullParserException { + final XmlResourceParser parser = mContext.getResources().getXml(R.layout.test_mixin_attributes); + while (!TemplateLayout.class.getName().equals(parser.getName())) { + parser.next(); } + new HeaderMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - @Test - public void testGetTextView() { - HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); - assertSame(mHeaderTextView, mixin.getTextView()); - } - - @Test - public void testSetTextId() { - HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); - mixin.setText(R.string.suw_next_button_label); - - assertEquals("Next", mHeaderTextView.getText()); - } - - @Test - public void testSetText() { - HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); - mixin.setText("Foobar"); - - assertEquals("Foobar", mHeaderTextView.getText()); - } - - @SuppressLint("SetTextI18n") // It's OK, this is a test - @Test - public void testGetText() { - mHeaderTextView.setText("Lorem ipsum"); - - HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); - assertEquals("Lorem ipsum", mixin.getText()); - } - - @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. - @Test - public void testSetTextFromXml() throws IOException, XmlPullParserException { - final XmlResourceParser parser = - mContext.getResources().getXml(R.layout.test_mixin_attributes); - while (!TemplateLayout.class.getName().equals(parser.getName())) { - parser.next(); - } - new HeaderMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - - assertEquals("lorem ipsum", mHeaderTextView.getText()); - } + assertEquals("lorem ipsum", mHeaderTextView.getText()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java index 5a36f4a..001fe33 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.template; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.mockito.Matchers.eq; @@ -30,115 +29,111 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.util.Xml; import android.view.View; import android.widget.ImageView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; - @RunWith(AndroidJUnit4.class) @SmallTest public class IconMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - private ImageView mIconView; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mIconView = new ImageView(mContext); - doReturn(mIconView).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_icon)); - } - - @Test - public void testGetIconView() { - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - assertSame(mIconView, mixin.getView()); - } - - @Test - public void testSetIcon() { - final ColorDrawable drawable = new ColorDrawable(Color.CYAN); - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mixin.setIcon(drawable); - - assertSame(drawable, mIconView.getDrawable()); - assertEquals(View.VISIBLE, mIconView.getVisibility()); - } - - @Test - public void setIcon_resourceId_shouldSetIcon() { - int icon = android.R.drawable.ic_menu_add; - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mixin.setIcon(icon); - - Drawable drawable = mIconView.getDrawable(); - assertThat(drawable).isInstanceOf(BitmapDrawable.class); - assertEquals(View.VISIBLE, mIconView.getVisibility()); - } - - @Test - public void setIcon_shouldSetVisibilityToGone_whenIconIsNull() { - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mixin.setIcon(null); - - assertEquals(View.GONE, mIconView.getVisibility()); - } - - @Test - public void testGetIcon() { - final ColorDrawable drawable = new ColorDrawable(Color.BLUE); - mIconView.setImageDrawable(drawable); - - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - assertSame(drawable, mixin.getIcon()); - } - - @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. - @Test - public void testSetIconFromXml() throws IOException, XmlPullParserException { - final XmlResourceParser parser = - mContext.getResources().getXml(R.layout.test_mixin_attributes); - while (!TemplateLayout.class.getName().equals(parser.getName())) { - parser.next(); - } - new IconMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - - // Check that the bitmaps themselves are equal because BitmapDrawable does not implement - // equals() - final BitmapDrawable expected = (BitmapDrawable) mContext.getResources() - .getDrawable(android.R.drawable.ic_menu_add); - final BitmapDrawable actual = (BitmapDrawable) mIconView.getDrawable(); - assertEquals(expected.getBitmap(), actual.getBitmap()); - assertEquals(View.VISIBLE, mIconView.getVisibility()); - } - - @Test - public void setContentDescription_shouldSetContentDescriptionOnIconView() { - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mixin.setContentDescription("hello world"); - assertThat(mIconView.getContentDescription()).isEqualTo("hello world"); - } - - @Test - public void getContentDescription_shouldReturnContentDescriptionFromView() { - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mIconView.setContentDescription("aloha"); - assertThat(mixin.getContentDescription()).isEqualTo("aloha"); + private Context mContext; + private TemplateLayout mTemplateLayout; + private ImageView mIconView; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mIconView = new ImageView(mContext); + doReturn(mIconView).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_icon)); + } + + @Test + public void testGetIconView() { + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + assertSame(mIconView, mixin.getView()); + } + + @Test + public void testSetIcon() { + final ColorDrawable drawable = new ColorDrawable(Color.CYAN); + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mixin.setIcon(drawable); + + assertSame(drawable, mIconView.getDrawable()); + assertEquals(View.VISIBLE, mIconView.getVisibility()); + } + + @Test + public void setIcon_resourceId_shouldSetIcon() { + int icon = android.R.drawable.ic_menu_add; + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mixin.setIcon(icon); + + Drawable drawable = mIconView.getDrawable(); + assertThat(drawable).isInstanceOf(BitmapDrawable.class); + assertEquals(View.VISIBLE, mIconView.getVisibility()); + } + + @Test + public void setIcon_shouldSetVisibilityToGone_whenIconIsNull() { + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mixin.setIcon(null); + + assertEquals(View.GONE, mIconView.getVisibility()); + } + + @Test + public void testGetIcon() { + final ColorDrawable drawable = new ColorDrawable(Color.BLUE); + mIconView.setImageDrawable(drawable); + + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + assertSame(drawable, mixin.getIcon()); + } + + @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. + @Test + public void testSetIconFromXml() throws IOException, XmlPullParserException { + final XmlResourceParser parser = mContext.getResources().getXml(R.layout.test_mixin_attributes); + while (!TemplateLayout.class.getName().equals(parser.getName())) { + parser.next(); } + new IconMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); + + // Check that the bitmaps themselves are equal because BitmapDrawable does not implement + // equals() + final BitmapDrawable expected = + (BitmapDrawable) mContext.getResources().getDrawable(android.R.drawable.ic_menu_add); + final BitmapDrawable actual = (BitmapDrawable) mIconView.getDrawable(); + assertEquals(expected.getBitmap(), actual.getBitmap()); + assertEquals(View.VISIBLE, mIconView.getVisibility()); + } + + @Test + public void setContentDescription_shouldSetContentDescriptionOnIconView() { + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mixin.setContentDescription("hello world"); + assertThat(mIconView.getContentDescription()).isEqualTo("hello world"); + } + + @Test + public void getContentDescription_shouldReturnContentDescriptionFromView() { + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mIconView.setContentDescription("aloha"); + assertThat(mixin.getContentDescription()).isEqualTo("aloha"); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/ListMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/ListMixinTest.java index 30d68f1..e73e2bc 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/ListMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/ListMixinTest.java @@ -32,16 +32,14 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.widget.ListAdapter; import android.widget.ListView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,140 +50,138 @@ import org.mockito.MockitoAnnotations; @SmallTest public class ListMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; + private Context mContext; + private TemplateLayout mTemplateLayout; - private ListView mListView; + private ListView mListView; - @Mock - private ListAdapter mAdapter; + @Mock private ListAdapter mAdapter; - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); - mListView = mock(ListView.class, delegatesTo(new ListView(mContext))); - doReturn(1).when(mAdapter).getViewTypeCount(); + mListView = mock(ListView.class, delegatesTo(new ListView(mContext))); + doReturn(1).when(mAdapter).getViewTypeCount(); - doReturn(mListView).when(mTemplateLayout) - .findManagedViewById(eq(android.R.id.list)); - doReturn(true).when(mTemplateLayout).isLayoutDirectionResolved(); - } + doReturn(mListView).when(mTemplateLayout).findManagedViewById(eq(android.R.id.list)); + doReturn(true).when(mTemplateLayout).isLayoutDirectionResolved(); + } - @Test - public void testGetListView() { - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - assertSame(mListView, mixin.getListView()); - } + @Test + public void testGetListView() { + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + assertSame(mListView, mixin.getListView()); + } - @Test - public void testGetAdapter() { - mListView.setAdapter(mAdapter); + @Test + public void testGetAdapter() { + mListView.setAdapter(mAdapter); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - assertSame(mAdapter, mixin.getAdapter()); - } + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + assertSame(mAdapter, mixin.getAdapter()); + } - @Test - public void testSetAdapter() { - assertNull(mListView.getAdapter()); + @Test + public void testSetAdapter() { + assertNull(mListView.getAdapter()); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setAdapter(mAdapter); + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setAdapter(mAdapter); - assertSame(mAdapter, mListView.getAdapter()); - } + assertSame(mAdapter, mListView.getAdapter()); + } - @Test - public void testDividerInsetLegacy() { - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setDividerInset(123); + @Test + public void testDividerInsetLegacy() { + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setDividerInset(123); - assertEquals(123, mixin.getDividerInset()); + assertEquals(123, mixin.getDividerInset()); - final Drawable divider = mListView.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mListView.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(123, 0, 0, 0), rect); - } + assertEquals(new Rect(123, 0, 0, 0), rect); + } - @Test - public void testDividerInsets() { - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setDividerInsets(123, 456); + @Test + public void testDividerInsets() { + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setDividerInsets(123, 456); - assertEquals(123, mixin.getDividerInsetStart()); - assertEquals(456, mixin.getDividerInsetEnd()); + assertEquals(123, mixin.getDividerInsetStart()); + assertEquals(456, mixin.getDividerInsetEnd()); - final Drawable divider = mListView.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mListView.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(123, 0, 456, 0), rect); - } + assertEquals(new Rect(123, 0, 456, 0), rect); + } - @Test - public void testDividerInsetLegacyRtl() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); + @Test + public void testDividerInsetLegacyRtl() { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setDividerInset(123); + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setDividerInset(123); - assertEquals(123, mixin.getDividerInset()); + assertEquals(123, mixin.getDividerInset()); - final Drawable divider = mListView.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mListView.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(0, 0, 123, 0), rect); - } - // else the test passes + assertEquals(new Rect(0, 0, 123, 0), rect); } + // else the test passes + } - @Test - public void testDividerInsetsRtl() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); + @Test + public void testDividerInsetsRtl() { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setDividerInsets(123, 456); + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setDividerInsets(123, 456); - assertEquals(123, mixin.getDividerInsetStart()); - assertEquals(456, mixin.getDividerInsetEnd()); + assertEquals(123, mixin.getDividerInsetStart()); + assertEquals(456, mixin.getDividerInsetEnd()); - final Drawable divider = mListView.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mListView.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(456, 0, 123, 0), rect); - } - // else the test passes + assertEquals(new Rect(456, 0, 123, 0), rect); } + // else the test passes + } - @Test - public void testNoList() { - doReturn(null).when(mTemplateLayout).findManagedViewById(eq(android.R.id.list)); + @Test + public void testNoList() { + doReturn(null).when(mTemplateLayout).findManagedViewById(eq(android.R.id.list)); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setAdapter(mAdapter); - mixin.setDividerInset(123); + mixin.setAdapter(mAdapter); + mixin.setDividerInset(123); - assertNull(mixin.getListView()); - assertNull(mixin.getAdapter()); - mixin.getDividerInset(); // Test that it doesn't crash. The return value is not significant. - assertNull(mixin.getDivider()); + assertNull(mixin.getListView()); + assertNull(mixin.getAdapter()); + mixin.getDividerInset(); // Test that it doesn't crash. The return value is not significant. + assertNull(mixin.getDivider()); - verifyNoMoreInteractions(mListView); - } + verifyNoMoreInteractions(mListView); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/NavigationBarMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/NavigationBarMixinTest.java index aca6084..1e2aff3 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/NavigationBarMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/NavigationBarMixinTest.java @@ -29,12 +29,10 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; import com.android.setupwizardlib.view.NavigationBar; import com.android.setupwizardlib.view.NavigationBar.NavigationBarListener; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,56 +41,57 @@ import org.junit.runner.RunWith; @SmallTest public class NavigationBarMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - private NavigationBar mNavigationBar; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mNavigationBar = new NavigationBar(mContext); - doReturn(mNavigationBar).when(mTemplateLayout) - .findManagedViewById(eq(R.id.suw_layout_navigation_bar)); - } - - @Test - public void testGetNavigationBar() { - NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); - assertSame(mNavigationBar, mixin.getNavigationBar()); - } - - @Test - public void testSetNextButtonText() { - NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); - mixin.setNextButtonText(R.string.suw_more_button_label); - assertEquals("More", mNavigationBar.getNextButton().getText()); - - mixin.setNextButtonText("Foobar"); - assertEquals("Foobar", mNavigationBar.getNextButton().getText()); - } - - @SuppressLint("SetTextI18n") // It's OK, this is just a test - @Test - public void testGetNextButtonText() { - mNavigationBar.getNextButton().setText("lorem ipsum"); - - NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); - assertSame("lorem ipsum", mixin.getNextButtonText()); - } - - @Test - public void testSetNavigationBarListener() { - final NavigationBarListener listener = mock(NavigationBarListener.class); - NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); - mixin.setNavigationBarListener(listener); - - mNavigationBar.getNextButton().performClick(); - verify(listener).onNavigateNext(); - - mNavigationBar.getBackButton().performClick(); - verify(listener).onNavigateBack(); - } + private Context mContext; + private TemplateLayout mTemplateLayout; + private NavigationBar mNavigationBar; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mNavigationBar = new NavigationBar(mContext); + doReturn(mNavigationBar) + .when(mTemplateLayout) + .findManagedViewById(eq(R.id.suw_layout_navigation_bar)); + } + + @Test + public void testGetNavigationBar() { + NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); + assertSame(mNavigationBar, mixin.getNavigationBar()); + } + + @Test + public void testSetNextButtonText() { + NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); + mixin.setNextButtonText(R.string.suw_more_button_label); + assertEquals("More", mNavigationBar.getNextButton().getText()); + + mixin.setNextButtonText("Foobar"); + assertEquals("Foobar", mNavigationBar.getNextButton().getText()); + } + + @SuppressLint("SetTextI18n") // It's OK, this is just a test + @Test + public void testGetNextButtonText() { + mNavigationBar.getNextButton().setText("lorem ipsum"); + + NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); + assertSame("lorem ipsum", mixin.getNextButtonText()); + } + + @Test + public void testSetNavigationBarListener() { + final NavigationBarListener listener = mock(NavigationBarListener.class); + NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); + mixin.setNavigationBarListener(listener); + + mNavigationBar.getNextButton().performClick(); + verify(listener).onNavigateNext(); + + mNavigationBar.getBackButton().performClick(); + verify(listener).onNavigateBack(); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/ProgressBarMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/ProgressBarMixinTest.java index 5b2fb50..78ebe1e 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/ProgressBarMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/ProgressBarMixinTest.java @@ -29,16 +29,14 @@ import android.graphics.Canvas; import android.graphics.Color; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.ProgressBar; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,111 +45,107 @@ import org.junit.runner.RunWith; @SmallTest public class ProgressBarMixinTest { - private TemplateLayout mTemplateLayout; - - @Before - public void setUp() { - Context context = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - mTemplateLayout = new TemplateLayout( - context, - R.layout.test_progress_bar_template, R.id.suw_layout_content); - } - - @Test - public void testSetShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setShown(true); - - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertNotNull("Progress bar should be available after setting to shown", progressBar); - assertEquals(View.VISIBLE, progressBar.getVisibility()); - } - - @Test - public void testNotShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setShown(true); - mixin.setShown(false); - - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertNotEquals(View.VISIBLE, progressBar.getVisibility()); + private TemplateLayout mTemplateLayout; + + @Before + public void setUp() { + Context context = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + mTemplateLayout = + new TemplateLayout(context, R.layout.test_progress_bar_template, R.id.suw_layout_content); + } + + @Test + public void testSetShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setShown(true); + + ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertNotNull("Progress bar should be available after setting to shown", progressBar); + assertEquals(View.VISIBLE, progressBar.getVisibility()); + } + + @Test + public void testNotShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setShown(true); + mixin.setShown(false); + + ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertNotEquals(View.VISIBLE, progressBar.getVisibility()); + } + + @Test + public void testIsShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + + mixin.setShown(true); + assertTrue(mixin.isShown()); + + mixin.setShown(false); + assertFalse(mixin.isShown()); + } + + @Test + public void testPeekProgressBar() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + assertNull( + "PeekProgressBar should return null when stub not inflated yet", mixin.peekProgressBar()); + + mixin.setShown(true); + assertNotNull( + "PeekProgressBar should be available after setting to shown", mixin.peekProgressBar()); + } + + @Test + public void testSetColorBeforeSetShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setColor(ColorStateList.valueOf(Color.MAGENTA)); + + mixin.setShown(true); + + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = + (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertEquals(ColorStateList.valueOf(Color.MAGENTA), progressBar.getIndeterminateTintList()); + assertEquals( + ColorStateList.valueOf(Color.MAGENTA), progressBar.getProgressBackgroundTintList()); } - - @Test - public void testIsShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - - mixin.setShown(true); - assertTrue(mixin.isShown()); - - mixin.setShown(false); - assertFalse(mixin.isShown()); + // this method is a no-op on versions < lollipop. Just check that it doesn't crash. + } + + @Test + public void testSetColorAfterSetShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setShown(true); + + mixin.setColor(ColorStateList.valueOf(Color.YELLOW)); + + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = + (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertEquals(ColorStateList.valueOf(Color.YELLOW), progressBar.getIndeterminateTintList()); + assertEquals( + ColorStateList.valueOf(Color.YELLOW), progressBar.getProgressBackgroundTintList()); } - - @Test - public void testPeekProgressBar() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - assertNull("PeekProgressBar should return null when stub not inflated yet", - mixin.peekProgressBar()); - - mixin.setShown(true); - assertNotNull("PeekProgressBar should be available after setting to shown", - mixin.peekProgressBar()); - } - - @Test - public void testSetColorBeforeSetShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setColor(ColorStateList.valueOf(Color.MAGENTA)); - - mixin.setShown(true); - - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertEquals(ColorStateList.valueOf(Color.MAGENTA), - progressBar.getIndeterminateTintList()); - assertEquals(ColorStateList.valueOf(Color.MAGENTA), - progressBar.getProgressBackgroundTintList()); - } - // this method is a no-op on versions < lollipop. Just check that it doesn't crash. - } - - @Test - public void testSetColorAfterSetShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setShown(true); - - mixin.setColor(ColorStateList.valueOf(Color.YELLOW)); - - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertEquals(ColorStateList.valueOf(Color.YELLOW), - progressBar.getIndeterminateTintList()); - assertEquals(ColorStateList.valueOf(Color.YELLOW), - progressBar.getProgressBackgroundTintList()); - } - // this method is a no-op on versions < lollipop. Just check that it doesn't crash. - } - - @Test - public void testDeterminateProgressBarNullTint() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setShown(true); - mixin.peekProgressBar().setIndeterminate(false); - - mixin.setColor(null); - - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertEquals(null, progressBar.getProgressBackgroundTintList()); - progressBar.draw(new Canvas()); - } - // setColor is a no-op on versions < lollipop. Just check that it doesn't crash. + // this method is a no-op on versions < lollipop. Just check that it doesn't crash. + } + + @Test + public void testDeterminateProgressBarNullTint() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setShown(true); + mixin.peekProgressBar().setIndeterminate(false); + + mixin.setColor(null); + + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = + (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertEquals(null, progressBar.getProgressBackgroundTintList()); + progressBar.draw(new Canvas()); } + // setColor is a no-op on versions < lollipop. Just check that it doesn't crash. + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/TemplateLayoutMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/TemplateLayoutMixinTest.java index 7cc934a..6adebc6 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/TemplateLayoutMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/TemplateLayoutMixinTest.java @@ -24,10 +24,8 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,35 +34,38 @@ import org.junit.runner.RunWith; @SmallTest public class TemplateLayoutMixinTest { - private TestTemplateLayout mLayout; + private TestTemplateLayout mLayout; - @Before - public void setUp() throws Exception { - mLayout = new TestTemplateLayout(InstrumentationRegistry.getContext()); - } + @Before + public void setUp() throws Exception { + mLayout = new TestTemplateLayout(InstrumentationRegistry.getContext()); + } - @Test - public void testGetMixin() { - final TestMixin mixin = mLayout.getMixin(TestMixin.class); - assertNotNull("TestMixin should not be null", mixin); - assertTrue("TestMixin should be an instance of TestMixinSubclass. " - + "Found " + mixin.getClass() + " instead.", - mixin instanceof TestMixinSubclass); + @Test + public void testGetMixin() { + final TestMixin mixin = mLayout.getMixin(TestMixin.class); + assertNotNull("TestMixin should not be null", mixin); + assertTrue( + "TestMixin should be an instance of TestMixinSubclass. " + + "Found " + + mixin.getClass() + + " instead.", + mixin instanceof TestMixinSubclass); - // Mixin must be retrieved using the interface it's registered with, not the concrete class, - // although they are often the same. - assertNull("TestMixinSubclass should be null", mLayout.getMixin(TestMixinSubclass.class)); - } + // Mixin must be retrieved using the interface it's registered with, not the concrete class, + // although they are often the same. + assertNull("TestMixinSubclass should be null", mLayout.getMixin(TestMixinSubclass.class)); + } - private static class TestTemplateLayout extends TemplateLayout { + private static class TestTemplateLayout extends TemplateLayout { - TestTemplateLayout(Context context) { - super(context, R.layout.test_template, R.id.suw_layout_content); - registerMixin(TestMixin.class, new TestMixinSubclass()); - } + TestTemplateLayout(Context context) { + super(context, R.layout.test_template, R.id.suw_layout_content); + registerMixin(TestMixin.class, new TestMixinSubclass()); } + } - private static class TestMixin implements Mixin {} + private static class TestMixin implements Mixin {} - private static class TestMixinSubclass extends TestMixin {} + private static class TestMixinSubclass extends TestMixin {} } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/BottomScrollViewTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/BottomScrollViewTest.java index 1a8eb21..4f9487c 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/BottomScrollViewTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/BottomScrollViewTest.java @@ -21,13 +21,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; +import android.view.View; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; - import com.android.setupwizardlib.view.BottomScrollView; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,101 +34,101 @@ import org.junit.runner.RunWith; @SmallTest public class BottomScrollViewTest { - private TestBottomScrollListener mListener; + private TestBottomScrollListener mListener; - @Before - public void setUp() throws Exception { - mListener = new TestBottomScrollListener(); - } + @Before + public void setUp() throws Exception { + mListener = new TestBottomScrollListener(); + } - @Test - public void testNoNeedScroll() { - createScrollView(20); - assertTrue("Scroll should not be required", mListener.mScrolledToBottom); - } + @Test + public void testNoNeedScroll() { + createScrollView(20); + assertTrue("Scroll should not be required", mListener.mScrolledToBottom); + } - @Test - public void testNeedScroll() { - createScrollView(110); - assertFalse("Scroll should be required", mListener.mScrolledToBottom); - } + @Test + public void testNeedScroll() { + createScrollView(110); + assertFalse("Scroll should be required", mListener.mScrolledToBottom); + } - @Test - public void testScrollToBottom() { - final BottomScrollView bottomScrollView = createScrollView(110); + @Test + public void testScrollToBottom() { + final BottomScrollView bottomScrollView = createScrollView(110); - assertFalse("Scroll should be required", mListener.mScrolledToBottom); + assertFalse("Scroll should be required", mListener.mScrolledToBottom); - bottomScrollView.scrollTo(0, 10); - assertTrue("Should already be scrolled to bottom", mListener.mScrolledToBottom); - } + bottomScrollView.scrollTo(0, 10); + assertTrue("Should already be scrolled to bottom", mListener.mScrolledToBottom); + } - @Test - public void testScrollThreshold() { - final BottomScrollView bottomScrollView = createScrollView(110); - assertEquals("Scroll threshold should be 10", 10, bottomScrollView.getScrollThreshold()); - } + @Test + public void testScrollThreshold() { + final BottomScrollView bottomScrollView = createScrollView(110); + assertEquals("Scroll threshold should be 10", 10, bottomScrollView.getScrollThreshold()); + } - private BottomScrollView createScrollView(final int childHeight) { - final Context context = InstrumentationRegistry.getContext(); - final BottomScrollView bottomScrollView = new TestBottomScrollView(context); - bottomScrollView.setBottomScrollListener(mListener); + private BottomScrollView createScrollView(final int childHeight) { + final Context context = InstrumentationRegistry.getContext(); + final BottomScrollView bottomScrollView = new TestBottomScrollView(context); + bottomScrollView.setBottomScrollListener(mListener); - final View child = new TestChildView(context, childHeight); + final View child = new TestChildView(context, childHeight); - child.measure(0, 0); // TestChildView's measured dimensions doesn't depend on the arguments - bottomScrollView.addView(child); - bottomScrollView.layout(0, 0, 100, 100); + child.measure(0, 0); // TestChildView's measured dimensions doesn't depend on the arguments + bottomScrollView.addView(child); + bottomScrollView.layout(0, 0, 100, 100); - return bottomScrollView; - } + return bottomScrollView; + } - private static class TestChildView extends View { + private static class TestChildView extends View { - private static final int WIDTH = 10; - private int mHeight; + private static final int WIDTH = 10; + private int mHeight; - TestChildView(Context context, int height) { - super(context); - mHeight = height; - } + TestChildView(Context context, int height) { + super(context); + mHeight = height; + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(WIDTH, mHeight); - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(WIDTH, mHeight); + } - public void setHeight(int height) { - mHeight = height; - } + public void setHeight(int height) { + mHeight = height; } + } - private static class TestBottomScrollView extends BottomScrollView { + private static class TestBottomScrollView extends BottomScrollView { - TestBottomScrollView(Context context) { - super(context); - } + TestBottomScrollView(Context context) { + super(context); + } - @Override - public boolean post(Runnable action) { - // Post all runnables synchronously so that tests can check the callbacks. - action.run(); - return true; - } + @Override + public boolean post(Runnable action) { + // Post all runnables synchronously so that tests can check the callbacks. + action.run(); + return true; } + } - private static class TestBottomScrollListener implements BottomScrollView.BottomScrollListener { + private static class TestBottomScrollListener implements BottomScrollView.BottomScrollListener { - boolean mScrolledToBottom = true; + boolean mScrolledToBottom = true; - @Override - public void onScrolledToBottom() { - mScrolledToBottom = true; - } + @Override + public void onScrolledToBottom() { + mScrolledToBottom = true; + } - @Override - public void onRequiresScroll() { - mScrolledToBottom = false; - } + @Override + public void onRequiresScroll() { + mScrolledToBottom = false; } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ButtonBarItemTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ButtonBarItemTest.java index 18c295e..aacffeb 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ButtonBarItemTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ButtonBarItemTest.java @@ -20,18 +20,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.items.ButtonBarItem; import com.android.setupwizardlib.items.ButtonItem; import com.android.setupwizardlib.items.Item; import com.android.setupwizardlib.items.ItemHierarchy; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,87 +38,92 @@ import org.junit.runner.RunWith; @SmallTest public class ButtonBarItemTest { - private ButtonItem mChild1; - private ButtonItem mChild2; - private ButtonItem mChild3; - - @Before - public void setUp() throws Exception { - mChild1 = new ButtonItem(); - mChild2 = new ButtonItem(); - mChild3 = new ButtonItem(); + private ButtonItem mChild1; + private ButtonItem mChild2; + private ButtonItem mChild3; + + @Before + public void setUp() throws Exception { + mChild1 = new ButtonItem(); + mChild2 = new ButtonItem(); + mChild3 = new ButtonItem(); + } + + @Test + public void testFindItemById() { + ButtonBarItem item = new ButtonBarItem(); + item.setId(888); + + mChild1.setId(123); + mChild2.setId(456); + mChild3.setId(789); + item.addChild(mChild1); + item.addChild(mChild2); + item.addChild(mChild3); + + assertEquals("Finding 123 should return child1", mChild1, item.findItemById(123)); + assertEquals("Finding 456 should return child2", mChild2, item.findItemById(456)); + assertEquals("Finding 789 should return child3", mChild3, item.findItemById(789)); + + assertEquals("Finding 888 should return ButtonBarItem itself", item, item.findItemById(888)); + + assertNull("Finding 999 should return null", item.findItemById(999)); + } + + @Test + public void testBindEmpty() { + ButtonBarItem item = new ButtonBarItem(); + final ViewGroup layout = createLayout(); + item.onBindView(layout); + + assertEquals( + "Binding empty ButtonBar should not create any children", 0, layout.getChildCount()); + } + + @Test + public void testBind() { + ButtonBarItem item = new ButtonBarItem(); + + item.addChild(mChild1); + mChild1.setText("child1"); + item.addChild(mChild2); + mChild2.setText("child2"); + item.addChild(mChild3); + mChild3.setText("child3"); + + final ViewGroup layout = createLayout(); + item.onBindView(layout); + + assertEquals("Binding ButtonBar should create 3 children", 3, layout.getChildCount()); + assertEquals( + "First button should have text \"child1\"", + "child1", + ((Button) layout.getChildAt(0)).getText()); + assertEquals( + "Second button should have text \"child2\"", + "child2", + ((Button) layout.getChildAt(1)).getText()); + assertEquals( + "Third button should have text \"child3\"", + "child3", + ((Button) layout.getChildAt(2)).getText()); + } + + @Test + public void testAddInvalidChild() { + ButtonBarItem item = new ButtonBarItem(); + + ItemHierarchy invalidChild = new Item(); + + try { + item.addChild(invalidChild); + fail("Adding non ButtonItem to ButtonBarItem should throw exception"); + } catch (UnsupportedOperationException e) { + // pass } + } - @Test - public void testFindItemById() { - ButtonBarItem item = new ButtonBarItem(); - item.setId(888); - - mChild1.setId(123); - mChild2.setId(456); - mChild3.setId(789); - item.addChild(mChild1); - item.addChild(mChild2); - item.addChild(mChild3); - - assertEquals("Finding 123 should return child1", mChild1, item.findItemById(123)); - assertEquals("Finding 456 should return child2", mChild2, item.findItemById(456)); - assertEquals("Finding 789 should return child3", mChild3, item.findItemById(789)); - - assertEquals("Finding 888 should return ButtonBarItem itself", item, - item.findItemById(888)); - - assertNull("Finding 999 should return null", item.findItemById(999)); - } - - @Test - public void testBindEmpty() { - ButtonBarItem item = new ButtonBarItem(); - final ViewGroup layout = createLayout(); - item.onBindView(layout); - - assertEquals("Binding empty ButtonBar should not create any children", 0, - layout.getChildCount()); - } - - @Test - public void testBind() { - ButtonBarItem item = new ButtonBarItem(); - - item.addChild(mChild1); - mChild1.setText("child1"); - item.addChild(mChild2); - mChild2.setText("child2"); - item.addChild(mChild3); - mChild3.setText("child3"); - - final ViewGroup layout = createLayout(); - item.onBindView(layout); - - assertEquals("Binding ButtonBar should create 3 children", 3, layout.getChildCount()); - assertEquals("First button should have text \"child1\"", "child1", - ((Button) layout.getChildAt(0)).getText()); - assertEquals("Second button should have text \"child2\"", "child2", - ((Button) layout.getChildAt(1)).getText()); - assertEquals("Third button should have text \"child3\"", "child3", - ((Button) layout.getChildAt(2)).getText()); - } - - @Test - public void testAddInvalidChild() { - ButtonBarItem item = new ButtonBarItem(); - - ItemHierarchy invalidChild = new Item(); - - try { - item.addChild(invalidChild); - fail("Adding non ButtonItem to ButtonBarItem should throw exception"); - } catch (UnsupportedOperationException e) { - // pass - } - } - - private ViewGroup createLayout() { - return new LinearLayout(InstrumentationRegistry.getContext()); - } + private ViewGroup createLayout() { + return new LinearLayout(InstrumentationRegistry.getContext()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/DrawableLayoutDirectionHelperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/DrawableLayoutDirectionHelperTest.java index 95245b0..1445660 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/DrawableLayoutDirectionHelperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/DrawableLayoutDirectionHelperTest.java @@ -28,117 +28,124 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; +import android.view.View; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; - import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper; - +import java.util.Locale; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Locale; - @RunWith(AndroidJUnit4.class) @SmallTest public class DrawableLayoutDirectionHelperTest { - @Test - public void testCreateRelativeInsetDrawableLtr() { - final Drawable drawable = new ColorDrawable(Color.RED); - @SuppressLint("InlinedApi") // Testing with inlined constant is OK here - final InsetDrawable insetDrawable = - DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable, - 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, - View.LAYOUT_DIRECTION_LTR); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - assertSame("Drawable from getDrawable() should be same as passed in", drawable, - insetDrawable.getDrawable()); - } - Rect outRect = new Rect(); - insetDrawable.getPadding(outRect); - assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), - outRect); + @Test + public void testCreateRelativeInsetDrawableLtr() { + final Drawable drawable = new ColorDrawable(Color.RED); + @SuppressLint("InlinedApi") // Testing with inlined constant is OK here + final InsetDrawable insetDrawable = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + drawable, + 1 /* start */, + 2 /* top */, + 3 /* end */, + 4 /* bottom */, + View.LAYOUT_DIRECTION_LTR); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertSame( + "Drawable from getDrawable() should be same as passed in", + drawable, + insetDrawable.getDrawable()); } + Rect outRect = new Rect(); + insetDrawable.getPadding(outRect); + assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), outRect); + } - @Test - public void testCreateRelativeInsetDrawableRtl() { - final Drawable drawable = new ColorDrawable(Color.RED); - @SuppressLint("InlinedApi") // Testing with inlined constant is OK here - final InsetDrawable insetDrawable = - DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable, - 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, - View.LAYOUT_DIRECTION_RTL); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - assertSame("Drawable from getDrawable() should be same as passed in", drawable, - insetDrawable.getDrawable()); - } - Rect outRect = new Rect(); - insetDrawable.getPadding(outRect); - assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), - outRect); + @Test + public void testCreateRelativeInsetDrawableRtl() { + final Drawable drawable = new ColorDrawable(Color.RED); + @SuppressLint("InlinedApi") // Testing with inlined constant is OK here + final InsetDrawable insetDrawable = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + drawable, + 1 /* start */, + 2 /* top */, + 3 /* end */, + 4 /* bottom */, + View.LAYOUT_DIRECTION_RTL); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertSame( + "Drawable from getDrawable() should be same as passed in", + drawable, + insetDrawable.getDrawable()); } + Rect outRect = new Rect(); + insetDrawable.getPadding(outRect); + assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), outRect); + } - @Test - public void testCreateRelativeInsetDrawableViewRtl() { - final Drawable drawable = new ColorDrawable(Color.RED); - final View view = new ForceRtlView(InstrumentationRegistry.getContext()); - final InsetDrawable insetDrawable = - DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable, - 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, view); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - assertSame("Drawable from getDrawable() should be same as passed in", drawable, - insetDrawable.getDrawable()); - } - Rect outRect = new Rect(); - insetDrawable.getPadding(outRect); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), - outRect); - } else { - assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), - outRect); - } + @Test + public void testCreateRelativeInsetDrawableViewRtl() { + final Drawable drawable = new ColorDrawable(Color.RED); + final View view = new ForceRtlView(InstrumentationRegistry.getContext()); + final InsetDrawable insetDrawable = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + drawable, 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, view); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertSame( + "Drawable from getDrawable() should be same as passed in", + drawable, + insetDrawable.getDrawable()); + } + Rect outRect = new Rect(); + insetDrawable.getPadding(outRect); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), outRect); + } else { + assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), outRect); } + } - @Test - public void testCreateRelativeInsetDrawableContextRtl() { - Context context = InstrumentationRegistry.getContext(); - final Drawable drawable = new ColorDrawable(Color.RED); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - final Configuration config = new Configuration(); - config.setLayoutDirection(new Locale("fa", "IR")); - context = context.createConfigurationContext(config); - } - final InsetDrawable insetDrawable = - DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable, - 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - assertSame("Drawable from getDrawable() should be same as passed in", drawable, - insetDrawable.getDrawable()); - } - Rect outRect = new Rect(); - insetDrawable.getPadding(outRect); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), - outRect); - } else { - assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), - outRect); - } + @Test + public void testCreateRelativeInsetDrawableContextRtl() { + Context context = InstrumentationRegistry.getContext(); + final Drawable drawable = new ColorDrawable(Color.RED); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + final Configuration config = new Configuration(); + config.setLayoutDirection(new Locale("fa", "IR")); + context = context.createConfigurationContext(config); + } + final InsetDrawable insetDrawable = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + drawable, 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, context); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertSame( + "Drawable from getDrawable() should be same as passed in", + drawable, + insetDrawable.getDrawable()); } + Rect outRect = new Rect(); + insetDrawable.getPadding(outRect); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), outRect); + } else { + assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), outRect); + } + } - private static class ForceRtlView extends View { + private static class ForceRtlView extends View { - ForceRtlView(Context context) { - super(context); - } + ForceRtlView(Context context) { + super(context); + } - @Override - @SuppressLint("InlinedApi") // Testing with inlined constant is OK here - public int getLayoutDirection() { - return View.LAYOUT_DIRECTION_RTL; - } + @Override + @SuppressLint("InlinedApi") // Testing with inlined constant is OK here + public int getLayoutDirection() { + return View.LAYOUT_DIRECTION_RTL; } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifLayoutTest.java index e12b31d..f8aae5a 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifLayoutTest.java @@ -26,9 +26,6 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; @@ -36,9 +33,10 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.GlifLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,94 +45,99 @@ import org.junit.runner.RunWith; @SmallTest public class GlifLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + GlifLayout layout = (GlifLayout) inflater.inflate(R.layout.test_glif_layout, null); + assertDefaultTemplateInflated(layout); + View content = layout.findViewById(R.id.test_content); + assertTrue("@id/test_content should be a TextView", content instanceof TextView); + } + + @Test + public void testPrimaryColorFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + GlifLayout layout = + (GlifLayout) inflater.inflate(R.layout.test_glif_layout_primary_color, null); + assertDefaultTemplateInflated(layout); + + assertEquals(ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); + } + + @Test + public void testSetProgressBarShownInvalid() { + GlifLayout layout = new GlifLayout(mContext, R.layout.test_template); + layout.setProgressBarShown(true); + // This is a no-op because there is no progress bar stub + } + + @Test + public void testGlifTheme() { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + final GlifLayout glifLayout = new GlifLayout(mContext); + + if (VERSION.SDK_INT >= VERSION_CODES.M) { + // Scroll indicators are only available on versions >= M + assertEquals(View.SCROLL_INDICATOR_BOTTOM, glifLayout.getScrollView().getScrollIndicators()); } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - GlifLayout layout = (GlifLayout) inflater.inflate(R.layout.test_glif_layout, null); - assertDefaultTemplateInflated(layout); - View content = layout.findViewById(R.id.test_content); - assertTrue("@id/test_content should be a TextView", content instanceof TextView); - } - - @Test - public void testPrimaryColorFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - GlifLayout layout = - (GlifLayout) inflater.inflate(R.layout.test_glif_layout_primary_color, null); - assertDefaultTemplateInflated(layout); - - assertEquals(ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); + } + + @Test + public void testGlifV2Theme() { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlifV2_Light); + final GlifLayout glifLayout = new GlifLayout(mContext); + final TextView titleView = (TextView) glifLayout.findManagedViewById(R.id.suw_layout_title); + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + assertEquals(View.TEXT_ALIGNMENT_GRAVITY, titleView.getTextAlignment()); } - - @Test - public void testSetProgressBarShownInvalid() { - GlifLayout layout = new GlifLayout(mContext, R.layout.test_template); - layout.setProgressBarShown(true); - // This is a no-op because there is no progress bar stub - } - - @Test - public void testGlifTheme() { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); - final GlifLayout glifLayout = new GlifLayout(mContext); - - if (VERSION.SDK_INT >= VERSION_CODES.M) { - // Scroll indicators are only available on versions >= M - assertEquals(View.SCROLL_INDICATOR_BOTTOM, - glifLayout.getScrollView().getScrollIndicators()); - } + assertEquals( + "Title text should be center aligned on GLIF v2 theme", + Gravity.CENTER_HORIZONTAL, + titleView.getGravity() & Gravity.CENTER_HORIZONTAL); + + if (VERSION.SDK_INT >= VERSION_CODES.N) { + // LinearLayout.getGravity is only available on versions >= N + final View iconView = glifLayout.findManagedViewById(R.id.suw_layout_icon); + final LinearLayout parent = (LinearLayout) iconView.getParent(); + assertEquals( + "Icon should be center aligned on GLIF v2 theme", + Gravity.CENTER_HORIZONTAL, + parent.getGravity() & Gravity.CENTER_HORIZONTAL); } - @Test - public void testGlifV2Theme() { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlifV2_Light); - final GlifLayout glifLayout = new GlifLayout(mContext); - final TextView titleView = (TextView) glifLayout.findManagedViewById(R.id.suw_layout_title); - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - assertEquals(View.TEXT_ALIGNMENT_GRAVITY, titleView.getTextAlignment()); - } - assertEquals("Title text should be center aligned on GLIF v2 theme", - Gravity.CENTER_HORIZONTAL, titleView.getGravity() & Gravity.CENTER_HORIZONTAL); - - if (VERSION.SDK_INT >= VERSION_CODES.N) { - // LinearLayout.getGravity is only available on versions >= N - final View iconView = glifLayout.findManagedViewById(R.id.suw_layout_icon); - final LinearLayout parent = (LinearLayout) iconView.getParent(); - assertEquals("Icon should be center aligned on GLIF v2 theme", - Gravity.CENTER_HORIZONTAL, parent.getGravity() & Gravity.CENTER_HORIZONTAL); - } - - assertEquals("Status bar color should be white in GLIF v2 theme", - "ffffffff", - Integer.toHexString(glifLayout.getBackgroundBaseColor().getDefaultColor())); - assertFalse("GLIF v2 theme shuold not have patterned background", - glifLayout.isBackgroundPatterned()); - - if (VERSION.SDK_INT >= VERSION_CODES.M) { - // Scroll indicators are only available on versions >= M - assertEquals(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM, - glifLayout.getScrollView().getScrollIndicators()); - } + assertEquals( + "Status bar color should be white in GLIF v2 theme", + "ffffffff", + Integer.toHexString(glifLayout.getBackgroundBaseColor().getDefaultColor())); + assertFalse( + "GLIF v2 theme shuold not have patterned background", glifLayout.isBackgroundPatterned()); + + if (VERSION.SDK_INT >= VERSION_CODES.M) { + // Scroll indicators are only available on versions >= M + assertEquals( + View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM, + glifLayout.getScrollView().getScrollIndicators()); } + } - private void assertDefaultTemplateInflated(GlifLayout layout) { - View title = layout.findViewById(R.id.suw_layout_title); - assertNotNull("@id/suw_layout_title should not be null", title); + private void assertDefaultTemplateInflated(GlifLayout layout) { + View title = layout.findViewById(R.id.suw_layout_title); + assertNotNull("@id/suw_layout_title should not be null", title); - View icon = layout.findViewById(R.id.suw_layout_icon); - assertNotNull("@id/suw_layout_icon should not be null", icon); + View icon = layout.findViewById(R.id.suw_layout_icon); + assertNotNull("@id/suw_layout_icon should not be null", icon); - View scrollView = layout.findViewById(R.id.suw_scroll_view); - assertTrue("@id/suw_scroll_view should be a ScrollView", scrollView instanceof ScrollView); - } + View scrollView = layout.findViewById(R.id.suw_scroll_view); + assertTrue("@id/suw_scroll_view should be a ScrollView", scrollView instanceof ScrollView); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifListLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifListLayoutTest.java index c2e932c..0665bfe 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifListLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifListLayoutTest.java @@ -26,9 +26,6 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -36,9 +33,10 @@ import android.widget.ArrayAdapter; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.GlifListLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,102 +45,100 @@ import org.junit.runner.RunWith; @SmallTest public class GlifListLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + } + + @Test + public void testDefaultTemplate() { + GlifListLayout layout = new GlifListLayout(mContext); + assertListTemplateInflated(layout); + } + + @Test + public void testAddView() { + GlifListLayout layout = new GlifListLayout(mContext); + TextView tv = new TextView(mContext); + try { + layout.addView(tv); + fail("Adding view to ListLayout should throw"); + } catch (UnsupportedOperationException e) { + // Expected exception } - - @Test - public void testDefaultTemplate() { - GlifListLayout layout = new GlifListLayout(mContext); - assertListTemplateInflated(layout); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + GlifListLayout layout = (GlifListLayout) inflater.inflate(R.layout.test_glif_list_layout, null); + assertListTemplateInflated(layout); + } + + @Test + public void testGetListView() { + GlifListLayout layout = new GlifListLayout(mContext); + assertListTemplateInflated(layout); + assertNotNull("getListView should not be null", layout.getListView()); + } + + @Test + public void testAdapter() { + GlifListLayout layout = new GlifListLayout(mContext); + assertListTemplateInflated(layout); + + final ArrayAdapter<String> adapter = + new ArrayAdapter<>(mContext, android.R.layout.simple_list_item_1); + adapter.add("Abracadabra"); + layout.setAdapter(adapter); + + final ListAdapter gotAdapter = layout.getAdapter(); + // Note: the wrapped adapter should be returned directly, not the HeaderViewListAdapter. + assertSame("Adapter got from GlifListLayout should be same as set", adapter, gotAdapter); + } + + @Test + public void testDividerInsetLegacy() { + GlifListLayout layout = new GlifListLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertListTemplateInflated(layout); - @Test - public void testAddView() { - GlifListLayout layout = new GlifListLayout(mContext); - TextView tv = new TextView(mContext); - try { - layout.addView(tv); - fail("Adding view to ListLayout should throw"); - } catch (UnsupportedOperationException e) { - // Expected exception - } - } + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - GlifListLayout layout = (GlifListLayout) - inflater.inflate(R.layout.test_glif_list_layout, null); - assertListTemplateInflated(layout); - } + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - @Test - public void testGetListView() { - GlifListLayout layout = new GlifListLayout(mContext); - assertListTemplateInflated(layout); - assertNotNull("getListView should not be null", layout.getListView()); + @Test + public void testDividerInsets() { + GlifListLayout layout = new GlifListLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertListTemplateInflated(layout); - @Test - public void testAdapter() { - GlifListLayout layout = new GlifListLayout(mContext); - assertListTemplateInflated(layout); + layout.setDividerInsets(10, 15); + assertEquals("Divider inset should be 10", 10, layout.getDividerInsetStart()); + assertEquals("Divider inset should be 15", 15, layout.getDividerInsetEnd()); - final ArrayAdapter<String> adapter = - new ArrayAdapter<>(mContext, android.R.layout.simple_list_item_1); - adapter.add("Abracadabra"); - layout.setAdapter(adapter); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - final ListAdapter gotAdapter = layout.getAdapter(); - // Note: the wrapped adapter should be returned directly, not the HeaderViewListAdapter. - assertSame("Adapter got from GlifListLayout should be same as set", - adapter, gotAdapter); - } - - @Test - public void testDividerInsetLegacy() { - GlifListLayout layout = new GlifListLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertListTemplateInflated(layout); - - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testDividerInsets() { - GlifListLayout layout = new GlifListLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertListTemplateInflated(layout); + private void assertListTemplateInflated(GlifListLayout layout) { + View title = layout.findViewById(R.id.suw_layout_title); + assertNotNull("@id/suw_layout_title should not be null", title); - layout.setDividerInsets(10, 15); - assertEquals("Divider inset should be 10", 10, layout.getDividerInsetStart()); - assertEquals("Divider inset should be 15", 15, layout.getDividerInsetEnd()); + View icon = layout.findViewById(R.id.suw_layout_icon); + assertNotNull("@id/suw_layout_icon should not be null", icon); - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - private void assertListTemplateInflated(GlifListLayout layout) { - View title = layout.findViewById(R.id.suw_layout_title); - assertNotNull("@id/suw_layout_title should not be null", title); - - View icon = layout.findViewById(R.id.suw_layout_icon); - assertNotNull("@id/suw_layout_icon should not be null", icon); - - View listView = layout.findViewById(android.R.id.list); - assertTrue("@android:id/list should be a ListView", listView instanceof ListView); - } + View listView = layout.findViewById(android.R.id.list); + assertTrue("@android:id/list should be a ListView", listView instanceof ListView); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java index 37ac41a..1783d4e 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java @@ -25,14 +25,11 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.os.Debug; +import android.util.Log; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.Log; - import com.android.setupwizardlib.GlifPatternDrawable; - import junit.framework.AssertionFailedError; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,141 +38,144 @@ import org.junit.runner.RunWith; @SmallTest public class GlifPatternDrawableTest { - private static final String TAG = "GlifPatternDrawableTest"; + private static final String TAG = "GlifPatternDrawableTest"; - @Before - public void setUp() throws Exception { - GlifPatternDrawable.invalidatePattern(); - } + @Before + public void setUp() throws Exception { + GlifPatternDrawable.invalidatePattern(); + } - @Test - public void testDraw() { - final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); + @Test + public void testDraw() { + final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 768); - drawable.draw(canvas); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 768); + drawable.draw(canvas); - assertSameColor("Top left pixel should be #e61a1a", 0xffe61a1a, bitmap.getPixel(0, 0)); - assertSameColor("Center pixel should be #d90d0d", 0xffd90d0d, bitmap.getPixel(683, 384)); - assertSameColor("Bottom right pixel should be #d40808", 0xffd40808, - bitmap.getPixel(1365, 767)); - } + assertSameColor("Top left pixel should be #e61a1a", 0xffe61a1a, bitmap.getPixel(0, 0)); + assertSameColor("Center pixel should be #d90d0d", 0xffd90d0d, bitmap.getPixel(683, 384)); + assertSameColor("Bottom right pixel should be #d40808", 0xffd40808, bitmap.getPixel(1365, 767)); + } - @Test - public void testDrawTwice() { - // Test that the second time the drawable is drawn is also correct, to make sure caching is - // done correctly. + @Test + public void testDrawTwice() { + // Test that the second time the drawable is drawn is also correct, to make sure caching is + // done correctly. - final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); + final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 768); - drawable.draw(canvas); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 768); + drawable.draw(canvas); - Paint paint = new Paint(); - paint.setColor(Color.WHITE); - canvas.drawRect(0, 0, 1366, 768, paint); // Erase the entire canvas + Paint paint = new Paint(); + paint.setColor(Color.WHITE); + canvas.drawRect(0, 0, 1366, 768, paint); // Erase the entire canvas - drawable.draw(canvas); + drawable.draw(canvas); - assertSameColor("Top left pixel should be #e61a1a", 0xffe61a1a, bitmap.getPixel(0, 0)); - assertSameColor("Center pixel should be #d90d0d", 0xffd90d0d, bitmap.getPixel(683, 384)); - assertSameColor("Bottom right pixel should be #d40808", 0xffd40808, - bitmap.getPixel(1365, 767)); - } + assertSameColor("Top left pixel should be #e61a1a", 0xffe61a1a, bitmap.getPixel(0, 0)); + assertSameColor("Center pixel should be #d90d0d", 0xffd90d0d, bitmap.getPixel(683, 384)); + assertSameColor("Bottom right pixel should be #d40808", 0xffd40808, bitmap.getPixel(1365, 767)); + } - @Test - public void testScaleToCanvasSquare() { - final Canvas canvas = new Canvas(); - Matrix expected = new Matrix(canvas.getMatrix()); + @Test + public void testScaleToCanvasSquare() { + final Canvas canvas = new Canvas(); + Matrix expected = new Matrix(canvas.getMatrix()); - Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); + Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 683, 384); // half each side of the view box - drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 683, 384); // half each side of the view box + drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); - expected.postScale(0.5f, 0.5f); + expected.postScale(0.5f, 0.5f); - assertEquals("Matrices should match", expected, canvas.getMatrix()); - } + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } - @Test - public void testScaleToCanvasTall() { - final Canvas canvas = new Canvas(); - final Matrix expected = new Matrix(canvas.getMatrix()); + @Test + public void testScaleToCanvasTall() { + final Canvas canvas = new Canvas(); + final Matrix expected = new Matrix(canvas.getMatrix()); - Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); + Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 683, 768); // half the width only - drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 683, 768); // half the width only + drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); - expected.postScale(1f, 1f); - expected.postTranslate(-99.718f, 0f); + expected.postScale(1f, 1f); + expected.postTranslate(-99.718f, 0f); - assertEquals("Matrices should match", expected, canvas.getMatrix()); - } + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } - @Test - public void testScaleToCanvasWide() { - final Canvas canvas = new Canvas(); - final Matrix expected = new Matrix(canvas.getMatrix()); + @Test + public void testScaleToCanvasWide() { + final Canvas canvas = new Canvas(); + final Matrix expected = new Matrix(canvas.getMatrix()); - Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); + Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 384); // half the height only - drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 384); // half the height only + drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); - expected.postScale(1f, 1f); - expected.postTranslate(0f, -87.552f); + expected.postScale(1f, 1f); + expected.postTranslate(0f, -87.552f); - assertEquals("Matrices should match", expected, canvas.getMatrix()); - } + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } - @Test - public void testScaleToCanvasMaxSize() { - final Canvas canvas = new Canvas(); - final Matrix expected = new Matrix(canvas.getMatrix()); + @Test + public void testScaleToCanvasMaxSize() { + final Canvas canvas = new Canvas(); + final Matrix expected = new Matrix(canvas.getMatrix()); - Bitmap mockBitmapCache = Bitmap.createBitmap(2049, 1152, Bitmap.Config.ALPHA_8); + Bitmap mockBitmapCache = Bitmap.createBitmap(2049, 1152, Bitmap.Config.ALPHA_8); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 768); // original viewbox size - drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 768); // original viewbox size + drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); - expected.postScale(1 / 1.5f, 1 / 1.5f); - expected.postTranslate(0f, 0f); + expected.postScale(1 / 1.5f, 1 / 1.5f); + expected.postTranslate(0f, 0f); - assertEquals("Matrices should match", expected, canvas.getMatrix()); - } + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } - @Test - public void testMemoryAllocation() { - Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo(); - Debug.getMemoryInfo(memoryInfo); - final long memoryBefore = memoryInfo.getTotalPss(); // Get memory usage in KB + @Test + public void testMemoryAllocation() { + Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo(); + Debug.getMemoryInfo(memoryInfo); + final long memoryBefore = memoryInfo.getTotalPss(); // Get memory usage in KB - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 768); - drawable.createBitmapCache(2049, 1152); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 768); + drawable.createBitmapCache(2049, 1152); - Debug.getMemoryInfo(memoryInfo); - final long memoryAfter = memoryInfo.getTotalPss(); - Log.i(TAG, "Memory allocated for bitmap cache: " + (memoryAfter - memoryBefore)); - assertTrue("Memory allocation should not exceed 5MB", memoryAfter < memoryBefore + 5000); - } + Debug.getMemoryInfo(memoryInfo); + final long memoryAfter = memoryInfo.getTotalPss(); + Log.i(TAG, "Memory allocated for bitmap cache: " + (memoryAfter - memoryBefore)); + assertTrue("Memory allocation should not exceed 5MB", memoryAfter < memoryBefore + 5000); + } - private void assertSameColor(String message, int expected, int actual) { - try { - assertEquals(expected, actual); - } catch (AssertionFailedError e) { - throw new AssertionFailedError(message + " expected <#" + Integer.toHexString(expected) - + "> but found <#" + Integer.toHexString(actual) + "> instead"); - } + private void assertSameColor(String message, int expected, int actual) { + try { + assertEquals(expected, actual); + } catch (AssertionFailedError e) { + throw new AssertionFailedError( + message + + " expected <#" + + Integer.toHexString(expected) + + "> but found <#" + + Integer.toHexString(actual) + + "> instead"); } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/IllustrationTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/IllustrationTest.java index a4b6f27..253893e 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/IllustrationTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/IllustrationTest.java @@ -23,13 +23,11 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.view.View; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; - import com.android.setupwizardlib.view.Illustration; - import org.junit.Test; import org.junit.runner.RunWith; @@ -37,28 +35,29 @@ import org.junit.runner.RunWith; @SmallTest public class IllustrationTest { - @Test - public void testWillDraw() { - final Illustration illustration = new Illustration(InstrumentationRegistry.getContext()); - assertFalse("The illustration needs to be drawn", illustration.willNotDraw()); - } - - @Test - public void testAspectRatio() { - final Context context = InstrumentationRegistry.getContext(); - // Force the context to be xhdpi - context.getResources().getDisplayMetrics().density = 2.0f; - - final Illustration illustration = new Illustration(context); - illustration.setAspectRatio(3.0f); - final Drawable backgroundDrawable = new ColorDrawable(Color.RED); - final Drawable illustrationDrawable = new ColorDrawable(Color.BLUE); - illustration.setBackgroundDrawable(backgroundDrawable); - illustration.setIllustration(illustrationDrawable); - - illustration.measure(View.MeasureSpec.makeMeasureSpec(300, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); - // (300px / 3) round down to nearest mod (8dp = 16px) = 96px - assertEquals("Top padding should be 96", 96, illustration.getPaddingTop()); - } + @Test + public void testWillDraw() { + final Illustration illustration = new Illustration(InstrumentationRegistry.getContext()); + assertFalse("The illustration needs to be drawn", illustration.willNotDraw()); + } + + @Test + public void testAspectRatio() { + final Context context = InstrumentationRegistry.getContext(); + // Force the context to be xhdpi + context.getResources().getDisplayMetrics().density = 2.0f; + + final Illustration illustration = new Illustration(context); + illustration.setAspectRatio(3.0f); + final Drawable backgroundDrawable = new ColorDrawable(Color.RED); + final Drawable illustrationDrawable = new ColorDrawable(Color.BLUE); + illustration.setBackgroundDrawable(backgroundDrawable); + illustration.setIllustration(illustrationDrawable); + + illustration.measure( + View.MeasureSpec.makeMeasureSpec(300, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + // (300px / 3) round down to nearest mod (8dp = 16px) = 96px + assertEquals("Top padding should be 96", 96, illustration.getPaddingTop()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemAdapterTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemAdapterTest.java index e5875e4..63180dc 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemAdapterTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemAdapterTest.java @@ -24,78 +24,74 @@ import static org.mockito.Mockito.mock; import android.database.DataSetObserver; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.items.Item; import com.android.setupwizardlib.items.ItemAdapter; import com.android.setupwizardlib.items.ItemGroup; import com.android.setupwizardlib.items.ItemHierarchy; - +import java.util.Arrays; +import java.util.HashSet; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; -import java.util.Arrays; -import java.util.HashSet; - @RunWith(AndroidJUnit4.class) @SmallTest public class ItemAdapterTest { - private Item[] mItems = new Item[5]; - private ItemGroup mItemGroup = new ItemGroup(); - - @Before - public void setUp() throws Exception { - for (int i = 0; i < 5; i++) { - Item item = new Item(); - item.setTitle("TestTitle" + i); - item.setId(i); - item.setLayoutResource(((i % 3) + 1) * 10); - mItems[i] = item; - mItemGroup.addChild(item); - } - } - - @Test - public void testAdapter() { - ItemAdapter adapter = new ItemAdapter(mItemGroup); - assertEquals("Adapter should have 5 items", 5, adapter.getCount()); - assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); - assertEquals("ID should be same as position", 2, adapter.getItemId(2)); - - // Each test item has its own layout resource, and therefore its own view type - assertEquals("Should have 3 different view types", 3, adapter.getViewTypeCount()); - HashSet<Integer> viewTypes = new HashSet<>(3); - viewTypes.add(adapter.getItemViewType(0)); - viewTypes.add(adapter.getItemViewType(1)); - viewTypes.add(adapter.getItemViewType(2)); - - assertEquals("View types should be 0, 1, 2", - new HashSet<>(Arrays.asList(0, 1, 2)), viewTypes); - } - - @Test - public void testGetRootItemHierarchy() { - ItemAdapter adapter = new ItemAdapter(mItemGroup); - ItemHierarchy root = adapter.getRootItemHierarchy(); - assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); - } - - @Test - public void testAdapterNotifications() { - ItemAdapter adapter = new ItemAdapter(mItemGroup); - final DataSetObserver observer = mock(DataSetObserver.class); - adapter.registerDataSetObserver(observer); - final InOrder inOrder = inOrder(observer); - - mItems[0].setTitle("Child 1"); - inOrder.verify(observer).onChanged(); - - mItemGroup.removeChild(mItems[1]); - inOrder.verify(observer).onChanged(); - - mItemGroup.addChild(mItems[1]); - inOrder.verify(observer).onChanged(); + private Item[] mItems = new Item[5]; + private ItemGroup mItemGroup = new ItemGroup(); + + @Before + public void setUp() throws Exception { + for (int i = 0; i < 5; i++) { + Item item = new Item(); + item.setTitle("TestTitle" + i); + item.setId(i); + item.setLayoutResource(((i % 3) + 1) * 10); + mItems[i] = item; + mItemGroup.addChild(item); } + } + + @Test + public void testAdapter() { + ItemAdapter adapter = new ItemAdapter(mItemGroup); + assertEquals("Adapter should have 5 items", 5, adapter.getCount()); + assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); + assertEquals("ID should be same as position", 2, adapter.getItemId(2)); + + // Each test item has its own layout resource, and therefore its own view type + assertEquals("Should have 3 different view types", 3, adapter.getViewTypeCount()); + HashSet<Integer> viewTypes = new HashSet<>(3); + viewTypes.add(adapter.getItemViewType(0)); + viewTypes.add(adapter.getItemViewType(1)); + viewTypes.add(adapter.getItemViewType(2)); + + assertEquals("View types should be 0, 1, 2", new HashSet<>(Arrays.asList(0, 1, 2)), viewTypes); + } + + @Test + public void testGetRootItemHierarchy() { + ItemAdapter adapter = new ItemAdapter(mItemGroup); + ItemHierarchy root = adapter.getRootItemHierarchy(); + assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); + } + + @Test + public void testAdapterNotifications() { + ItemAdapter adapter = new ItemAdapter(mItemGroup); + final DataSetObserver observer = mock(DataSetObserver.class); + adapter.registerDataSetObserver(observer); + final InOrder inOrder = inOrder(observer); + + mItems[0].setTitle("Child 1"); + inOrder.verify(observer).onChanged(); + + mItemGroup.removeChild(mItems[1]); + inOrder.verify(observer).onChanged(); + + mItemGroup.addChild(mItems[1]); + inOrder.verify(observer).onChanged(); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemInflaterTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemInflaterTest.java index 20fd2cc..9e96bae 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemInflaterTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemInflaterTest.java @@ -22,12 +22,10 @@ import static org.junit.Assert.assertTrue; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.items.Item; import com.android.setupwizardlib.items.ItemGroup; import com.android.setupwizardlib.items.ItemHierarchy; import com.android.setupwizardlib.items.ItemInflater; - import org.junit.Test; import org.junit.runner.RunWith; @@ -35,25 +33,26 @@ import org.junit.runner.RunWith; @SmallTest public class ItemInflaterTest { - @Test - public void testDefaultPackage() { - ItemInflater inflater = new ItemInflater(InstrumentationRegistry.getContext()); - assertEquals("Default package should be the one containing Item class", - "com.android.setupwizardlib.items.", inflater.getDefaultPackage()); - } - - @Test - public void testInflate() { - ItemInflater inflater = new ItemInflater(InstrumentationRegistry.getContext()); - ItemHierarchy item = inflater.inflate(R.xml.test_items); - assertTrue("Inflated item should be ItemGroup", item instanceof ItemGroup); - ItemGroup itemGroup = (ItemGroup) item; - - Item child0 = (Item) itemGroup.getItemAt(0); - Item child1 = (Item) itemGroup.getItemAt(1); - assertEquals("Title of first child should be Title1", "Title1", child0.getTitle()); - assertEquals("ID of second child should be test_item_2", R.id.test_item_2, child1.getId()); - assertEquals("Summary of second child should be Summary2", "Summary2", - child1.getSummary()); - } + @Test + public void testDefaultPackage() { + ItemInflater inflater = new ItemInflater(InstrumentationRegistry.getContext()); + assertEquals( + "Default package should be the one containing Item class", + "com.android.setupwizardlib.items.", + inflater.getDefaultPackage()); + } + + @Test + public void testInflate() { + ItemInflater inflater = new ItemInflater(InstrumentationRegistry.getContext()); + ItemHierarchy item = inflater.inflate(R.xml.test_items); + assertTrue("Inflated item should be ItemGroup", item instanceof ItemGroup); + ItemGroup itemGroup = (ItemGroup) item; + + Item child0 = (Item) itemGroup.getItemAt(0); + Item child1 = (Item) itemGroup.getItemAt(1); + assertEquals("Title of first child should be Title1", "Title1", child0.getTitle()); + assertEquals("ID of second child should be test_item_2", R.id.test_item_2, child1.getId()); + assertEquals("Summary of second child should be Summary2", "Summary2", child1.getSummary()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemLayoutTest.java index 85876b4..dbf71b2 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemLayoutTest.java @@ -17,26 +17,22 @@ package com.android.setupwizardlib.test; import static android.support.test.InstrumentationRegistry.getTargetContext; - import static org.junit.Assert.assertNotNull; import android.content.Context; -import android.support.test.filters.SmallTest; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.widget.FrameLayout; - +import android.support.test.filters.SmallTest; import com.android.setupwizardlib.R; import com.android.setupwizardlib.items.Item; - +import java.util.ArrayList; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import java.util.ArrayList; -import java.util.List; - /** * Sanity test for all the item layouts to make sure they won't crash when being inflated in * different themes. @@ -45,50 +41,50 @@ import java.util.List; @SmallTest public class ItemLayoutTest { - @Parameters - public static Iterable<Object[]> data() { - int[] themes = new int[] { - R.style.SuwThemeMaterial_Light, - R.style.SuwThemeMaterial, - R.style.SuwThemeGlif_Light, - R.style.SuwThemeGlif, - R.style.SuwThemeGlifV2_Light, - R.style.SuwThemeGlifV2 + @Parameters + public static Iterable<Object[]> data() { + int[] themes = + new int[] { + R.style.SuwThemeMaterial_Light, + R.style.SuwThemeMaterial, + R.style.SuwThemeGlif_Light, + R.style.SuwThemeGlif, + R.style.SuwThemeGlifV2_Light, + R.style.SuwThemeGlifV2 }; - int[] layouts = new int[] { - R.layout.suw_items_default, - R.layout.suw_items_verbose, - R.layout.suw_items_description + int[] layouts = + new int[] { + R.layout.suw_items_default, R.layout.suw_items_verbose, R.layout.suw_items_description }; - // Test all the possible combinations of themes and layouts. - List<Object[]> params = new ArrayList<>(); - for (int theme : themes) { - for (int layout : layouts) { - params.add(new Object[] { theme, layout }); - } - } - return params; + // Test all the possible combinations of themes and layouts. + List<Object[]> params = new ArrayList<>(); + for (int theme : themes) { + for (int layout : layouts) { + params.add(new Object[] {theme, layout}); + } } + return params; + } - private final Context mContext; - private final FrameLayout mParent; - private final Item mItem; + private final Context mContext; + private final FrameLayout mParent; + private final Item mItem; - public ItemLayoutTest(int theme, int layout) { - mContext = new ContextThemeWrapper(getTargetContext(), theme); - mParent = new FrameLayout(mContext); - mItem = new Item(); - mItem.setLayoutResource(layout); - } + public ItemLayoutTest(int theme, int layout) { + mContext = new ContextThemeWrapper(getTargetContext(), theme); + mParent = new FrameLayout(mContext); + mItem = new Item(); + mItem.setLayoutResource(layout); + } - @Test - public void testInflateLayoutHasBasicViews() { - LayoutInflater.from(mContext).inflate(mItem.getLayoutResource(), mParent, true); - mItem.onBindView(mParent); + @Test + public void testInflateLayoutHasBasicViews() { + LayoutInflater.from(mContext).inflate(mItem.getLayoutResource(), mParent, true); + mItem.onBindView(mParent); - assertNotNull("Title should exist", mParent.findViewById(R.id.suw_items_title)); - assertNotNull("Summary should exist", mParent.findViewById(R.id.suw_items_summary)); - assertNotNull("Icon should exist", mParent.findViewById(R.id.suw_items_icon)); - } + assertNotNull("Title should exist", mParent.findViewById(R.id.suw_items_title)); + assertNotNull("Summary should exist", mParent.findViewById(R.id.suw_items_summary)); + assertNotNull("Icon should exist", mParent.findViewById(R.id.suw_items_icon)); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemTest.java index b4ebabb..84990dd 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemTest.java @@ -28,19 +28,17 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.R; import com.android.setupwizardlib.items.Item; import com.android.setupwizardlib.items.ItemHierarchy.Observer; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,149 +50,149 @@ import org.mockito.MockitoAnnotations; @SmallTest public class ItemTest { - private TextView mTitleView; - private TextView mSummaryView; - private ImageView mIconView; - private FrameLayout mIconContainer; - - @Mock - private Observer mObserver; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testOnBindView() { - Item item = new Item(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - Drawable icon = new ShapeDrawable(); - icon.setLevel(4); - item.setIcon(icon); - View view = createLayout(); - - mIconView.setImageLevel(1); - Drawable recycledIcon = new ShapeDrawable(); - mIconView.setImageDrawable(recycledIcon); - - item.onBindView(view); - - assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString()); - assertEquals("Summary should be \"TestSummary\"", "TestSummary", - mSummaryView.getText().toString()); - assertSame("Icon should be the icon shape drawable", icon, mIconView.getDrawable()); - assertEquals("Recycled icon level should not change", 1, recycledIcon.getLevel()); - assertEquals("Icon should be level 4", 4, icon.getLevel()); - } - - @Test - public void testSingleLineItem() { - Item item = new Item(); - item.setTitle("TestTitle"); - View view = createLayout(); - - item.onBindView(view); - - assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString()); - assertEquals("Summary should be gone", View.GONE, mSummaryView.getVisibility()); - assertEquals("IconContainer should be gone", View.GONE, mIconContainer.getVisibility()); - } - - @Test - public void testProperties() { - Item item = new Item(); - item.registerObserver(mObserver); - final InOrder inOrder = inOrder(mObserver); - - item.setTitle("TestTitle"); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - item.setSummary("TestSummary"); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - item.setEnabled(false); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - ShapeDrawable icon = new ShapeDrawable(); - item.setIcon(icon); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - item.setId(12345); - - item.setLayoutResource(56789); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - assertEquals("Title should be \"TestTitle\"", "TestTitle", item.getTitle()); - assertEquals("Summary should be \"TestSummary\"", "TestSummary", item.getSummary()); - assertFalse("Enabled should be false", item.isEnabled()); - assertSame("Icon should be same as set", icon, item.getIcon()); - assertEquals("ID should be 12345", 12345, item.getId()); - assertEquals("Layout resource should be 56789", 56789, item.getLayoutResource()); - } - - @Test - public void testDefaultValues() { - Item item = new Item(); - - assertNull("Default title should be null", item.getTitle()); - assertNull("Default summary should be null", item.getSummary()); - assertNull("Default icon should be null", item.getIcon()); - assertTrue("Default enabled should be true", item.isEnabled()); - assertEquals("Default ID should be 0", 0, item.getId()); - assertEquals("Default layout resource should be R.layout.suw_items_text", - R.layout.suw_items_default, item.getLayoutResource()); - assertTrue("Default visible should be true", item.isVisible()); - } - - @Test - public void testHierarchyImplementation() { - Item item = new Item(); - item.setId(12345); - - assertEquals("getCount should be 1", 1, item.getCount()); - assertSame("getItemAt should return itself", item, item.getItemAt(0)); - assertSame("findItemById with same ID should return itself", item, - item.findItemById(12345)); - assertNull("findItemById with different ID should return null", item.findItemById(34567)); - } - - @Test - public void testVisible() { - Item item = new Item(); - item.registerObserver(mObserver); - item.setVisible(false); - - assertFalse("Item should not be visible", item.isVisible()); - assertEquals("Item count should be 0 when not visible", 0, item.getCount()); - - verify(mObserver).onItemRangeRemoved(eq(item), eq(0), eq(1)); - - item.setVisible(true); - verify(mObserver).onItemRangeInserted(eq(item), eq(0), eq(1)); - } - - private ViewGroup createLayout() { - Context context = InstrumentationRegistry.getContext(); - ViewGroup root = new FrameLayout(context); - - mTitleView = new TextView(context); - mTitleView.setId(R.id.suw_items_title); - root.addView(mTitleView); - - mSummaryView = new TextView(context); - mSummaryView.setId(R.id.suw_items_summary); - root.addView(mSummaryView); - - mIconContainer = new FrameLayout(context); - mIconContainer.setId(R.id.suw_items_icon_container); - root.addView(mIconContainer); - - mIconView = new ImageView(context); - mIconView.setId(R.id.suw_items_icon); - mIconContainer.addView(mIconView); - - return root; - } + private TextView mTitleView; + private TextView mSummaryView; + private ImageView mIconView; + private FrameLayout mIconContainer; + + @Mock private Observer mObserver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testOnBindView() { + Item item = new Item(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + Drawable icon = new ShapeDrawable(); + icon.setLevel(4); + item.setIcon(icon); + View view = createLayout(); + + mIconView.setImageLevel(1); + Drawable recycledIcon = new ShapeDrawable(); + mIconView.setImageDrawable(recycledIcon); + + item.onBindView(view); + + assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString()); + assertEquals( + "Summary should be \"TestSummary\"", "TestSummary", mSummaryView.getText().toString()); + assertSame("Icon should be the icon shape drawable", icon, mIconView.getDrawable()); + assertEquals("Recycled icon level should not change", 1, recycledIcon.getLevel()); + assertEquals("Icon should be level 4", 4, icon.getLevel()); + } + + @Test + public void testSingleLineItem() { + Item item = new Item(); + item.setTitle("TestTitle"); + View view = createLayout(); + + item.onBindView(view); + + assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString()); + assertEquals("Summary should be gone", View.GONE, mSummaryView.getVisibility()); + assertEquals("IconContainer should be gone", View.GONE, mIconContainer.getVisibility()); + } + + @Test + public void testProperties() { + Item item = new Item(); + item.registerObserver(mObserver); + final InOrder inOrder = inOrder(mObserver); + + item.setTitle("TestTitle"); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + item.setSummary("TestSummary"); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + item.setEnabled(false); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + ShapeDrawable icon = new ShapeDrawable(); + item.setIcon(icon); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + item.setId(12345); + + item.setLayoutResource(56789); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + assertEquals("Title should be \"TestTitle\"", "TestTitle", item.getTitle()); + assertEquals("Summary should be \"TestSummary\"", "TestSummary", item.getSummary()); + assertFalse("Enabled should be false", item.isEnabled()); + assertSame("Icon should be same as set", icon, item.getIcon()); + assertEquals("ID should be 12345", 12345, item.getId()); + assertEquals("Layout resource should be 56789", 56789, item.getLayoutResource()); + } + + @Test + public void testDefaultValues() { + Item item = new Item(); + + assertNull("Default title should be null", item.getTitle()); + assertNull("Default summary should be null", item.getSummary()); + assertNull("Default icon should be null", item.getIcon()); + assertTrue("Default enabled should be true", item.isEnabled()); + assertEquals("Default ID should be 0", 0, item.getId()); + assertEquals( + "Default layout resource should be R.layout.suw_items_text", + R.layout.suw_items_default, + item.getLayoutResource()); + assertTrue("Default visible should be true", item.isVisible()); + } + + @Test + public void testHierarchyImplementation() { + Item item = new Item(); + item.setId(12345); + + assertEquals("getCount should be 1", 1, item.getCount()); + assertSame("getItemAt should return itself", item, item.getItemAt(0)); + assertSame("findItemById with same ID should return itself", item, item.findItemById(12345)); + assertNull("findItemById with different ID should return null", item.findItemById(34567)); + } + + @Test + public void testVisible() { + Item item = new Item(); + item.registerObserver(mObserver); + item.setVisible(false); + + assertFalse("Item should not be visible", item.isVisible()); + assertEquals("Item count should be 0 when not visible", 0, item.getCount()); + + verify(mObserver).onItemRangeRemoved(eq(item), eq(0), eq(1)); + + item.setVisible(true); + verify(mObserver).onItemRangeInserted(eq(item), eq(0), eq(1)); + } + + private ViewGroup createLayout() { + Context context = InstrumentationRegistry.getContext(); + ViewGroup root = new FrameLayout(context); + + mTitleView = new TextView(context); + mTitleView.setId(R.id.suw_items_title); + root.addView(mTitleView); + + mSummaryView = new TextView(context); + mSummaryView.setId(R.id.suw_items_summary); + root.addView(mSummaryView); + + mIconContainer = new FrameLayout(context); + mIconContainer.setId(R.id.suw_items_icon_container); + root.addView(mIconContainer); + + mIconView = new ImageView(context); + mIconView.setId(R.id.suw_items_icon); + mIconContainer.addView(mIconView); + + return root; + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ReflectionInflaterTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ReflectionInflaterTest.java index 137a146..69e5882 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ReflectionInflaterTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ReflectionInflaterTest.java @@ -20,64 +20,59 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.annotation.NonNull; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.ScaleAnimation; - -import androidx.annotation.NonNull; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.items.ReflectionInflater; - +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.List; - @SmallTest @RunWith(AndroidJUnit4.class) public class ReflectionInflaterTest { - @Test - public void testInflateXml() { - final Context context = InstrumentationRegistry.getContext(); - TestInflater inflater = new TestInflater(context); - final Animation result = inflater.inflate(R.xml.reflection_inflater_test); + @Test + public void testInflateXml() { + final Context context = InstrumentationRegistry.getContext(); + TestInflater inflater = new TestInflater(context); + final Animation result = inflater.inflate(R.xml.reflection_inflater_test); - assertTrue(result instanceof AnimationSet); - final AnimationSet set = (AnimationSet) result; - final List<Animation> animations = set.getAnimations(); - assertEquals(1, animations.size()); - assertTrue(animations.get(0) instanceof ScaleAnimation); - } + assertTrue(result instanceof AnimationSet); + final AnimationSet set = (AnimationSet) result; + final List<Animation> animations = set.getAnimations(); + assertEquals(1, animations.size()); + assertTrue(animations.get(0) instanceof ScaleAnimation); + } - @Test - public void testDefaultPackage() { - final Context context = InstrumentationRegistry.getContext(); - TestInflater inflater = new TestInflater(context); - inflater.setDefaultPackage("android.view.animation."); - final Animation result = - inflater.inflate(R.xml.reflection_inflater_test_with_default_package); + @Test + public void testDefaultPackage() { + final Context context = InstrumentationRegistry.getContext(); + TestInflater inflater = new TestInflater(context); + inflater.setDefaultPackage("android.view.animation."); + final Animation result = inflater.inflate(R.xml.reflection_inflater_test_with_default_package); - assertTrue(result instanceof AnimationSet); - final AnimationSet set = (AnimationSet) result; - final List<Animation> animations = set.getAnimations(); - assertEquals(1, animations.size()); - assertTrue(animations.get(0) instanceof ScaleAnimation); - } + assertTrue(result instanceof AnimationSet); + final AnimationSet set = (AnimationSet) result; + final List<Animation> animations = set.getAnimations(); + assertEquals(1, animations.size()); + assertTrue(animations.get(0) instanceof ScaleAnimation); + } - private static class TestInflater extends ReflectionInflater<Animation> { + private static class TestInflater extends ReflectionInflater<Animation> { - protected TestInflater(@NonNull Context context) { - super(context); - } + protected TestInflater(@NonNull Context context) { + super(context); + } - @Override - protected void onAddChildItem(Animation parent, Animation child) { - final AnimationSet group = (AnimationSet) parent; - group.addAnimation(child); - } + @Override + protected void onAddChildItem(Animation parent, Animation child) { + final AnimationSet group = (AnimationSet) parent; + group.addAnimation(child); } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java index 531d69e..9d2f784 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java @@ -27,9 +27,7 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Parcelable; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.annotation.IdRes; import android.util.SparseArray; import android.view.AbsSavedState; import android.view.ContextThemeWrapper; @@ -37,15 +35,14 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; - -import androidx.annotation.IdRes; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.SetupWizardLayout; import com.android.setupwizardlib.template.HeaderMixin; import com.android.setupwizardlib.template.NavigationBarMixin; import com.android.setupwizardlib.template.ProgressBarMixin; import com.android.setupwizardlib.view.NavigationBar; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,191 +51,196 @@ import org.junit.runner.RunWith; @SmallTest public class SetupWizardLayoutTest { - @IdRes - private static final int ID1234 = 1234; - - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - } - - @Test - public void testDefaultTemplate() { - SetupWizardLayout layout = new SetupWizardLayout(mContext); - assertDefaultTemplateInflated(layout); - } - - @Test - public void testSetHeaderText() { - SetupWizardLayout layout = new SetupWizardLayout(mContext); - TextView title = (TextView) layout.findViewById(R.id.suw_layout_title); - layout.setHeaderText("Abracadabra"); - assertEquals("Header text should be \"Abracadabra\"", "Abracadabra", title.getText()); - } - - @Test - public void testAddView() { - SetupWizardLayout layout = new SetupWizardLayout(mContext); - TextView tv = new TextView(mContext); - tv.setId(R.id.test_view_id); - layout.addView(tv); - assertDefaultTemplateInflated(layout); - View view = layout.findViewById(R.id.test_view_id); - assertSame("The view added should be the same text view", tv, view); - } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - SetupWizardLayout layout = (SetupWizardLayout) inflater.inflate(R.layout.test_layout, null); - assertDefaultTemplateInflated(layout); - View content = layout.findViewById(R.id.test_content); - assertTrue("@id/test_content should be a TextView", content instanceof TextView); - } - - @Test - public void testCustomTemplate() { - SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); - View templateView = layout.findViewById(R.id.test_template_view); - assertNotNull("@id/test_template_view should exist in template", templateView); - - TextView tv = new TextView(mContext); - tv.setId(R.id.test_view_id); - layout.addView(tv); - - templateView = layout.findViewById(R.id.test_template_view); - assertNotNull("@id/test_template_view should exist in template", templateView); - View contentView = layout.findViewById(R.id.test_view_id); - assertSame("The view added should be the same text view", tv, contentView); - - // The following methods should be no-ops because the custom template doesn't contain the - // corresponding optional views. Just check that they don't throw exceptions. - layout.setHeaderText("Abracadabra"); - layout.setIllustration(new ColorDrawable(Color.MAGENTA)); - layout.setLayoutBackground(new ColorDrawable(Color.RED)); - } - - @Test - public void testGetNavigationBar() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - final NavigationBar navigationBar = layout.getNavigationBar(); - assertEquals("Navigation bar should have ID = @id/suw_layout_navigation_bar", - R.id.suw_layout_navigation_bar, navigationBar.getId()); - } - - @Test - public void testGetNavigationBarNull() { - // test_template does not have navigation bar so getNavigationBar() should return null. - final SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); - final NavigationBar navigationBar = layout.getNavigationBar(); - assertNull("getNavigationBar() in test_template should return null", navigationBar); - } - - @Test - public void testShowProgressBar() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.showProgressBar(); - assertTrue("Progress bar should be shown", layout.isProgressBarShown()); - final View progressBar = layout.findViewById(R.id.suw_layout_progress); - assertTrue("Progress bar view should be shown", - progressBar instanceof ProgressBar && progressBar.getVisibility() == View.VISIBLE); - } - - @Test - public void testHideProgressBar() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.showProgressBar(); - assertTrue("Progress bar should be shown", layout.isProgressBarShown()); - layout.hideProgressBar(); - assertFalse("Progress bar should be hidden", layout.isProgressBarShown()); - final View progressBar = layout.findViewById(R.id.suw_layout_progress); - assertTrue("Progress bar view should exist", - progressBar == null || progressBar.getVisibility() != View.VISIBLE); - } - - @Test - public void testShowProgressBarNotExist() { - // test_template does not have progress bar, so showNavigationBar() should do nothing. - final SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); - layout.showProgressBar(); - assertFalse("Progress bar should not be shown", layout.isProgressBarShown()); - } - - @Test - public void testNonMaterialTheme() { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - android.R.style.Theme); - new SetupWizardLayout(mContext); - // Inflating with a non-Material theme should not crash - } - - @Test - public void testOnRestoreFromInstanceState() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.setId(ID1234); - - SparseArray<Parcelable> container = new SparseArray<>(); - layout.saveHierarchyState(container); - - final SetupWizardLayout layout2 = new SetupWizardLayout(mContext); - layout2.setId(ID1234); - layout2.restoreHierarchyState(container); - - assertFalse("Progress bar should not be shown", layout2.isProgressBarShown()); - } - - @Test - public void testOnRestoreFromInstanceStateProgressBarShown() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.setId(ID1234); - - layout.setProgressBarShown(true); - - SparseArray<Parcelable> container = new SparseArray<>(); - layout.saveHierarchyState(container); - - final SetupWizardLayout layout2 = new SetupWizardLayout(mContext); - layout2.setId(ID1234); - layout2.restoreHierarchyState(container); - - assertTrue("Progress bar should be shown", layout2.isProgressBarShown()); - } - - @Test - public void testOnRestoreFromIncompatibleInstanceState() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.setId(ID1234); - - SparseArray<Parcelable> container = new SparseArray<>(); - container.put(1234, AbsSavedState.EMPTY_STATE); - layout.restoreHierarchyState(container); - - // SetupWizardLayout shouldn't crash with incompatible Parcelable - - assertFalse("Progress bar should not be shown", layout.isProgressBarShown()); - } - - @Test - public void testGetMixins() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - assertNotNull("SetupWizardLayout should have header mixin", - layout.getMixin(HeaderMixin.class)); - assertNotNull("SetupWizardLayout should have progress bar mixin", - layout.getMixin(ProgressBarMixin.class)); - assertNotNull("SetupWizardLayout should have navigation bar mixin", - layout.getMixin(NavigationBarMixin.class)); - } - - private void assertDefaultTemplateInflated(SetupWizardLayout layout) { - View decorView = layout.findViewById(R.id.suw_layout_decor); - View navbar = layout.findViewById(R.id.suw_layout_navigation_bar); - View title = layout.findViewById(R.id.suw_layout_title); - assertNotNull("@id/suw_layout_decor_view should not be null", decorView); - assertTrue("@id/suw_layout_navigation_bar should be an instance of NavigationBar", - navbar instanceof NavigationBar); - assertNotNull("@id/suw_layout_title should not be null", title); - } + @IdRes private static final int ID1234 = 1234; + + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + } + + @Test + public void testDefaultTemplate() { + SetupWizardLayout layout = new SetupWizardLayout(mContext); + assertDefaultTemplateInflated(layout); + } + + @Test + public void testSetHeaderText() { + SetupWizardLayout layout = new SetupWizardLayout(mContext); + TextView title = (TextView) layout.findViewById(R.id.suw_layout_title); + layout.setHeaderText("Abracadabra"); + assertEquals("Header text should be \"Abracadabra\"", "Abracadabra", title.getText()); + } + + @Test + public void testAddView() { + SetupWizardLayout layout = new SetupWizardLayout(mContext); + TextView tv = new TextView(mContext); + tv.setId(R.id.test_view_id); + layout.addView(tv); + assertDefaultTemplateInflated(layout); + View view = layout.findViewById(R.id.test_view_id); + assertSame("The view added should be the same text view", tv, view); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + SetupWizardLayout layout = (SetupWizardLayout) inflater.inflate(R.layout.test_layout, null); + assertDefaultTemplateInflated(layout); + View content = layout.findViewById(R.id.test_content); + assertTrue("@id/test_content should be a TextView", content instanceof TextView); + } + + @Test + public void testCustomTemplate() { + SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); + View templateView = layout.findViewById(R.id.test_template_view); + assertNotNull("@id/test_template_view should exist in template", templateView); + + TextView tv = new TextView(mContext); + tv.setId(R.id.test_view_id); + layout.addView(tv); + + templateView = layout.findViewById(R.id.test_template_view); + assertNotNull("@id/test_template_view should exist in template", templateView); + View contentView = layout.findViewById(R.id.test_view_id); + assertSame("The view added should be the same text view", tv, contentView); + + // The following methods should be no-ops because the custom template doesn't contain the + // corresponding optional views. Just check that they don't throw exceptions. + layout.setHeaderText("Abracadabra"); + layout.setIllustration(new ColorDrawable(Color.MAGENTA)); + layout.setLayoutBackground(new ColorDrawable(Color.RED)); + } + + @Test + public void testGetNavigationBar() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + final NavigationBar navigationBar = layout.getNavigationBar(); + assertEquals( + "Navigation bar should have ID = @id/suw_layout_navigation_bar", + R.id.suw_layout_navigation_bar, + navigationBar.getId()); + } + + @Test + public void testGetNavigationBarNull() { + // test_template does not have navigation bar so getNavigationBar() should return null. + final SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); + final NavigationBar navigationBar = layout.getNavigationBar(); + assertNull("getNavigationBar() in test_template should return null", navigationBar); + } + + @Test + public void testShowProgressBar() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.showProgressBar(); + assertTrue("Progress bar should be shown", layout.isProgressBarShown()); + final View progressBar = layout.findViewById(R.id.suw_layout_progress); + assertTrue( + "Progress bar view should be shown", + progressBar instanceof ProgressBar && progressBar.getVisibility() == View.VISIBLE); + } + + @Test + public void testHideProgressBar() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.showProgressBar(); + assertTrue("Progress bar should be shown", layout.isProgressBarShown()); + layout.hideProgressBar(); + assertFalse("Progress bar should be hidden", layout.isProgressBarShown()); + final View progressBar = layout.findViewById(R.id.suw_layout_progress); + assertTrue( + "Progress bar view should exist", + progressBar == null || progressBar.getVisibility() != View.VISIBLE); + } + + @Test + public void testShowProgressBarNotExist() { + // test_template does not have progress bar, so showNavigationBar() should do nothing. + final SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); + layout.showProgressBar(); + assertFalse("Progress bar should not be shown", layout.isProgressBarShown()); + } + + @Test + public void testNonMaterialTheme() { + mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), android.R.style.Theme); + new SetupWizardLayout(mContext); + // Inflating with a non-Material theme should not crash + } + + @Test + public void testOnRestoreFromInstanceState() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.setId(ID1234); + + SparseArray<Parcelable> container = new SparseArray<>(); + layout.saveHierarchyState(container); + + final SetupWizardLayout layout2 = new SetupWizardLayout(mContext); + layout2.setId(ID1234); + layout2.restoreHierarchyState(container); + + assertFalse("Progress bar should not be shown", layout2.isProgressBarShown()); + } + + @Test + public void testOnRestoreFromInstanceStateProgressBarShown() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.setId(ID1234); + + layout.setProgressBarShown(true); + + SparseArray<Parcelable> container = new SparseArray<>(); + layout.saveHierarchyState(container); + + final SetupWizardLayout layout2 = new SetupWizardLayout(mContext); + layout2.setId(ID1234); + layout2.restoreHierarchyState(container); + + assertTrue("Progress bar should be shown", layout2.isProgressBarShown()); + } + + @Test + public void testOnRestoreFromIncompatibleInstanceState() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.setId(ID1234); + + SparseArray<Parcelable> container = new SparseArray<>(); + container.put(1234, AbsSavedState.EMPTY_STATE); + layout.restoreHierarchyState(container); + + // SetupWizardLayout shouldn't crash with incompatible Parcelable + + assertFalse("Progress bar should not be shown", layout.isProgressBarShown()); + } + + @Test + public void testGetMixins() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + assertNotNull("SetupWizardLayout should have header mixin", layout.getMixin(HeaderMixin.class)); + assertNotNull( + "SetupWizardLayout should have progress bar mixin", + layout.getMixin(ProgressBarMixin.class)); + assertNotNull( + "SetupWizardLayout should have navigation bar mixin", + layout.getMixin(NavigationBarMixin.class)); + } + + private void assertDefaultTemplateInflated(SetupWizardLayout layout) { + View decorView = layout.findViewById(R.id.suw_layout_decor); + View navbar = layout.findViewById(R.id.suw_layout_navigation_bar); + View title = layout.findViewById(R.id.suw_layout_title); + assertNotNull("@id/suw_layout_decor_view should not be null", decorView); + assertTrue( + "@id/suw_layout_navigation_bar should be an instance of NavigationBar", + navbar instanceof NavigationBar); + assertNotNull("@id/suw_layout_title should not be null", title); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardListLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardListLayoutTest.java index 5c34fe0..fc18a31 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardListLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardListLayoutTest.java @@ -25,20 +25,18 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.SetupWizardLayout; import com.android.setupwizardlib.SetupWizardListLayout; import com.android.setupwizardlib.view.NavigationBar; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,90 +45,93 @@ import org.junit.runner.RunWith; @SmallTest public class SetupWizardListLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - } - - @Test - public void testDefaultTemplate() { - SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - assertListTemplateInflated(layout); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + } + + @Test + public void testDefaultTemplate() { + SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + assertListTemplateInflated(layout); + } + + @Test + public void testAddView() { + SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + TextView tv = new TextView(mContext); + try { + layout.addView(tv); + fail("Adding view to ListLayout should throw"); + } catch (UnsupportedOperationException e) { + // Expected exception } - - @Test - public void testAddView() { - SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - TextView tv = new TextView(mContext); - try { - layout.addView(tv); - fail("Adding view to ListLayout should throw"); - } catch (UnsupportedOperationException e) { - // Expected exception - } - } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - SetupWizardListLayout layout = (SetupWizardListLayout) - inflater.inflate(R.layout.test_list_layout, null); - assertListTemplateInflated(layout); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + SetupWizardListLayout layout = + (SetupWizardListLayout) inflater.inflate(R.layout.test_list_layout, null); + assertListTemplateInflated(layout); + } + + @Test + public void testShowProgressBar() { + final SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + layout.showProgressBar(); + assertTrue("Progress bar should be shown", layout.isProgressBarShown()); + final View progressBar = layout.findViewById(R.id.suw_layout_progress); + assertTrue( + "Progress bar view should be shown", + progressBar instanceof ProgressBar && progressBar.getVisibility() == View.VISIBLE); + } + + @Test + public void testDividerInsetLegacy() { + SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertListTemplateInflated(layout); - @Test - public void testShowProgressBar() { - final SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - layout.showProgressBar(); - assertTrue("Progress bar should be shown", layout.isProgressBarShown()); - final View progressBar = layout.findViewById(R.id.suw_layout_progress); - assertTrue("Progress bar view should be shown", - progressBar instanceof ProgressBar && progressBar.getVisibility() == View.VISIBLE); - } - - @Test - public void testDividerInsetLegacy() { - SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertListTemplateInflated(layout); - - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testDividerInsets() { - SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertListTemplateInflated(layout); - - layout.setDividerInsets(10, 15); - assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); - assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - private void assertListTemplateInflated(SetupWizardLayout layout) { - View decorView = layout.findViewById(R.id.suw_layout_decor); - View navbar = layout.findViewById(R.id.suw_layout_navigation_bar); - View title = layout.findViewById(R.id.suw_layout_title); - View list = layout.findViewById(android.R.id.list); - assertNotNull("@id/suw_layout_decor_view should not be null", decorView); - assertTrue("@id/suw_layout_navigation_bar should be an instance of NavigationBar", - navbar instanceof NavigationBar); - assertNotNull("@id/suw_layout_title should not be null", title); - assertTrue("@android:id/list should be an instance of ListView", list instanceof ListView); + @Test + public void testDividerInsets() { + SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertListTemplateInflated(layout); + + layout.setDividerInsets(10, 15); + assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); + assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); + + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } + + private void assertListTemplateInflated(SetupWizardLayout layout) { + View decorView = layout.findViewById(R.id.suw_layout_decor); + View navbar = layout.findViewById(R.id.suw_layout_navigation_bar); + View title = layout.findViewById(R.id.suw_layout_title); + View list = layout.findViewById(android.R.id.list); + assertNotNull("@id/suw_layout_decor_view should not be null", decorView); + assertTrue( + "@id/suw_layout_navigation_bar should be an instance of NavigationBar", + navbar instanceof NavigationBar); + assertNotNull("@id/suw_layout_title should not be null", title); + assertTrue("@android:id/list should be an instance of ListView", list instanceof ListView); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SimpleInflaterTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SimpleInflaterTest.java index f4738ca..da39a7b 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SimpleInflaterTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SimpleInflaterTest.java @@ -20,15 +20,12 @@ import static org.junit.Assert.assertEquals; import android.content.Context; import android.content.res.Resources; +import androidx.annotation.NonNull; +import android.util.AttributeSet; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.AttributeSet; - -import androidx.annotation.NonNull; - import com.android.setupwizardlib.items.SimpleInflater; - import org.junit.Test; import org.junit.runner.RunWith; @@ -36,30 +33,30 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class SimpleInflaterTest { - @Test - public void testInflateXml() { - final Context context = InstrumentationRegistry.getContext(); - TestInflater inflater = new TestInflater(context.getResources()); - final StringBuilder result = inflater.inflate(R.xml.simple_inflater_test); + @Test + public void testInflateXml() { + final Context context = InstrumentationRegistry.getContext(); + TestInflater inflater = new TestInflater(context.getResources()); + final StringBuilder result = inflater.inflate(R.xml.simple_inflater_test); - assertEquals("Parent[null] > Child[foobar]", result.toString()); - } + assertEquals("Parent[null] > Child[foobar]", result.toString()); + } - private static class TestInflater extends SimpleInflater<StringBuilder> { + private static class TestInflater extends SimpleInflater<StringBuilder> { - protected TestInflater(@NonNull Resources resources) { - super(resources); - } + protected TestInflater(@NonNull Resources resources) { + super(resources); + } - @Override - protected StringBuilder onCreateItem(String tagName, AttributeSet attrs) { - final String attribute = attrs.getAttributeValue(null, "myattribute"); - return new StringBuilder(tagName).append("[").append(attribute).append("]"); - } + @Override + protected StringBuilder onCreateItem(String tagName, AttributeSet attrs) { + final String attribute = attrs.getAttributeValue(null, "myattribute"); + return new StringBuilder(tagName).append("[").append(attribute).append("]"); + } - @Override - protected void onAddChildItem(StringBuilder parent, StringBuilder child) { - parent.append(" > ").append(child); - } + @Override + protected void onAddChildItem(StringBuilder parent, StringBuilder child) { + parent.append(" > ").append(child); } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SpanHelperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SpanHelperTest.java index 903cf5e..920d7ab 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SpanHelperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SpanHelperTest.java @@ -19,13 +19,11 @@ package com.android.setupwizardlib.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.text.Annotation; import android.text.SpannableStringBuilder; - +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.span.SpanHelper; - import org.junit.Test; import org.junit.runner.RunWith; @@ -33,17 +31,17 @@ import org.junit.runner.RunWith; @SmallTest public class SpanHelperTest { - @Test - public void testReplaceSpan() { - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - Annotation oldSpan = new Annotation("key", "value"); - Annotation newSpan = new Annotation("newkey", "newvalue"); - ssb.setSpan(oldSpan, 2, 5, 0 /* flags */); + @Test + public void testReplaceSpan() { + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + Annotation oldSpan = new Annotation("key", "value"); + Annotation newSpan = new Annotation("newkey", "newvalue"); + ssb.setSpan(oldSpan, 2, 5, 0 /* flags */); - SpanHelper.replaceSpan(ssb, oldSpan, newSpan); + SpanHelper.replaceSpan(ssb, oldSpan, newSpan); - final Object[] spans = ssb.getSpans(0, ssb.length(), Object.class); - assertEquals("There should be one span in the builder", 1, spans.length); - assertSame("The span should be newSpan", newSpan, spans[0]); - } + final Object[] spans = ssb.getSpans(0, ssb.length(), Object.class); + assertEquals("There should be one span in the builder", 1, spans.length); + assertSame("The span should be newSpan", newSpan, spans[0]); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java index 006e5c4..e0fd49b 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java @@ -24,9 +24,7 @@ import android.graphics.drawable.ShapeDrawable; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.view.StatusBarBackgroundLayout; - import org.junit.Test; import org.junit.runner.RunWith; @@ -34,46 +32,48 @@ import org.junit.runner.RunWith; @SmallTest public class StatusBarBackgroundLayoutTest { - @Test - public void testSetStatusBarBackground() { - final StatusBarBackgroundLayout layout = new StatusBarBackgroundLayout( - InstrumentationRegistry.getContext()); - final ShapeDrawable drawable = new ShapeDrawable(); - layout.setStatusBarBackground(drawable); - assertSame("Status bar background drawable should be same as set", - drawable, layout.getStatusBarBackground()); - } + @Test + public void testSetStatusBarBackground() { + final StatusBarBackgroundLayout layout = + new StatusBarBackgroundLayout(InstrumentationRegistry.getContext()); + final ShapeDrawable drawable = new ShapeDrawable(); + layout.setStatusBarBackground(drawable); + assertSame( + "Status bar background drawable should be same as set", + drawable, + layout.getStatusBarBackground()); + } - @Test - public void testAttachedToWindow() { - // Attaching to window should request apply window inset - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - final TestStatusBarBackgroundLayout layout = - new TestStatusBarBackgroundLayout(InstrumentationRegistry.getContext()); - layout.mRequestApplyInsets = false; - layout.onAttachedToWindow(); + @Test + public void testAttachedToWindow() { + // Attaching to window should request apply window inset + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + final TestStatusBarBackgroundLayout layout = + new TestStatusBarBackgroundLayout(InstrumentationRegistry.getContext()); + layout.mRequestApplyInsets = false; + layout.onAttachedToWindow(); - assertTrue("Attaching to window should apply window inset", layout.mRequestApplyInsets); - } + assertTrue("Attaching to window should apply window inset", layout.mRequestApplyInsets); } + } - private static class TestStatusBarBackgroundLayout extends StatusBarBackgroundLayout { + private static class TestStatusBarBackgroundLayout extends StatusBarBackgroundLayout { - boolean mRequestApplyInsets = false; + boolean mRequestApplyInsets = false; - TestStatusBarBackgroundLayout(Context context) { - super(context); - } + TestStatusBarBackgroundLayout(Context context) { + super(context); + } - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - } + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + } - @Override - public void requestApplyInsets() { - super.requestApplyInsets(); - mRequestApplyInsets = true; - } + @Override + public void requestApplyInsets() { + super.requestApplyInsets(); + mRequestApplyInsets = true; } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java index 98c28f6..1b534e1 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.test; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import android.annotation.SuppressLint; @@ -28,19 +27,17 @@ import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.support.test.InstrumentationRegistry; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; -import android.view.ContextThemeWrapper; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; - import com.android.setupwizardlib.test.util.MockWindow; import com.android.setupwizardlib.util.SystemBarHelper; - import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,241 +46,240 @@ import org.junit.runner.RunWith; @SmallTest public class SystemBarHelperTest { - @Rule - public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); - - private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; - - @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; - - @UiThreadTest - @Test - public void testAddVisibilityFlagView() { - final View view = createViewWithSystemUiVisibility(0x456); - SystemBarHelper.addVisibilityFlag(view, 0x1400); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - // Check that result is 0x1456, because 0x1400 | 0x456 = 0x1456. - assertEquals("View visibility should be 0x1456", 0x1456, view.getSystemUiVisibility()); - } + @Rule public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); + + private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; + + @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; + + @UiThreadTest + @Test + public void testAddVisibilityFlagView() { + final View view = createViewWithSystemUiVisibility(0x456); + SystemBarHelper.addVisibilityFlag(view, 0x1400); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + // Check that result is 0x1456, because 0x1400 | 0x456 = 0x1456. + assertEquals("View visibility should be 0x1456", 0x1456, view.getSystemUiVisibility()); } - - @UiThreadTest - @Test - public void testRemoveVisibilityFlagView() { - final View view = createViewWithSystemUiVisibility(0x456); - SystemBarHelper.removeVisibilityFlag(view, 0x1400); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - // Check that result is 0x56, because 0x456 & ~0x1400 = 0x56. - assertEquals("View visibility should be 0x56", 0x56, view.getSystemUiVisibility()); - } + } + + @UiThreadTest + @Test + public void testRemoveVisibilityFlagView() { + final View view = createViewWithSystemUiVisibility(0x456); + SystemBarHelper.removeVisibilityFlag(view, 0x1400); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + // Check that result is 0x56, because 0x456 & ~0x1400 = 0x56. + assertEquals("View visibility should be 0x56", 0x56, view.getSystemUiVisibility()); } - - @UiThreadTest - @Test - public void testAddVisibilityFlagWindow() { - final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.addVisibilityFlag(window, 0x1400); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - // Check that result is 0x1456 = 0x1400 | 0x456. - assertEquals("View visibility should be 0x1456", 0x1456, - window.getAttributes().systemUiVisibility); - } + } + + @UiThreadTest + @Test + public void testAddVisibilityFlagWindow() { + final Window window = createWindowWithSystemUiVisibility(0x456); + SystemBarHelper.addVisibilityFlag(window, 0x1400); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + // Check that result is 0x1456 = 0x1400 | 0x456. + assertEquals( + "View visibility should be 0x1456", 0x1456, window.getAttributes().systemUiVisibility); } - - @UiThreadTest - @Test - public void testRemoveVisibilityFlagWindow() { - final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.removeVisibilityFlag(window, 0x1400); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - // Check that result is 0x56 = 0x456 & ~0x1400. - assertEquals("View visibility should be 0x56", 0x56, - window.getAttributes().systemUiVisibility); - } + } + + @UiThreadTest + @Test + public void testRemoveVisibilityFlagWindow() { + final Window window = createWindowWithSystemUiVisibility(0x456); + SystemBarHelper.removeVisibilityFlag(window, 0x1400); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + // Check that result is 0x56 = 0x456 & ~0x1400. + assertEquals( + "View visibility should be 0x56", 0x56, window.getAttributes().systemUiVisibility); } - - @UiThreadTest - @Test - public void testHideSystemBarsWindow() { - final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.hideSystemBars(window); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals("DEFAULT_IMMERSIVE_FLAGS should be added to window's systemUiVisibility", - DEFAULT_IMMERSIVE_FLAGS | 0x456, - window.getAttributes().systemUiVisibility); - assertEquals( - "DEFAULT_IMMERSIVE_FLAGS should be added to decorView's systemUiVisibility", - DEFAULT_IMMERSIVE_FLAGS | 0x456, - window.getDecorView().getSystemUiVisibility()); - assertEquals("Navigation bar should be transparent", window.getNavigationBarColor(), 0); - assertEquals("Status bar should be transparent", window.getStatusBarColor(), 0); - } + } + + @UiThreadTest + @Test + public void testHideSystemBarsWindow() { + final Window window = createWindowWithSystemUiVisibility(0x456); + SystemBarHelper.hideSystemBars(window); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals( + "DEFAULT_IMMERSIVE_FLAGS should be added to window's systemUiVisibility", + DEFAULT_IMMERSIVE_FLAGS | 0x456, + window.getAttributes().systemUiVisibility); + assertEquals( + "DEFAULT_IMMERSIVE_FLAGS should be added to decorView's systemUiVisibility", + DEFAULT_IMMERSIVE_FLAGS | 0x456, + window.getDecorView().getSystemUiVisibility()); + assertEquals("Navigation bar should be transparent", window.getNavigationBarColor(), 0); + assertEquals("Status bar should be transparent", window.getStatusBarColor(), 0); } - - @UiThreadTest - @Test - public void testShowSystemBarsWindow() { - final Window window = createWindowWithSystemUiVisibility(0x456); - Context context = new ContextThemeWrapper( - InstrumentationRegistry.getContext(), android.R.style.Theme); - SystemBarHelper.showSystemBars(window, context); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals( - "DEFAULT_IMMERSIVE_FLAGS should be removed from window's systemUiVisibility", - 0x456 & ~DEFAULT_IMMERSIVE_FLAGS, - window.getAttributes().systemUiVisibility); - assertEquals( - "DEFAULT_IMMERSIVE_FLAGS should be removed from decorView's systemUiVisibility", - 0x456 & ~DEFAULT_IMMERSIVE_FLAGS, - window.getDecorView().getSystemUiVisibility()); - assertEquals("Navigation bar should not be transparent", - window.getNavigationBarColor(), 0xff000000); - assertEquals("Status bar should not be transparent", - window.getStatusBarColor(), 0xff000000); - } + } + + @UiThreadTest + @Test + public void testShowSystemBarsWindow() { + final Window window = createWindowWithSystemUiVisibility(0x456); + Context context = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), android.R.style.Theme); + SystemBarHelper.showSystemBars(window, context); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals( + "DEFAULT_IMMERSIVE_FLAGS should be removed from window's systemUiVisibility", + 0x456 & ~DEFAULT_IMMERSIVE_FLAGS, + window.getAttributes().systemUiVisibility); + assertEquals( + "DEFAULT_IMMERSIVE_FLAGS should be removed from decorView's systemUiVisibility", + 0x456 & ~DEFAULT_IMMERSIVE_FLAGS, + window.getDecorView().getSystemUiVisibility()); + assertEquals( + "Navigation bar should not be transparent", window.getNavigationBarColor(), 0xff000000); + assertEquals("Status bar should not be transparent", window.getStatusBarColor(), 0xff000000); } - - @UiThreadTest - @Test - public void testHideSystemBarsNoInfiniteLoop() throws InterruptedException { - final TestWindow window = new TestWindow(InstrumentationRegistry.getContext(), null); - final HandlerThread thread = new HandlerThread("SystemBarHelperTest"); - thread.start(); - final Handler handler = new Handler(thread.getLooper()); - handler.post(new Runnable() { - @Override - public void run() { - SystemBarHelper.hideSystemBars(window); - } + } + + @UiThreadTest + @Test + public void testHideSystemBarsNoInfiniteLoop() throws InterruptedException { + final TestWindow window = new TestWindow(InstrumentationRegistry.getContext(), null); + final HandlerThread thread = new HandlerThread("SystemBarHelperTest"); + thread.start(); + final Handler handler = new Handler(thread.getLooper()); + handler.post( + new Runnable() { + @Override + public void run() { + SystemBarHelper.hideSystemBars(window); + } }); - SystemClock.sleep(500); // Wait for the looper to drain all the messages - thread.quit(); - // Initial peek + 3 retries = 4 tries total - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals("Peek decor view should give up after 4 tries", 4, - window.peekDecorViewCount); - } + SystemClock.sleep(500); // Wait for the looper to drain all the messages + thread.quit(); + // Initial peek + 3 retries = 4 tries total + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals("Peek decor view should give up after 4 tries", 4, window.peekDecorViewCount); } - - @UiThreadTest - @Test - public void testHideSystemBarsDialog() { - final Dialog dialog = new Dialog(InstrumentationRegistry.getContext()); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - final WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes(); - attrs.systemUiVisibility = 0x456; - dialog.getWindow().setAttributes(attrs); - } - - SystemBarHelper.hideSystemBars(dialog); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals("DIALOG_IMMERSIVE_FLAGS should be added to window's systemUiVisibility", - DIALOG_IMMERSIVE_FLAGS | 0x456, - dialog.getWindow().getAttributes().systemUiVisibility); - } + } + + @UiThreadTest + @Test + public void testHideSystemBarsDialog() { + final Dialog dialog = new Dialog(InstrumentationRegistry.getContext()); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + final WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes(); + attrs.systemUiVisibility = 0x456; + dialog.getWindow().setAttributes(attrs); } - @UiThreadTest - @Test - public void testSetBackButtonVisibleTrue() { - final Window window = createWindowWithSystemUiVisibility(STATUS_BAR_DISABLE_BACK | 0x456); - SystemBarHelper.setBackButtonVisible(window, true); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - assertThat(window.getAttributes().systemUiVisibility) - .named("window sysUiVisibility") - .isEqualTo(0x456); - assertThat(window.getDecorView().getSystemUiVisibility()) - .named("decor view sysUiVisibility") - .isEqualTo(0x456); - } + SystemBarHelper.hideSystemBars(dialog); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals( + "DIALOG_IMMERSIVE_FLAGS should be added to window's systemUiVisibility", + DIALOG_IMMERSIVE_FLAGS | 0x456, + dialog.getWindow().getAttributes().systemUiVisibility); } - - @UiThreadTest - @Test - public void testSetBackButtonVisibleFalse() { - final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.setBackButtonVisible(window, false); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - assertThat(window.getAttributes().systemUiVisibility) - .named("window sysUiVisibility") - .isEqualTo(0x456 | STATUS_BAR_DISABLE_BACK); - assertThat(window.getDecorView().getSystemUiVisibility()) - .named("decor view sysUiVisibility") - .isEqualTo(0x456 | STATUS_BAR_DISABLE_BACK); - } + } + + @UiThreadTest + @Test + public void testSetBackButtonVisibleTrue() { + final Window window = createWindowWithSystemUiVisibility(STATUS_BAR_DISABLE_BACK | 0x456); + SystemBarHelper.setBackButtonVisible(window, true); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + assertThat(window.getAttributes().systemUiVisibility) + .named("window sysUiVisibility") + .isEqualTo(0x456); + assertThat(window.getDecorView().getSystemUiVisibility()) + .named("decor view sysUiVisibility") + .isEqualTo(0x456); } - - private View createViewWithSystemUiVisibility(int vis) { - final View view = new View(InstrumentationRegistry.getContext()); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - view.setSystemUiVisibility(vis); - } - return view; + } + + @UiThreadTest + @Test + public void testSetBackButtonVisibleFalse() { + final Window window = createWindowWithSystemUiVisibility(0x456); + SystemBarHelper.setBackButtonVisible(window, false); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + assertThat(window.getAttributes().systemUiVisibility) + .named("window sysUiVisibility") + .isEqualTo(0x456 | STATUS_BAR_DISABLE_BACK); + assertThat(window.getDecorView().getSystemUiVisibility()) + .named("decor view sysUiVisibility") + .isEqualTo(0x456 | STATUS_BAR_DISABLE_BACK); } + } - private Window createWindowWithSystemUiVisibility(int vis) { - final Window window = new TestWindow(InstrumentationRegistry.getContext(), - createViewWithSystemUiVisibility(vis)); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - WindowManager.LayoutParams attrs = window.getAttributes(); - attrs.systemUiVisibility = vis; - window.setAttributes(attrs); - } - return window; + private View createViewWithSystemUiVisibility(int vis) { + final View view = new View(InstrumentationRegistry.getContext()); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + view.setSystemUiVisibility(vis); + } + return view; + } + + private Window createWindowWithSystemUiVisibility(int vis) { + final Window window = + new TestWindow(InstrumentationRegistry.getContext(), createViewWithSystemUiVisibility(vis)); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.systemUiVisibility = vis; + window.setAttributes(attrs); } + return window; + } - private static class TestWindow extends MockWindow { + private static class TestWindow extends MockWindow { - private View mDecorView; - public int peekDecorViewCount = 0; + private View mDecorView; + public int peekDecorViewCount = 0; - private int mNavigationBarColor = -1; - private int mStatusBarColor = -1; + private int mNavigationBarColor = -1; + private int mStatusBarColor = -1; - TestWindow(Context context, View decorView) { - super(context); - mDecorView = decorView; - } + TestWindow(Context context, View decorView) { + super(context); + mDecorView = decorView; + } - @Override - public View getDecorView() { - return mDecorView; - } + @Override + public View getDecorView() { + return mDecorView; + } - @Override - public View peekDecorView() { - peekDecorViewCount++; - return mDecorView; - } + @Override + public View peekDecorView() { + peekDecorViewCount++; + return mDecorView; + } - @Override - public void setNavigationBarColor(int i) { - mNavigationBarColor = i; - } + @Override + public void setNavigationBarColor(int i) { + mNavigationBarColor = i; + } - @Override - public int getNavigationBarColor() { - return mNavigationBarColor; - } + @Override + public int getNavigationBarColor() { + return mNavigationBarColor; + } - @Override - public void setStatusBarColor(int i) { - mStatusBarColor = i; - } + @Override + public void setStatusBarColor(int i) { + mStatusBarColor = i; + } - @Override - public int getStatusBarColor() { - return mStatusBarColor; - } + @Override + public int getStatusBarColor() { + return mStatusBarColor; } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestHelper.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestHelper.java index 6910513..918d63a 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestHelper.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestHelper.java @@ -24,69 +24,69 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.support.test.InstrumentationRegistry; +import androidx.annotation.StyleRes; import android.view.View; import android.view.View.MeasureSpec; - -import androidx.annotation.StyleRes; +import android.support.test.InstrumentationRegistry; public class DrawingTestHelper { - /** - * Creates an activity of which to inflate views and drawables for drawing tests. This method - * will return an instance of AppCompatActivity which allows testing of drawing behavior - * injected by support libraries (like drawable tinting) as well. - */ - public static Activity createCanvasActivity(@StyleRes int theme) - throws IllegalAccessException, InstantiationException { - final Context context = InstrumentationRegistry.getTargetContext(); - final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + /** + * Creates an activity of which to inflate views and drawables for drawing tests. This method will + * return an instance of AppCompatActivity which allows testing of drawing behavior injected by + * support libraries (like drawable tinting) as well. + */ + public static Activity createCanvasActivity(@StyleRes int theme) + throws IllegalAccessException, InstantiationException { + final Context context = InstrumentationRegistry.getTargetContext(); + final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - final Intent intent = new Intent(context, DrawingTestActivity.class); - final Activity activity = instrumentation.newActivity( - DrawingTestActivity.class, - context, - null, /* token */ - new Application(), - intent, - new ActivityInfo(), - "", /* title */ - null, /* parent */ - null, /* id */ - null /* lastNonConfigurationInstance */); - instrumentation.callActivityOnCreate(activity, null); - activity.setTheme(theme); - return activity; - } + final Intent intent = new Intent(context, DrawingTestActivity.class); + final Activity activity = + instrumentation.newActivity( + DrawingTestActivity.class, + context, + null, /* token */ + new Application(), + intent, + new ActivityInfo(), + "", /* title */ + null, /* parent */ + null, /* id */ + null /* lastNonConfigurationInstance */); + instrumentation.callActivityOnCreate(activity, null); + activity.setTheme(theme); + return activity; + } - private final int mWidth; - private final int mHeight; - private final Canvas mCanvas; - private final Bitmap mBitmap; + private final int mWidth; + private final int mHeight; + private final Canvas mCanvas; + private final Bitmap mBitmap; - public DrawingTestHelper(int width, int height) { - mWidth = width; - mHeight = height; + public DrawingTestHelper(int width, int height) { + mWidth = width; + mHeight = height; - mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(mBitmap); - } + mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBitmap); + } - public void drawView(View view) { - view.measure( - MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY)); - view.layout(0, 0, mWidth, mHeight); - view.draw(mCanvas); - } + public void drawView(View view) { + view.measure( + MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY)); + view.layout(0, 0, mWidth, mHeight); + view.draw(mCanvas); + } - public int[] getPixels() { - int[] out = new int[mWidth * mHeight]; - mBitmap.getPixels(out, 0, mWidth, 0, 0, mWidth, mHeight); - return out; - } + public int[] getPixels() { + int[] out = new int[mWidth * mHeight]; + mBitmap.getPixels(out, 0, mWidth, 0, 0, mWidth, mHeight); + return out; + } - public int getPixel(int x, int y) { - return mBitmap.getPixel(x, y); - } + public int getPixel(int x, int y) { + return mBitmap.getPixel(x, y); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/util/MockWindow.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/util/MockWindow.java index 7af20eb..1e096eb 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/util/MockWindow.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/util/MockWindow.java @@ -21,6 +21,7 @@ import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import androidx.annotation.NonNull; import android.view.InputQueue; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -30,252 +31,250 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; -import androidx.annotation.NonNull; - public class MockWindow extends Window { - public MockWindow(Context context) { - super(context); - } - - @Override - public void takeSurface(SurfaceHolder.Callback2 callback2) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void takeInputQueue(InputQueue.Callback callback) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean isFloating() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setContentView(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setContentView(View view) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setContentView(View view, ViewGroup.LayoutParams layoutParams) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void addContentView(View view, ViewGroup.LayoutParams layoutParams) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public View getCurrentFocus() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @NonNull - @Override - public LayoutInflater getLayoutInflater() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setTitle(CharSequence charSequence) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setTitleColor(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void openPanel(int i, KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void closePanel(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void togglePanel(int i, KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void invalidatePanelMenu(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean performPanelShortcut(int i, int i1, KeyEvent keyEvent, int i2) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean performPanelIdentifierAction(int i, int i1, int i2) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void closeAllPanels() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean performContextMenuIdentifierAction(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void onConfigurationChanged(Configuration configuration) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setBackgroundDrawable(Drawable drawable) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureDrawableResource(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureDrawableUri(int i, Uri uri) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureDrawable(int i, Drawable drawable) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureDrawableAlpha(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureInt(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void takeKeyEvents(boolean b) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchKeyEvent(KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchKeyShortcutEvent(KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchTouchEvent(MotionEvent motionEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchTrackballEvent(MotionEvent motionEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchGenericMotionEvent(MotionEvent motionEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public View getDecorView() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public View peekDecorView() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public Bundle saveHierarchyState() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void restoreHierarchyState(Bundle bundle) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - protected void onActive() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setChildDrawable(int i, Drawable drawable) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setChildInt(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean isShortcutKey(int i, KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setVolumeControlStream(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public int getVolumeControlStream() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public int getStatusBarColor() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setStatusBarColor(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public int getNavigationBarColor() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setNavigationBarColor(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setDecorCaptionShade(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setResizingCaptionDrawable(Drawable drawable) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } + public MockWindow(Context context) { + super(context); + } + + @Override + public void takeSurface(SurfaceHolder.Callback2 callback2) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void takeInputQueue(InputQueue.Callback callback) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean isFloating() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setContentView(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setContentView(View view) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams layoutParams) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams layoutParams) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public View getCurrentFocus() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @NonNull + @Override + public LayoutInflater getLayoutInflater() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setTitle(CharSequence charSequence) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setTitleColor(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void openPanel(int i, KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void closePanel(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void togglePanel(int i, KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void invalidatePanelMenu(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean performPanelShortcut(int i, int i1, KeyEvent keyEvent, int i2) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean performPanelIdentifierAction(int i, int i1, int i2) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void closeAllPanels() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean performContextMenuIdentifierAction(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void onConfigurationChanged(Configuration configuration) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setBackgroundDrawable(Drawable drawable) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureDrawableResource(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureDrawableUri(int i, Uri uri) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureDrawable(int i, Drawable drawable) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureDrawableAlpha(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureInt(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void takeKeyEvents(boolean b) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchKeyEvent(KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchKeyShortcutEvent(KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchTouchEvent(MotionEvent motionEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchTrackballEvent(MotionEvent motionEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchGenericMotionEvent(MotionEvent motionEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public View getDecorView() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public View peekDecorView() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public Bundle saveHierarchyState() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void restoreHierarchyState(Bundle bundle) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + protected void onActive() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setChildDrawable(int i, Drawable drawable) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setChildInt(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean isShortcutKey(int i, KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setVolumeControlStream(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public int getVolumeControlStream() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public int getStatusBarColor() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setStatusBarColor(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public int getNavigationBarColor() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setNavigationBarColor(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setDecorCaptionShade(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setResizingCaptionDrawable(Drawable drawable) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/util/FallbackThemeWrapperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/util/FallbackThemeWrapperTest.java index d492765..99d997b 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/util/FallbackThemeWrapperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/util/FallbackThemeWrapperTest.java @@ -20,13 +20,11 @@ import static org.junit.Assert.assertEquals; import android.content.Context; import android.content.res.TypedArray; +import android.view.ContextThemeWrapper; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.ContextThemeWrapper; - import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,37 +33,35 @@ import org.junit.runner.RunWith; @SmallTest public class FallbackThemeWrapperTest { - private FallbackThemeWrapper mThemedContext; + private FallbackThemeWrapper mThemedContext; - @Before - public void setUp() { - Context baseContext = new ContextThemeWrapper( - InstrumentationRegistry.getContext(), - R.style.TestBaseTheme); - mThemedContext = new FallbackThemeWrapper(baseContext, R.style.TestFallbackTheme); - } + @Before + public void setUp() { + Context baseContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.TestBaseTheme); + mThemedContext = new FallbackThemeWrapper(baseContext, R.style.TestFallbackTheme); + } - @Test - public void testThemeValueOnlyInBase() { - final TypedArray a = - mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.background}); - assertEquals(0xffff0000, a.getColor(0, 0)); - a.recycle(); - } + @Test + public void testThemeValueOnlyInBase() { + final TypedArray a = + mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.background}); + assertEquals(0xffff0000, a.getColor(0, 0)); + a.recycle(); + } - @Test - public void testThemeValueOnlyInFallback() { - final TypedArray a = - mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.foreground}); - assertEquals(0xff0000ff, a.getColor(0, 0)); - a.recycle(); - } + @Test + public void testThemeValueOnlyInFallback() { + final TypedArray a = + mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.foreground}); + assertEquals(0xff0000ff, a.getColor(0, 0)); + a.recycle(); + } - @Test - public void testThemeValueInBoth() { - final TypedArray a = - mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.theme}); - assertEquals(R.style.TestBaseTheme, a.getResourceId(0, 0)); - a.recycle(); - } + @Test + public void testThemeValueInBoth() { + final TypedArray a = mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.theme}); + assertEquals(R.style.TestBaseTheme, a.getResourceId(0, 0)); + a.recycle(); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java b/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java index 2a4a8c0..d77838f 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java @@ -16,14 +16,8 @@ package com.android.setupwizardlib; -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.robolectric.RuntimeEnvironment.application; import android.content.Context; @@ -34,328 +28,337 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import androidx.annotation.IdRes; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; - -import androidx.annotation.IdRes; - -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.template.ColoredHeaderMixin; import com.android.setupwizardlib.template.HeaderMixin; import com.android.setupwizardlib.template.IconMixin; import com.android.setupwizardlib.template.ProgressBarMixin; import com.android.setupwizardlib.view.StatusBarBackgroundLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class GlifLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - } - - @Test - public void testDefaultTemplate() { - GlifLayout layout = new GlifLayout(mContext); - assertDefaultTemplateInflated(layout); - } - - @Test - public void testSetHeaderText() { - GlifLayout layout = new GlifLayout(mContext); - TextView title = (TextView) layout.findViewById(R.id.suw_layout_title); - layout.setHeaderText("Abracadabra"); - assertEquals("Header text should be \"Abracadabra\"", "Abracadabra", title.getText()); - } - - @Test - public void testAddView() { - @IdRes int testViewId = 123456; - GlifLayout layout = new GlifLayout(mContext); - TextView tv = new TextView(mContext); - tv.setId(testViewId); - layout.addView(tv); - assertDefaultTemplateInflated(layout); - View view = layout.findViewById(testViewId); - assertSame("The view added should be the same text view", tv, view); - } - - @Test - public void testGetScrollView() { - GlifLayout layout = new GlifLayout(mContext); - assertNotNull("Get scroll view should not be null with default template", - layout.getScrollView()); - } - - @Test - public void testSetPrimaryColor() { - GlifLayout layout = new GlifLayout(mContext); - layout.setProgressBarShown(true); - layout.setPrimaryColor(ColorStateList.valueOf(Color.RED)); - assertEquals("Primary color should be red", - ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) layout.findViewById(R.id.suw_layout_progress); - assertEquals("Progress bar should be tinted red", - ColorStateList.valueOf(Color.RED), progressBar.getIndeterminateTintList()); - assertEquals("Determinate progress bar should also be tinted red", - ColorStateList.valueOf(Color.RED), progressBar.getProgressBackgroundTintList()); - } - } - - @Config(qualifiers = "sw600dp") - @Test - public void testSetPrimaryColorTablet() { - GlifLayout layout = new GlifLayout(mContext); - layout.setProgressBarShown(true); - layout.setPrimaryColor(ColorStateList.valueOf(Color.RED)); - assertEquals("Primary color should be red", - ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) layout.findViewById(R.id.suw_layout_progress); - assertEquals("Progress bar should be tinted red", - ColorStateList.valueOf(Color.RED), progressBar.getIndeterminateTintList()); - assertEquals("Determinate progress bar should also be tinted red", - ColorStateList.valueOf(Color.RED), progressBar.getProgressBackgroundTintList()); - } - - assertEquals(Color.RED, ((GlifPatternDrawable) getTabletBackground(layout)).getColor()); - } - - @Test - public void testSetBackgroundBaseColor() { - GlifLayout layout = new GlifLayout(mContext); - layout.setPrimaryColor(ColorStateList.valueOf(Color.BLUE)); - layout.setBackgroundBaseColor(ColorStateList.valueOf(Color.RED)); - - assertEquals(Color.RED, ((GlifPatternDrawable) getPhoneBackground(layout)).getColor()); - assertEquals(Color.RED, layout.getBackgroundBaseColor().getDefaultColor()); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testSetBackgroundBaseColorTablet() { - GlifLayout layout = new GlifLayout(mContext); - layout.setPrimaryColor(ColorStateList.valueOf(Color.BLUE)); - layout.setBackgroundBaseColor(ColorStateList.valueOf(Color.RED)); - - assertEquals(Color.RED, ((GlifPatternDrawable) getTabletBackground(layout)).getColor()); - assertEquals(Color.RED, layout.getBackgroundBaseColor().getDefaultColor()); - } - - @Test - public void testSetBackgroundPatternedTrue() { - GlifLayout layout = new GlifLayout(mContext); - layout.setBackgroundPatterned(true); - - assertThat(getPhoneBackground(layout), instanceOf(GlifPatternDrawable.class)); - assertTrue("Background should be patterned", layout.isBackgroundPatterned()); - } - - @Test - public void testSetBackgroundPatternedFalse() { - GlifLayout layout = new GlifLayout(mContext); - layout.setBackgroundPatterned(false); - - assertThat(getPhoneBackground(layout), instanceOf(ColorDrawable.class)); - assertFalse("Background should not be patterned", layout.isBackgroundPatterned()); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testSetBackgroundPatternedTrueTablet() { - GlifLayout layout = new GlifLayout(mContext); - layout.setBackgroundPatterned(true); - - assertThat(getTabletBackground(layout), instanceOf(GlifPatternDrawable.class)); - assertTrue("Background should be patterned", layout.isBackgroundPatterned()); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testSetBackgroundPatternedFalseTablet() { - GlifLayout layout = new GlifLayout(mContext); - layout.setBackgroundPatterned(false); - - assertThat(getTabletBackground(layout), instanceOf(ColorDrawable.class)); - assertFalse("Background should not be patterned", layout.isBackgroundPatterned()); - } - - @Test - public void testNonGlifTheme() { - mContext = new ContextThemeWrapper(application, android.R.style.Theme); - new GlifLayout(mContext); - // Inflating with a non-GLIF theme should not crash - } - - @Test - public void testPeekProgressBarNull() { - GlifLayout layout = new GlifLayout(mContext); - assertNull("PeekProgressBar should return null initially", layout.peekProgressBar()); - } - - @Test - public void testPeekProgressBar() { - GlifLayout layout = new GlifLayout(mContext); - layout.setProgressBarShown(true); - assertNotNull("Peek progress bar should return the bar after setProgressBarShown(true)", - layout.peekProgressBar()); - } - - @Test - public void testMixins() { - GlifLayout layout = new GlifLayout(mContext); - final HeaderMixin header = layout.getMixin(HeaderMixin.class); - assertTrue("Header should be instance of ColoredHeaderMixin. " - + "Found " + header.getClass() + " instead.", header instanceof ColoredHeaderMixin); - - assertNotNull("GlifLayout should have icon mixin", layout.getMixin(IconMixin.class)); - assertNotNull("GlifLayout should have progress bar mixin", - layout.getMixin(ProgressBarMixin.class)); - } - - @Test - public void testInflateFooter() { - GlifLayout layout = new GlifLayout(mContext); - - final View view = layout.inflateFooter(android.R.layout.simple_list_item_1); - assertEquals(android.R.id.text1, view.getId()); - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testInflateFooterTablet() { - testInflateFooter(); - } - - @Test - public void testInflateFooterBlankTemplate() { - GlifLayout layout = new GlifLayout(mContext, R.layout.suw_glif_blank_template); - - final View view = layout.inflateFooter(android.R.layout.simple_list_item_1); - assertEquals(android.R.id.text1, view.getId()); - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testInflateFooterBlankTemplateTablet() { - testInflateFooterBlankTemplate(); - } - - @Test - public void testFooterXml() { - GlifLayout layout = new GlifLayout( - mContext, - Robolectric.buildAttributeSet() - .addAttribute(R.attr.suwFooter, "@android:layout/simple_list_item_1") - .build()); - - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Test - public void inflateStickyHeader_shouldAddViewToLayout() { - GlifLayout layout = new GlifLayout(mContext); - - final View view = layout.inflateStickyHeader(android.R.layout.simple_list_item_1); - assertEquals(android.R.id.text1, view.getId()); - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Config(qualifiers = "sw600dp") - @Test - public void inflateStickyHeader_whenOnTablets_shouldAddViewToLayout() { - inflateStickyHeader_shouldAddViewToLayout(); - } - - @Test - public void inflateStickyHeader_whenInXml_shouldAddViewToLayout() { - GlifLayout layout = new GlifLayout( - mContext, - Robolectric.buildAttributeSet() - .addAttribute(R.attr.suwStickyHeader, "@android:layout/simple_list_item_1") - .build()); - - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Test - public void inflateStickyHeader_whenOnBlankTemplate_shouldAddViewToLayout() { - GlifLayout layout = new GlifLayout(mContext, R.layout.suw_glif_blank_template); - - final View view = layout.inflateStickyHeader(android.R.layout.simple_list_item_1); - assertEquals(android.R.id.text1, view.getId()); - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Config(qualifiers = "sw600dp") - @Test - public void inflateStickyHeader_whenOnBlankTemplateTablet_shouldAddViewToLayout() { - inflateStickyHeader_whenOnBlankTemplate_shouldAddViewToLayout(); - } - - @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.NEWEST_SDK) - @Test - public void createFromXml_shouldSetLayoutFullscreen_whenLayoutFullscreenIsNotSet() { - GlifLayout layout = new GlifLayout( - mContext, - Robolectric.buildAttributeSet() - .build()); - if (VERSION.SDK_INT >= VERSION_CODES.M) { - assertEquals( - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN, - layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - } - - @Test - public void createFromXml_shouldNotSetLayoutFullscreen_whenLayoutFullscreenIsFalse() { - GlifLayout layout = new GlifLayout( - mContext, - Robolectric.buildAttributeSet() - .addAttribute(R.attr.suwLayoutFullscreen, "false") - .build()); - - assertEquals( - 0, - layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - - private Drawable getPhoneBackground(GlifLayout layout) { - final StatusBarBackgroundLayout patternBg = - (StatusBarBackgroundLayout) layout.findManagedViewById(R.id.suw_pattern_bg); - return patternBg.getStatusBarBackground(); - } - - private Drawable getTabletBackground(GlifLayout layout) { - final View patternBg = layout.findManagedViewById(R.id.suw_pattern_bg); - return patternBg.getBackground(); - } - - private void assertDefaultTemplateInflated(GlifLayout layout) { - View title = layout.findViewById(R.id.suw_layout_title); - assertNotNull("@id/suw_layout_title should not be null", title); - - View icon = layout.findViewById(R.id.suw_layout_icon); - assertNotNull("@id/suw_layout_icon should not be null", icon); - - View scrollView = layout.findViewById(R.id.suw_scroll_view); - assertTrue("@id/suw_scroll_view should be a ScrollView", scrollView instanceof ScrollView); - } + private Context context; + + @Before + public void setUpContext() { + context = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + } + + @Test + public void testDefaultTemplate() { + GlifLayout layout = new GlifLayout(context); + assertDefaultTemplateInflated(layout); + } + + @Test + public void testSetHeaderText() { + GlifLayout layout = new GlifLayout(context); + TextView title = layout.findViewById(R.id.suw_layout_title); + layout.setHeaderText("Abracadabra"); + assertWithMessage("Header text should be \"Abracadabra\"") + .that(title.getText().toString()) + .isEqualTo("Abracadabra"); + } + + @Test + public void testAddView() { + @IdRes int testViewId = 123456; + GlifLayout layout = new GlifLayout(context); + TextView tv = new TextView(context); + tv.setId(testViewId); + layout.addView(tv); + assertDefaultTemplateInflated(layout); + View view = layout.findViewById(testViewId); + assertThat(view).named("Text view added").isSameAs(tv); + } + + @Test + public void testGetScrollView() { + GlifLayout layout = new GlifLayout(context); + assertWithMessage("Get scroll view should not be null with default template") + .that(layout.getScrollView()) + .isNotNull(); + } + + @Test + public void testSetPrimaryColor() { + GlifLayout layout = new GlifLayout(context); + layout.setProgressBarShown(true); + layout.setPrimaryColor(ColorStateList.valueOf(Color.RED)); + assertWithMessage("Primary color should be red") + .that(layout.getPrimaryColor()) + .isEqualTo(ColorStateList.valueOf(Color.RED)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = layout.findViewById(R.id.suw_layout_progress); + assertThat(progressBar.getIndeterminateTintList()) + .named("indeterminate progress bar tint") + .isEqualTo(ColorStateList.valueOf(Color.RED)); + assertThat(progressBar.getProgressBackgroundTintList()) + .named("determinate progress bar tint") + .isEqualTo(ColorStateList.valueOf(Color.RED)); + } + } + + @Config(qualifiers = "sw600dp") + @Test + public void testSetPrimaryColorTablet() { + GlifLayout layout = new GlifLayout(context); + layout.setProgressBarShown(true); + layout.setPrimaryColor(ColorStateList.valueOf(Color.RED)); + assertWithMessage("Primary color should be red") + .that(layout.getPrimaryColor()) + .isEqualTo(ColorStateList.valueOf(Color.RED)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = layout.findViewById(R.id.suw_layout_progress); + assertWithMessage("Progress bar should be tinted red") + .that(progressBar.getIndeterminateTintList()) + .isEqualTo(ColorStateList.valueOf(Color.RED)); + assertWithMessage("Determinate progress bar should also be tinted red") + .that(progressBar.getProgressBackgroundTintList()) + .isEqualTo(ColorStateList.valueOf(Color.RED)); + } + + assertThat(((GlifPatternDrawable) getTabletBackground(layout)).getColor()).isEqualTo(Color.RED); + } + + @Test + public void testSetBackgroundBaseColor() { + GlifLayout layout = new GlifLayout(context); + layout.setPrimaryColor(ColorStateList.valueOf(Color.BLUE)); + layout.setBackgroundBaseColor(ColorStateList.valueOf(Color.RED)); + + assertThat(((GlifPatternDrawable) getPhoneBackground(layout)).getColor()).isEqualTo(Color.RED); + assertThat(layout.getBackgroundBaseColor().getDefaultColor()).isEqualTo(Color.RED); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testSetBackgroundBaseColorTablet() { + GlifLayout layout = new GlifLayout(context); + layout.setPrimaryColor(ColorStateList.valueOf(Color.BLUE)); + layout.setBackgroundBaseColor(ColorStateList.valueOf(Color.RED)); + + assertThat(((GlifPatternDrawable) getTabletBackground(layout)).getColor()).isEqualTo(Color.RED); + assertThat(layout.getBackgroundBaseColor().getDefaultColor()).isEqualTo(Color.RED); + } + + @Test + public void testSetBackgroundPatternedTrue() { + GlifLayout layout = new GlifLayout(context); + layout.setBackgroundPatterned(true); + + assertThat(getPhoneBackground(layout)).isInstanceOf(GlifPatternDrawable.class); + assertThat(layout.isBackgroundPatterned()).named("background is patterned").isTrue(); + } + + @Test + public void testSetBackgroundPatternedFalse() { + GlifLayout layout = new GlifLayout(context); + layout.setBackgroundPatterned(false); + + assertThat(getPhoneBackground(layout)).isInstanceOf(ColorDrawable.class); + assertThat(layout.isBackgroundPatterned()).named("background is patterned").isFalse(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testSetBackgroundPatternedTrueTablet() { + GlifLayout layout = new GlifLayout(context); + layout.setBackgroundPatterned(true); + + assertThat(getTabletBackground(layout)).isInstanceOf(GlifPatternDrawable.class); + assertThat(layout.isBackgroundPatterned()).named("background is patterned").isTrue(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testSetBackgroundPatternedFalseTablet() { + GlifLayout layout = new GlifLayout(context); + layout.setBackgroundPatterned(false); + + assertThat(getTabletBackground(layout)).isInstanceOf(ColorDrawable.class); + assertThat(layout.isBackgroundPatterned()).named("background is patterned").isFalse(); + } + + @Test + public void testNonGlifTheme() { + context = new ContextThemeWrapper(application, android.R.style.Theme); + new GlifLayout(context); + // Inflating with a non-GLIF theme should not crash + } + + @Test + public void testPeekProgressBarNull() { + GlifLayout layout = new GlifLayout(context); + assertWithMessage("PeekProgressBar should return null initially") + .that(layout.peekProgressBar()) + .isNull(); + } + + @Test + public void testPeekProgressBar() { + GlifLayout layout = new GlifLayout(context); + layout.setProgressBarShown(true); + assertWithMessage("Peek progress bar should return the bar after setProgressBarShown(true)") + .that(layout.peekProgressBar()) + .isNotNull(); + } + + @Test + public void testMixins() { + GlifLayout layout = new GlifLayout(context); + final HeaderMixin header = layout.getMixin(HeaderMixin.class); + assertThat(header).named("header").isInstanceOf(ColoredHeaderMixin.class); + + assertWithMessage("GlifLayout should have icon mixin") + .that(layout.getMixin(IconMixin.class)) + .isNotNull(); + assertWithMessage("GlifLayout should have progress bar mixin") + .that(layout.getMixin(ProgressBarMixin.class)) + .isNotNull(); + } + + @Test + public void testInflateFooter() { + GlifLayout layout = new GlifLayout(context); + + final View view = layout.inflateFooter(android.R.layout.simple_list_item_1); + assertThat(view.getId()).isEqualTo(android.R.id.text1); + assertThat((View) layout.findViewById(android.R.id.text1)).isNotNull(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testInflateFooterTablet() { + testInflateFooter(); + } + + @Test + public void testInflateFooterBlankTemplate() { + GlifLayout layout = new GlifLayout(context, R.layout.suw_glif_blank_template); + + final View view = layout.inflateFooter(android.R.layout.simple_list_item_1); + assertThat(view.getId()).isEqualTo(android.R.id.text1); + assertThat((View) layout.findViewById(android.R.id.text1)).isNotNull(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testInflateFooterBlankTemplateTablet() { + testInflateFooterBlankTemplate(); + } + + @Test + public void testFooterXml() { + GlifLayout layout = + new GlifLayout( + context, + Robolectric.buildAttributeSet() + .addAttribute(R.attr.suwFooter, "@android:layout/simple_list_item_1") + .build()); + + assertThat((View) layout.findViewById(android.R.id.text1)).isNotNull(); + } + + @Test + public void inflateStickyHeader_shouldAddViewToLayout() { + GlifLayout layout = new GlifLayout(context); + + final View view = layout.inflateStickyHeader(android.R.layout.simple_list_item_1); + assertThat(view.getId()).isEqualTo(android.R.id.text1); + assertThat((View) layout.findViewById(android.R.id.text1)).isNotNull(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void inflateStickyHeader_whenOnTablets_shouldAddViewToLayout() { + inflateStickyHeader_shouldAddViewToLayout(); + } + + @Test + public void inflateStickyHeader_whenInXml_shouldAddViewToLayout() { + GlifLayout layout = + new GlifLayout( + context, + Robolectric.buildAttributeSet() + .addAttribute(R.attr.suwStickyHeader, "@android:layout/simple_list_item_1") + .build()); + + assertThat((View) layout.findViewById(android.R.id.text1)).isNotNull(); + } + + @Test + public void inflateStickyHeader_whenOnBlankTemplate_shouldAddViewToLayout() { + GlifLayout layout = new GlifLayout(context, R.layout.suw_glif_blank_template); + + final View view = layout.inflateStickyHeader(android.R.layout.simple_list_item_1); + assertThat(view.getId()).isEqualTo(android.R.id.text1); + assertThat((View) layout.findViewById(android.R.id.text1)).isNotNull(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void inflateStickyHeader_whenOnBlankTemplateTablet_shouldAddViewToLayout() { + inflateStickyHeader_whenOnBlankTemplate_shouldAddViewToLayout(); + } + + @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.NEWEST_SDK) + @Test + public void createFromXml_shouldSetLayoutFullscreen_whenLayoutFullscreenIsNotSet() { + GlifLayout layout = new GlifLayout(context, Robolectric.buildAttributeSet().build()); + if (VERSION.SDK_INT >= VERSION_CODES.M) { + assertThat(layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + .isEqualTo(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + } + + @Test + public void createFromXml_shouldNotSetLayoutFullscreen_whenLayoutFullscreenIsFalse() { + GlifLayout layout = + new GlifLayout( + context, + Robolectric.buildAttributeSet() + .addAttribute(R.attr.suwLayoutFullscreen, "false") + .build()); + + assertThat(layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN).isEqualTo(0); + } + + private Drawable getPhoneBackground(GlifLayout layout) { + final StatusBarBackgroundLayout patternBg = layout.findManagedViewById(R.id.suw_pattern_bg); + return patternBg.getStatusBarBackground(); + } + + private Drawable getTabletBackground(GlifLayout layout) { + final View patternBg = layout.findManagedViewById(R.id.suw_pattern_bg); + return patternBg.getBackground(); + } + + private void assertDefaultTemplateInflated(GlifLayout layout) { + View title = layout.findViewById(R.id.suw_layout_title); + assertWithMessage("@id/suw_layout_title should not be null").that(title).isNotNull(); + + View icon = layout.findViewById(R.id.suw_layout_icon); + assertWithMessage("@id/suw_layout_icon should not be null").that(icon).isNotNull(); + + View scrollView = layout.findViewById(R.id.suw_scroll_view); + assertWithMessage("@id/suw_scroll_view should be a ScrollView") + .that(scrollView instanceof ScrollView) + .isTrue(); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java b/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java index aa2cce3..ba3e2c5 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java @@ -23,96 +23,93 @@ import static org.robolectric.RuntimeEnvironment.application; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; - -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) -@RunWith(SuwLibRobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) +@RunWith(RobolectricTestRunner.class) public class ConsecutiveTapsGestureDetectorTest { - @Mock - private ConsecutiveTapsGestureDetector.OnConsecutiveTapsListener mListener; + @Mock private ConsecutiveTapsGestureDetector.OnConsecutiveTapsListener listener; - private ConsecutiveTapsGestureDetector mDetector; - private int mSlop; - private int mTapTimeout; + private ConsecutiveTapsGestureDetector detector; + private int slop; + private int tapTimeout; - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); - View view = new View(application); - view.measure(500, 500); - view.layout(0, 0, 500, 500); - mDetector = new ConsecutiveTapsGestureDetector(mListener, view); + View view = new View(application); + view.measure(500, 500); + view.layout(0, 0, 500, 500); + detector = new ConsecutiveTapsGestureDetector(listener, view); - mSlop = ViewConfiguration.get(application).getScaledDoubleTapSlop(); - mTapTimeout = ViewConfiguration.getDoubleTapTimeout(); - } + slop = ViewConfiguration.get(application).getScaledDoubleTapSlop(); + tapTimeout = ViewConfiguration.getDoubleTapTimeout(); + } - @Test - public void onTouchEvent_shouldTriggerCallbackOnFourTaps() { - InOrder inOrder = inOrder(mListener); + @Test + public void onTouchEvent_shouldTriggerCallbackOnFourTaps() { + InOrder inOrder = inOrder(listener); - tap(0, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(0, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(100, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + tap(100, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); - tap(200, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(3)); + tap(200, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(3)); - tap(300, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(4)); - } + tap(300, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(4)); + } - @Test - public void onTouchEvent_tapOnDifferentLocation_shouldResetCounter() { - InOrder inOrder = inOrder(mListener); + @Test + public void onTouchEvent_tapOnDifferentLocation_shouldResetCounter() { + InOrder inOrder = inOrder(listener); - tap(0, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(0, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(100, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + tap(100, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); - tap(200, 25f + mSlop * 2, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(200, 25f + slop * 2, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(300, 25f + mSlop * 2, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); - } + tap(300, 25f + slop * 2, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); + } - @Test - public void onTouchEvent_tapAfterTimeout_shouldResetCounter() { - InOrder inOrder = inOrder(mListener); + @Test + public void onTouchEvent_tapAfterTimeout_shouldResetCounter() { + InOrder inOrder = inOrder(listener); - tap(0, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(0, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(100, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + tap(100, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); - tap(200 + mTapTimeout, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(200 + tapTimeout, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(300 + mTapTimeout, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); - } + tap(300 + tapTimeout, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); + } - private void tap(int timeMillis, float x, float y) { - mDetector.onTouchEvent( - MotionEvent.obtain(timeMillis, timeMillis, MotionEvent.ACTION_DOWN, x, y, 0)); - mDetector.onTouchEvent( - MotionEvent.obtain(timeMillis, timeMillis + 10, MotionEvent.ACTION_UP, x, y, 0)); - } + private void tap(int timeMillis, float x, float y) { + detector.onTouchEvent( + MotionEvent.obtain(timeMillis, timeMillis, MotionEvent.ACTION_DOWN, x, y, 0)); + detector.onTouchEvent( + MotionEvent.obtain(timeMillis, timeMillis + 10, MotionEvent.ACTION_UP, x, y, 0)); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java b/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java index 40e5da8..b51e875 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java @@ -16,12 +16,8 @@ package com.android.setupwizardlib.items; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.same; @@ -31,152 +27,157 @@ import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; import android.content.Context; -import android.text.TextUtils; import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; import android.widget.LinearLayout; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.items.ButtonItem.OnClickListener; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class ButtonItemTest { - private ViewGroup mParent; - private Context mContext; - - @Before - public void setUp() { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - mParent = new LinearLayout(mContext); - } - - @Test - public void testDefaultItem() { - ButtonItem item = new ButtonItem(); - - assertTrue("ButtonItem should be enabled by default", item.isEnabled()); - assertEquals("ButtonItem should return count = 0", 0, item.getCount()); - assertEquals("ButtonItem should return layout resource = 0", 0, item.getLayoutResource()); - assertEquals("Default theme should be @style/SuwButtonItem", R.style.SuwButtonItem, - item.getTheme()); - assertNull("Default text should be null", item.getText()); - } - - @Test - public void testOnBindView() { - ButtonItem item = new ButtonItem(); - - try { - item.onBindView(new View(mContext)); - fail("Calling onBindView on ButtonItem should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - // pass - } - } - - @Test - public void testCreateButton() { - TestButtonItem item = new TestButtonItem(); - final Button button = item.createButton(mParent); - - assertTrue("Default button should be enabled", button.isEnabled()); - assertTrue("Default button text should be empty", TextUtils.isEmpty(button.getText())); - } - - @Test - public void testButtonItemSetsItsId() { - TestButtonItem item = new TestButtonItem(); - final int id = 12345; - item.setId(id); - - assertEquals("Button's id should be set", item.createButton(mParent).getId(), id); - } - - @Test - public void testCreateButtonTwice() { - TestButtonItem item = new TestButtonItem(); - final Button button = item.createButton(mParent); - - FrameLayout frameLayout = new FrameLayout(mContext); - frameLayout.addView(button); - - final Button button2 = item.createButton(mParent); - assertSame("createButton should be reused", button, button2); - assertNull("Should be removed from parent after createButton", button2.getParent()); + private ViewGroup parent; + private Context context; + + @Before + public void setUp() { + context = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + parent = new LinearLayout(context); + } + + @Test + public void testDefaultItem() { + ButtonItem item = new ButtonItem(); + + assertThat(item.isEnabled()).named("enabled").isTrue(); + assertThat(item.getCount()).named("count").isEqualTo(0); + assertThat(item.getLayoutResource()).named("layout resource").isEqualTo(0); + assertThat(item.getTheme()).named("theme").isEqualTo(R.style.SuwButtonItem); + assertThat(item.getText()).named("text").isNull(); + } + + @Test + public void testOnBindView() { + ButtonItem item = new ButtonItem(); + + try { + item.onBindView(new View(context)); + fail("Calling onBindView on ButtonItem should throw UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // pass } - - @Test - public void testSetEnabledTrue() { - TestButtonItem item = new TestButtonItem(); - item.setEnabled(true); - - final Button button = item.createButton(mParent); - assertTrue("ButtonItem should be enabled", item.isEnabled()); - assertTrue("Button should be enabled", button.isEnabled()); - } - - @Test - public void testSetEnabledFalse() { - TestButtonItem item = new TestButtonItem(); - item.setEnabled(false); - - final Button button = item.createButton(mParent); - assertFalse("ButtonItem should be disabled", item.isEnabled()); - assertFalse("Button should be disabled", button.isEnabled()); - } - - @Test - public void testSetText() { - TestButtonItem item = new TestButtonItem(); - item.setText("lorem ipsum"); - - final Button button = item.createButton(mParent); - assertEquals("ButtonItem text should be \"lorem ipsum\"", "lorem ipsum", item.getText()); - assertEquals("Button text should be \"lorem ipsum\"", "lorem ipsum", button.getText()); - } - - @Test - public void testSetTheme() { - TestButtonItem item = new TestButtonItem(); - item.setTheme(R.style.SuwButtonItem_Colored); - - final Button button = item.createButton(mParent); - assertEquals("ButtonItem theme should be SuwButtonItem.Colored", - R.style.SuwButtonItem_Colored, item.getTheme()); - assertNotNull(button.getContext().getTheme()); - } - - @Test - public void testOnClickListener() { - TestButtonItem item = new TestButtonItem(); - final OnClickListener listener = mock(OnClickListener.class); - item.setOnClickListener(listener); - - verify(listener, never()).onClick(any(ButtonItem.class)); - - final Button button = item.createButton(mParent); - button.performClick(); - - verify(listener).onClick(same(item)); - } - - private static class TestButtonItem extends ButtonItem { - - @Override - public Button createButton(ViewGroup parent) { - // Make this method public for testing - return super.createButton(parent); - } + } + + @Test + public void testCreateButton() { + TestButtonItem item = new TestButtonItem(); + final Button button = item.createButton(parent); + + assertThat(button.isEnabled()).named("enabled").isTrue(); + assertThat(button.getText().toString()).isEmpty(); + } + + @Test + public void testButtonItemSetsItsId() { + TestButtonItem item = new TestButtonItem(); + final int id = 12345; + item.setId(id); + + assertWithMessage("Button's id should be set") + .that(item.createButton(parent).getId()) + .isEqualTo(id); + } + + @Test + public void testCreateButtonTwice() { + TestButtonItem item = new TestButtonItem(); + final Button button = item.createButton(parent); + + FrameLayout frameLayout = new FrameLayout(context); + frameLayout.addView(button); + + final Button button2 = item.createButton(parent); + assertWithMessage("createButton should be reused").that(button2).isSameAs(button); + assertWithMessage("Should be removed from parent after createButton") + .that(button2.getParent()) + .isNull(); + } + + @Test + public void testSetEnabledTrue() { + TestButtonItem item = new TestButtonItem(); + item.setEnabled(true); + + final Button button = item.createButton(parent); + assertWithMessage("ButtonItem should be enabled").that(item.isEnabled()).isTrue(); + assertWithMessage("Button should be enabled").that(button.isEnabled()).isTrue(); + } + + @Test + public void testSetEnabledFalse() { + TestButtonItem item = new TestButtonItem(); + item.setEnabled(false); + + final Button button = item.createButton(parent); + assertWithMessage("ButtonItem should be disabled").that(item.isEnabled()).isFalse(); + assertWithMessage("Button should be disabled").that(button.isEnabled()).isFalse(); + } + + @Test + public void testSetText() { + TestButtonItem item = new TestButtonItem(); + item.setText("lorem ipsum"); + + final Button button = item.createButton(parent); + assertWithMessage("ButtonItem text should be \"lorem ipsum\"") + .that(item.getText().toString()) + .isEqualTo("lorem ipsum"); + assertWithMessage("Button text should be \"lorem ipsum\"") + .that(button.getText().toString()) + .isEqualTo("lorem ipsum"); + } + + @Test + public void testSetTheme() { + TestButtonItem item = new TestButtonItem(); + item.setTheme(R.style.SuwButtonItem_Colored); + + final Button button = item.createButton(parent); + assertWithMessage("ButtonItem theme should be SuwButtonItem.Colored") + .that(item.getTheme()) + .isEqualTo(R.style.SuwButtonItem_Colored); + assertThat(button.getContext().getTheme()).isNotNull(); + } + + @Test + public void testOnClickListener() { + TestButtonItem item = new TestButtonItem(); + final OnClickListener listener = mock(OnClickListener.class); + item.setOnClickListener(listener); + + verify(listener, never()).onClick(any(ButtonItem.class)); + + final Button button = item.createButton(parent); + button.performClick(); + + verify(listener).onClick(same(item)); + } + + private static class TestButtonItem extends ButtonItem { + + @Override + public Button createButton(ViewGroup parent) { + // Make this method public for testing + return super.createButton(parent); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java b/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java index ecaec71..3cbc576 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java @@ -16,289 +16,308 @@ package com.android.setupwizardlib.items; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class ItemGroupTest { - private static final Item CHILD_1 = new EqualsItem("Child 1"); - private static final Item CHILD_2 = new EqualsItem("Child 2"); - private static final Item CHILD_3 = new EqualsItem("Child 3"); - private static final Item CHILD_4 = new EqualsItem("Child 4"); - - private ItemGroup mItemGroup; - - @Mock - private ItemHierarchy.Observer mObserver; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mItemGroup = new ItemGroup(); - mItemGroup.registerObserver(mObserver); - } - - @Test - public void testGroup() { - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - assertSame("Item at position 0 should be child1", CHILD_1, mItemGroup.getItemAt(0)); - assertSame("Item at position 1 should be child2", CHILD_2, mItemGroup.getItemAt(1)); - assertEquals("Should have 2 children", 2, mItemGroup.getCount()); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(mItemGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(mItemGroup), eq(1), eq(1)); - } - - @Test - public void testRemoveChild() { - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - mItemGroup.addChild(CHILD_3); - - mItemGroup.removeChild(CHILD_2); - - assertSame("Item at position 0 should be child1", CHILD_1, mItemGroup.getItemAt(0)); - assertSame("Item at position 1 should be child3", CHILD_3, mItemGroup.getItemAt(1)); - assertEquals("Should have 2 children", 2, mItemGroup.getCount()); - - verify(mObserver).onItemRangeRemoved(eq(mItemGroup), eq(1), eq(1)); - } - - @Test - public void testClear() { - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - mItemGroup.clear(); - - assertEquals("Should have 0 child", 0, mItemGroup.getCount()); - - verify(mObserver).onItemRangeRemoved(eq(mItemGroup), eq(0), eq(2)); - } - - @Test - public void testNestedGroup() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - parentGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - childGroup.addChild(CHILD_3); - parentGroup.addChild(childGroup); - parentGroup.addChild(CHILD_4); - - assertSame("Position 0 should be child 1", CHILD_1, parentGroup.getItemAt(0)); - assertSame("Position 1 should be child 2", CHILD_2, parentGroup.getItemAt(1)); - assertSame("Position 2 should be child 3", CHILD_3, parentGroup.getItemAt(2)); - assertSame("Position 3 should be child 4", CHILD_4, parentGroup.getItemAt(3)); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNestedGroupClearNotification() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - parentGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - childGroup.addChild(CHILD_3); - parentGroup.addChild(childGroup); - parentGroup.addChild(CHILD_4); - - childGroup.clear(); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); - verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(1), eq(2)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNestedGroupRemoveNotification() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - parentGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - childGroup.addChild(CHILD_3); - parentGroup.addChild(childGroup); - parentGroup.addChild(CHILD_4); - - childGroup.removeChild(CHILD_3); - childGroup.removeChild(CHILD_2); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(2), eq(1)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(1), eq(1)); - verifyNoMoreInteractions(mObserver); + private static final Item CHILD_1 = new EqualsItem("Child 1"); + private static final Item CHILD_2 = new EqualsItem("Child 2"); + private static final Item CHILD_3 = new EqualsItem("Child 3"); + private static final Item CHILD_4 = new EqualsItem("Child 4"); + + private ItemGroup itemGroup; + + @Mock private ItemHierarchy.Observer observer; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + itemGroup = new ItemGroup(); + itemGroup.registerObserver(observer); + } + + @Test + public void testGroup() { + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + assertWithMessage("Item at position 0 should be child1") + .that(itemGroup.getItemAt(0)) + .isSameAs(CHILD_1); + assertWithMessage("Item at position 1 should be child2") + .that(itemGroup.getItemAt(1)) + .isSameAs(CHILD_2); + assertWithMessage("Should have 2 children").that(itemGroup.getCount()).isEqualTo(2); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(itemGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(itemGroup), eq(1), eq(1)); + } + + @Test + public void testRemoveChild() { + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + itemGroup.addChild(CHILD_3); + + itemGroup.removeChild(CHILD_2); + + assertWithMessage("Item at position 0 should be child1") + .that(itemGroup.getItemAt(0)) + .isSameAs(CHILD_1); + assertWithMessage("Item at position 1 should be child3") + .that(itemGroup.getItemAt(1)) + .isSameAs(CHILD_3); + assertWithMessage("Should have 2 children").that(itemGroup.getCount()).isEqualTo(2); + + verify(observer).onItemRangeRemoved(eq(itemGroup), eq(1), eq(1)); + } + + @Test + public void testClear() { + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + itemGroup.clear(); + + assertWithMessage("Should have 0 child").that(itemGroup.getCount()).isEqualTo(0); + + verify(observer).onItemRangeRemoved(eq(itemGroup), eq(0), eq(2)); + } + + @Test + public void testNestedGroup() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + parentGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + childGroup.addChild(CHILD_3); + parentGroup.addChild(childGroup); + parentGroup.addChild(CHILD_4); + + assertWithMessage("Position 0 should be child 1") + .that(parentGroup.getItemAt(0)) + .isSameAs(CHILD_1); + assertWithMessage("Position 1 should be child 2") + .that(parentGroup.getItemAt(1)) + .isSameAs(CHILD_2); + assertWithMessage("Position 2 should be child 3") + .that(parentGroup.getItemAt(2)) + .isSameAs(CHILD_3); + assertWithMessage("Position 3 should be child 4") + .that(parentGroup.getItemAt(3)) + .isSameAs(CHILD_4); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupClearNotification() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + parentGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + childGroup.addChild(CHILD_3); + parentGroup.addChild(childGroup); + parentGroup.addChild(CHILD_4); + + childGroup.clear(); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); + verify(observer).onItemRangeRemoved(eq(parentGroup), eq(1), eq(2)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupRemoveNotification() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + parentGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + childGroup.addChild(CHILD_3); + parentGroup.addChild(childGroup); + parentGroup.addChild(CHILD_4); + + childGroup.removeChild(CHILD_3); + childGroup.removeChild(CHILD_2); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(2), eq(1)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(1), eq(1)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupClear() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + parentGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + childGroup.addChild(CHILD_3); + parentGroup.addChild(childGroup); + + childGroup.clear(); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(1), eq(2)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupRemoveLastChild() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup1 = new ItemGroup(); + ItemGroup childGroup2 = new ItemGroup(); + parentGroup.registerObserver(observer); + + childGroup1.addChild(CHILD_1); + childGroup1.addChild(CHILD_2); + parentGroup.addChild(childGroup1); + childGroup2.addChild(CHILD_3); + childGroup2.addChild(CHILD_4); + parentGroup.addChild(childGroup2); + + childGroup2.removeChild(CHILD_4); + childGroup2.removeChild(CHILD_3); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(2)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(2), eq(2)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(3), eq(1)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(2), eq(1)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupClearOnlyChild() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + childGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + parentGroup.addChild(childGroup); + + childGroup.clear(); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(2)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(0), eq(2)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNotifyChange() { + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + CHILD_2.setTitle("Child 2 modified"); + + verify(observer).onItemRangeChanged(eq(itemGroup), eq(1), eq(1)); + } + + @Test + public void testEmptyChildGroup() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + + parentGroup.addChild(CHILD_1); + parentGroup.addChild(childGroup); + parentGroup.addChild(CHILD_2); + + assertWithMessage("Position 0 should be child 1") + .that(parentGroup.getItemAt(0)) + .isSameAs(CHILD_1); + assertWithMessage("Position 1 should be child 2") + .that(parentGroup.getItemAt(1)) + .isSameAs(CHILD_2); + } + + @Test + public void testFindItemById() { + CHILD_1.setId(12345); + CHILD_2.setId(23456); + + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + assertWithMessage("Find item 23456 should return child 2") + .that(itemGroup.findItemById(23456)) + .isSameAs(CHILD_2); + } + + @Test + public void testFindItemByIdNotFound() { + CHILD_1.setId(12345); + CHILD_2.setId(23456); + + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + assertWithMessage("ID not found should return null") + .that(itemGroup.findItemById(56789)) + .isNull(); + } + + /** + * This class will always return true on {@link #equals(Object)}. Used to ensure that ItemGroup is + * using identity rather than equals(). Be sure to use assertSame rather than assertEquals when + * comparing items of this class. + */ + private static class EqualsItem extends Item { + + EqualsItem(String name) { + setTitle(name); } - @Test - public void testNestedGroupClear() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - parentGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - childGroup.addChild(CHILD_3); - parentGroup.addChild(childGroup); - - childGroup.clear(); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(1), eq(2)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNestedGroupRemoveLastChild() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup1 = new ItemGroup(); - ItemGroup childGroup2 = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - childGroup1.addChild(CHILD_1); - childGroup1.addChild(CHILD_2); - parentGroup.addChild(childGroup1); - childGroup2.addChild(CHILD_3); - childGroup2.addChild(CHILD_4); - parentGroup.addChild(childGroup2); - - childGroup2.removeChild(CHILD_4); - childGroup2.removeChild(CHILD_3); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(2)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(2), eq(2)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(3), eq(1)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(2), eq(1)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNestedGroupClearOnlyChild() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - childGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - parentGroup.addChild(childGroup); - - childGroup.clear(); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(2)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(0), eq(2)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNotifyChange() { - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - CHILD_2.setTitle("Child 2 modified"); - - verify(mObserver).onItemRangeChanged(eq(mItemGroup), eq(1), eq(1)); - } - - @Test - public void testEmptyChildGroup() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - - parentGroup.addChild(CHILD_1); - parentGroup.addChild(childGroup); - parentGroup.addChild(CHILD_2); - - assertSame("Position 0 should be child 1", CHILD_1, parentGroup.getItemAt(0)); - assertSame("Position 1 should be child 2", CHILD_2, parentGroup.getItemAt(1)); + @Override + public int hashCode() { + return 1; } - @Test - public void testFindItemById() { - CHILD_1.setId(12345); - CHILD_2.setId(23456); - - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - assertSame("Find item 23456 should return child 2", - CHILD_2, mItemGroup.findItemById(23456)); - } - - @Test - public void testFindItemByIdNotFound() { - CHILD_1.setId(12345); - CHILD_2.setId(23456); - - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - assertNull("ID not found should return null", mItemGroup.findItemById(56789)); + @Override + public boolean equals(Object obj) { + return obj instanceof Item; } - /** - * This class will always return true on {@link #equals(Object)}. Used to ensure that ItemGroup - * is using identity rather than equals(). Be sure to use assertSame rather than assertEquals - * when comparing items of this class. - */ - private static class EqualsItem extends Item { - - EqualsItem(String name) { - setTitle(name); - } - - @Override - public int hashCode() { - return 1; - } - - @Override - public boolean equals(Object obj) { - return obj instanceof Item; - } - - @Override - public String toString() { - return "EqualsItem{title=" + getTitle() + "}"; - } + @Override + public String toString() { + return "EqualsItem{title=" + getTitle() + "}"; } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/robolectric/ExternalResources.java b/library/test/robotest/src/com/android/setupwizardlib/robolectric/ExternalResources.java new file mode 100644 index 0000000..06ef508 --- /dev/null +++ b/library/test/robotest/src/com/android/setupwizardlib/robolectric/ExternalResources.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2018 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.robolectric; + +import static org.robolectric.RuntimeEnvironment.application; +import static org.robolectric.Shadows.shadowOf; + +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import androidx.annotation.AnyRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.util.DisplayMetrics; +import java.util.HashMap; +import java.util.Map; +import org.robolectric.res.ResName; +import org.robolectric.res.ResType; +import org.robolectric.res.TypedResource; +import org.robolectric.shadows.ShadowPackageManager; + +/** + * Utility class to inject resources for an "external" application in Robolectric tests. This can be + * used with {@link org.robolectric.shadows.ShadowPackageManager#resources} to simulate loading + * resources from another package. + */ +public final class ExternalResources { + + public static Resources injectExternalResources(String packageName) { + return injectExternalResources(createPackageInfo(packageName)); + } + + public static Resources injectExternalResources(PackageInfo packageInfo) { + try { + application.getPackageManager().getPackageInfo(packageInfo.packageName, 0); + } catch (NameNotFoundException e) { + // Add the package if it does not exist + shadowOf(application.getPackageManager()).addPackage(packageInfo); + } + Resources resources = Resources.forPackageName(packageInfo.packageName); + ShadowPackageManager.resources.put(packageInfo.packageName, resources); + return resources; + } + + /** + * Constructed resources for testing, representing resources external to the current package under + * test. + */ + public static class Resources extends android.content.res.Resources { + + private final String packageName; + + public static Resources forPackageName(String packageName) { + android.content.res.Resources res = application.getResources(); + return new Resources( + packageName, res.getAssets(), res.getDisplayMetrics(), res.getConfiguration()); + } + + private Resources( + String packageName, AssetManager assets, DisplayMetrics metrics, Configuration config) { + super(assets, metrics, config); + this.packageName = packageName; + } + + @Override + public int getIdentifier(String name, String defType, String defPackage) { + Integer resourceId = resourceIds.get(ResName.qualifyResName(name, defPackage, defType)); + if (resourceId == null) { + return 0; + } + return resourceId; + } + + @Override + public int getInteger(int id) { + return (int) get(id, ResType.INTEGER); + } + + public void putInteger(String name, int value) { + put( + ResName.qualifyResName(name, packageName, "integer"), + new TypedResource<>(value, ResType.INTEGER, null)); + } + + @Override + public int getColor(int id) { + return (int) get(id, ResType.COLOR); + } + + @Override + public int getColor(int id, @Nullable Theme theme) { + return (int) get(id, ResType.COLOR); + } + + public void putColor(String name, int value) { + put( + ResName.qualifyResName(name, packageName, "color"), + new TypedResource<>(value, ResType.COLOR, null)); + } + + @NonNull + @Override + public CharSequence getText(int id) { + return (CharSequence) get(id, ResType.CHAR_SEQUENCE); + } + + @NonNull + @Override + public String getString(int id) { + return get(id, ResType.CHAR_SEQUENCE).toString(); + } + + public void putText(String name, CharSequence value) { + put( + ResName.qualifyResName(name, packageName, "string"), + new TypedResource<>(value, ResType.CHAR_SEQUENCE, null)); + } + + private final Map<Integer, TypedResource<?>> overrideResources = new HashMap<>(); + private final Map<ResName, Integer> resourceIds = new HashMap<>(); + private int nextId = 1; + + private <T> void put(ResName resName, TypedResource<T> value) { + int id = nextId++; + overrideResources.put(id, value); + resourceIds.put(resName, id); + } + + private Object get(@AnyRes int id, ResType type) { + TypedResource<?> override = overrideResources.get(id); + if (override != null && override.getResType() == type) { + return override.getData(); + } + throw new NotFoundException(); + } + } + + private static PackageInfo createPackageInfo(String packageName) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + return packageInfo; + } + + private ExternalResources() {} +} diff --git a/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java b/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java deleted file mode 100644 index 61baa23..0000000 --- a/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2017 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.robolectric; - -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; -import org.robolectric.RobolectricTestRunner; - -public class SuwLibRobolectricTestRunner extends RobolectricTestRunner { - - public SuwLibRobolectricTestRunner(Class<?> testClass) throws InitializationError { - super(testClass); - } - - @Override - protected void runChild(FrameworkMethod method, RunNotifier notifier) { - System.out.println("===== Running " + method + " ====="); - super.runChild(method, notifier); - } -} diff --git a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java b/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java deleted file mode 100644 index f1d37c8..0000000 --- a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2018 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.shadow; - -import android.util.Log; - -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -@Implements(Log.class) -public class ShadowLog extends org.robolectric.shadows.ShadowLog { - - public static boolean sWtfIsFatal = true; - - public static class TerribleFailure extends RuntimeException { - - public TerribleFailure(String msg, Throwable cause) { - super(msg, cause); - } - } - - @Implementation - public static void wtf(String tag, String msg) { - org.robolectric.shadows.ShadowLog.wtf(tag, msg); - if (sWtfIsFatal) { - throw new TerribleFailure(msg, null); - } - } - - @Implementation - public static void wtf(String tag, String msg, Throwable throwable) { - org.robolectric.shadows.ShadowLog.wtf(tag, msg, throwable); - if (sWtfIsFatal) { - throw new TerribleFailure(msg, throwable); - } - } -} diff --git a/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java b/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java index 3aafa7d..3bc9fd1 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java @@ -17,8 +17,7 @@ package com.android.setupwizardlib.span; import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertSame; +import static com.google.common.truth.Truth.assertWithMessage; import static org.robolectric.RuntimeEnvironment.application; import android.content.Context; @@ -27,82 +26,83 @@ import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.method.LinkMovementMethod; import android.widget.TextView; - -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -@RunWith(SuwLibRobolectricTestRunner.class) +@RunWith(RobolectricTestRunner.class) public class LinkSpanTest { - @Test - public void onClick_shouldCallListenerOnContext() { - final TestContext context = new TestContext(application); - final TextView textView = new TextView(context); - final LinkSpan linkSpan = new LinkSpan("test_id"); - - linkSpan.onClick(textView); - - assertSame("Clicked LinkSpan should be passed to setup", linkSpan, context.clickedSpan); - } - - @Test - public void onClick_contextDoesNotImplementOnClickListener_shouldBeNoOp() { - final TextView textView = new TextView(application); - final LinkSpan linkSpan = new LinkSpan("test_id"); - - linkSpan.onClick(textView); - - // This would be no-op, because the context doesn't implement LinkSpan.OnClickListener. - // Just check that no uncaught exception here. + @Test + public void onClick_shouldCallListenerOnContext() { + final TestContext context = new TestContext(application); + final TextView textView = new TextView(context); + final LinkSpan linkSpan = new LinkSpan("test_id"); + + linkSpan.onClick(textView); + + assertWithMessage("Clicked LinkSpan should be passed to setup") + .that(context.clickedSpan) + .isSameAs(linkSpan); + } + + @Test + public void onClick_contextDoesNotImplementOnClickListener_shouldBeNoOp() { + final TextView textView = new TextView(application); + final LinkSpan linkSpan = new LinkSpan("test_id"); + + linkSpan.onClick(textView); + + // This would be no-op, because the context doesn't implement LinkSpan.OnClickListener. + // Just check that no uncaught exception here. + } + + @Test + public void onClick_contextWrapsOnClickListener_shouldCallWrappedListener() { + final TestContext context = new TestContext(application); + final Context wrapperContext = new ContextWrapper(context); + final TextView textView = new TextView(wrapperContext); + final LinkSpan linkSpan = new LinkSpan("test_id"); + + linkSpan.onClick(textView); + assertWithMessage("Clicked LinkSpan should be passed to setup") + .that(context.clickedSpan) + .isSameAs(linkSpan); + } + + @Test + public void onClick_shouldClearSelection() { + final TestContext context = new TestContext(application); + final TextView textView = new TextView(context); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setFocusable(true); + textView.setFocusableInTouchMode(true); + final LinkSpan linkSpan = new LinkSpan("test_id"); + + SpannableStringBuilder text = new SpannableStringBuilder("Lorem ipsum dolor sit"); + textView.setText(text); + text.setSpan(linkSpan, /* start= */ 0, /* end= */ 5, /* flags= */ 0); + // Simulate the touch effect set by TextView when touched. + Selection.setSelection(text, /* start= */ 0, /* stop= */ 5); + + linkSpan.onClick(textView); + + assertThat(Selection.getSelectionStart(textView.getText())).isEqualTo(0); + assertThat(Selection.getSelectionEnd(textView.getText())).isEqualTo(0); + } + + @SuppressWarnings("deprecation") + private static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { + + public LinkSpan clickedSpan = null; + + TestContext(Context base) { + super(base); } - @Test - public void onClick_contextWrapsOnClickListener_shouldCallWrappedListener() { - final TestContext context = new TestContext(application); - final Context wrapperContext = new ContextWrapper(context); - final TextView textView = new TextView(wrapperContext); - final LinkSpan linkSpan = new LinkSpan("test_id"); - - - linkSpan.onClick(textView); - assertSame("Clicked LinkSpan should be passed to setup", linkSpan, context.clickedSpan); - } - - @Test - public void onClick_shouldClearSelection() { - final TestContext context = new TestContext(application); - final TextView textView = new TextView(context); - textView.setMovementMethod(LinkMovementMethod.getInstance()); - textView.setFocusable(true); - textView.setFocusableInTouchMode(true); - final LinkSpan linkSpan = new LinkSpan("test_id"); - - SpannableStringBuilder text = new SpannableStringBuilder("Lorem ipsum dolor sit"); - textView.setText(text); - text.setSpan(linkSpan, /* start= */ 0, /* end= */ 5, /* flags= */ 0); - // Simulate the touch effect set by TextView when touched. - Selection.setSelection(text, /* start= */ 0, /* end= */ 5); - - linkSpan.onClick(textView); - - assertThat(Selection.getSelectionStart(textView.getText())).isEqualTo(0); - assertThat(Selection.getSelectionEnd(textView.getText())).isEqualTo(0); - } - - @SuppressWarnings("deprecation") - private static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { - - public LinkSpan clickedSpan = null; - - TestContext(Context base) { - super(base); - } - - @Override - public void onClick(LinkSpan span) { - clickedSpan = span; - } + @Override + public void onClick(LinkSpan span) { + clickedSpan = span; } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java index ec3622d..9b99a68 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java @@ -16,105 +16,93 @@ package com.android.setupwizardlib.template; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; import android.content.Context; import android.view.View; import android.view.ViewGroup; -import android.widget.AbsListView.OnScrollListener; import android.widget.BaseAdapter; import android.widget.ListView; - -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) -@RunWith(SuwLibRobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) +@RunWith(RobolectricTestRunner.class) public class ListViewScrollHandlingDelegateTest { - @Mock - private RequireScrollMixin mRequireScrollMixin; + @Mock private RequireScrollMixin requireScrollMixin; - private ListView mListView; - private ListViewScrollHandlingDelegate mDelegate; - private ArgumentCaptor<OnScrollListener> mListenerCaptor; + private ListView listView; + private ListViewScrollHandlingDelegate delegate; - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); - mListView = spy(new TestListView(application)); - mDelegate = new ListViewScrollHandlingDelegate(mRequireScrollMixin, mListView); + listView = new TestListView(application); + delegate = new ListViewScrollHandlingDelegate(requireScrollMixin, listView); - mListenerCaptor = ArgumentCaptor.forClass(OnScrollListener.class); - doNothing().when(mListView).setOnScrollListener(mListenerCaptor.capture()); + listView.layout(0, 0, 50, 50); + } - mListView.layout(0, 0, 50, 50); - } + @Test + public void testRequireScroll() throws Throwable { + delegate.startListening(); - @Test - public void testRequireScroll() throws Throwable { - mDelegate.startListening(); + verify(requireScrollMixin).notifyScrollabilityChange(true); + } - verify(mRequireScrollMixin).notifyScrollabilityChange(true); - } + @Test + public void testScrolledToBottom() throws Throwable { + delegate.startListening(); - @Test - public void testScrolledToBottom() throws Throwable { - mDelegate.startListening(); + verify(requireScrollMixin).notifyScrollabilityChange(true); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); + Shadows.shadowOf(listView).getOnScrollListener().onScroll(listView, 2, 20, 20); - doReturn(20).when(mListView).getLastVisiblePosition(); - mListenerCaptor.getValue().onScroll(mListView, 2, 20, 20); + verify(requireScrollMixin).notifyScrollabilityChange(false); + } - verify(mRequireScrollMixin).notifyScrollabilityChange(false); - } + @Test + public void testPageScrollDown() throws Throwable { + delegate.pageScrollDown(); + assertThat(Shadows.shadowOf(listView).getLastSmoothScrollByDistance()).isEqualTo(50); + } - @Test - public void testPageScrollDown() throws Throwable { - mDelegate.pageScrollDown(); - verify(mListView).smoothScrollBy(eq(50), anyInt()); - } + private static class TestListView extends ListView { + + TestListView(Context context) { + super(context); + setAdapter( + new BaseAdapter() { + @Override + public int getCount() { + return 20; + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } - private static class TestListView extends ListView { - - TestListView(Context context) { - super(context); - setAdapter(new BaseAdapter() { - @Override - public int getCount() { - return 20; - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - return new View(parent.getContext()); - } - }); - } + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return new View(parent.getContext()); + } + }); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java index c641449..fe45d5f 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java @@ -16,13 +16,10 @@ package com.android.setupwizardlib.template; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -33,135 +30,138 @@ import android.annotation.SuppressLint; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; - +import com.android.setupwizardlib.GlifLayout; import com.android.setupwizardlib.TemplateLayout; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.template.RequireScrollMixin.OnRequireScrollStateChangedListener; import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate; import com.android.setupwizardlib.view.NavigationBar; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) -@RunWith(SuwLibRobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) +@RunWith(RobolectricTestRunner.class) public class RequireScrollMixinTest { - @Mock - private TemplateLayout mTemplateLayout; - - @Mock - private ScrollHandlingDelegate mDelegate; - - private RequireScrollMixin mRequireScrollMixin; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - doReturn(application).when(mTemplateLayout).getContext(); - mRequireScrollMixin = new RequireScrollMixin(mTemplateLayout); - mRequireScrollMixin.setScrollHandlingDelegate(mDelegate); - } - - @Test - public void testRequireScroll() { - mRequireScrollMixin.requireScroll(); - - verify(mDelegate).startListening(); - } - - @Test - public void testScrollStateChangedListener() { - OnRequireScrollStateChangedListener listener = - mock(OnRequireScrollStateChangedListener.class); - mRequireScrollMixin.setOnRequireScrollStateChangedListener(listener); - assertFalse("Scrolling should not be required initially", - mRequireScrollMixin.isScrollingRequired()); - - mRequireScrollMixin.notifyScrollabilityChange(true); - verify(listener).onRequireScrollStateChanged(true); - assertTrue("Scrolling should be required when there is more content below the fold", - mRequireScrollMixin.isScrollingRequired()); - - mRequireScrollMixin.notifyScrollabilityChange(false); - verify(listener).onRequireScrollStateChanged(false); - assertFalse("Scrolling should not be required after scrolling to bottom", - mRequireScrollMixin.isScrollingRequired()); - - // Once the user has scrolled to the bottom, they should not be forced to scroll down again - mRequireScrollMixin.notifyScrollabilityChange(true); - verifyNoMoreInteractions(listener); - - assertFalse("Scrolling should not be required after scrolling to bottom once", - mRequireScrollMixin.isScrollingRequired()); - - assertSame(listener, mRequireScrollMixin.getOnRequireScrollStateChangedListener()); - } - - @Test - public void testCreateOnClickListener() { - OnClickListener wrappedListener = mock(OnClickListener.class); - final OnClickListener onClickListener = - mRequireScrollMixin.createOnClickListener(wrappedListener); - - mRequireScrollMixin.notifyScrollabilityChange(true); - onClickListener.onClick(null); - - verify(wrappedListener, never()).onClick(any(View.class)); - verify(mDelegate).pageScrollDown(); - - mRequireScrollMixin.notifyScrollabilityChange(false); - onClickListener.onClick(null); - - verify(wrappedListener).onClick(any(View.class)); - } - - @Test - public void testRequireScrollWithNavigationBar() { - final NavigationBar navigationBar = new NavigationBar(application); - mRequireScrollMixin.requireScrollWithNavigationBar(navigationBar); - - mRequireScrollMixin.notifyScrollabilityChange(true); - assertEquals("More button should be visible", - View.VISIBLE, navigationBar.getMoreButton().getVisibility()); - assertEquals("Next button should be hidden", - View.GONE, navigationBar.getNextButton().getVisibility()); - - navigationBar.getMoreButton().performClick(); - verify(mDelegate).pageScrollDown(); - - mRequireScrollMixin.notifyScrollabilityChange(false); - assertEquals("More button should be hidden", - View.GONE, navigationBar.getMoreButton().getVisibility()); - assertEquals("Next button should be visible", - View.VISIBLE, navigationBar.getNextButton().getVisibility()); - } - - @SuppressLint("SetTextI18n") // It's OK for testing - @Test - public void testRequireScrollWithButton() { - final Button button = new Button(application); - button.setText("OriginalLabel"); - OnClickListener wrappedListener = mock(OnClickListener.class); - mRequireScrollMixin.requireScrollWithButton( - button, "TestMoreLabel", wrappedListener); - - assertEquals("Button label should be kept initially", "OriginalLabel", button.getText()); - - mRequireScrollMixin.notifyScrollabilityChange(true); - assertEquals("TestMoreLabel", button.getText()); - button.performClick(); - verify(wrappedListener, never()).onClick(eq(button)); - verify(mDelegate).pageScrollDown(); - - mRequireScrollMixin.notifyScrollabilityChange(false); - assertEquals("OriginalLabel", button.getText()); - button.performClick(); - verify(wrappedListener).onClick(eq(button)); - } + @Mock private ScrollHandlingDelegate delegate; + + private RequireScrollMixin requireScrollMixin; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + TemplateLayout templateLayout = new GlifLayout(application); + requireScrollMixin = new RequireScrollMixin(templateLayout); + requireScrollMixin.setScrollHandlingDelegate(delegate); + } + + @Test + public void testRequireScroll() { + requireScrollMixin.requireScroll(); + + verify(delegate).startListening(); + } + + @Test + public void testScrollStateChangedListener() { + OnRequireScrollStateChangedListener listener = mock(OnRequireScrollStateChangedListener.class); + requireScrollMixin.setOnRequireScrollStateChangedListener(listener); + assertWithMessage("Scrolling should not be required initially") + .that(requireScrollMixin.isScrollingRequired()) + .isFalse(); + + requireScrollMixin.notifyScrollabilityChange(true); + verify(listener).onRequireScrollStateChanged(true); + assertWithMessage("Scrolling should be required when there is more content below the fold") + .that(requireScrollMixin.isScrollingRequired()) + .isTrue(); + + requireScrollMixin.notifyScrollabilityChange(false); + verify(listener).onRequireScrollStateChanged(false); + assertWithMessage("Scrolling should not be required after scrolling to bottom") + .that(requireScrollMixin.isScrollingRequired()) + .isFalse(); + + // Once the user has scrolled to the bottom, they should not be forced to scroll down again + requireScrollMixin.notifyScrollabilityChange(true); + verifyNoMoreInteractions(listener); + + assertWithMessage("Scrolling should not be required after scrolling to bottom once") + .that(requireScrollMixin.isScrollingRequired()) + .isFalse(); + + assertThat(requireScrollMixin.getOnRequireScrollStateChangedListener()).isSameAs(listener); + } + + @Test + public void testCreateOnClickListener() { + OnClickListener wrappedListener = mock(OnClickListener.class); + final OnClickListener onClickListener = + requireScrollMixin.createOnClickListener(wrappedListener); + + requireScrollMixin.notifyScrollabilityChange(true); + onClickListener.onClick(null); + + verify(wrappedListener, never()).onClick(any(View.class)); + verify(delegate).pageScrollDown(); + + requireScrollMixin.notifyScrollabilityChange(false); + onClickListener.onClick(null); + + verify(wrappedListener).onClick(any(View.class)); + } + + @Test + public void testRequireScrollWithNavigationBar() { + final NavigationBar navigationBar = new NavigationBar(application); + requireScrollMixin.requireScrollWithNavigationBar(navigationBar); + + requireScrollMixin.notifyScrollabilityChange(true); + assertWithMessage("More button should be visible") + .that(navigationBar.getMoreButton().getVisibility()) + .isEqualTo(View.VISIBLE); + assertWithMessage("Next button should be hidden") + .that(navigationBar.getNextButton().getVisibility()) + .isEqualTo(View.GONE); + + navigationBar.getMoreButton().performClick(); + verify(delegate).pageScrollDown(); + + requireScrollMixin.notifyScrollabilityChange(false); + assertWithMessage("More button should be hidden") + .that(navigationBar.getMoreButton().getVisibility()) + .isEqualTo(View.GONE); + assertWithMessage("Next button should be visible") + .that(navigationBar.getNextButton().getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @SuppressLint("SetTextI18n") // It's OK for testing + @Test + public void testRequireScrollWithButton() { + final Button button = new Button(application); + button.setText("OriginalLabel"); + OnClickListener wrappedListener = mock(OnClickListener.class); + requireScrollMixin.requireScrollWithButton(button, "TestMoreLabel", wrappedListener); + + assertWithMessage("Button label should be kept initially") + .that(button.getText().toString()) + .isEqualTo("OriginalLabel"); + + requireScrollMixin.notifyScrollabilityChange(true); + assertThat(button.getText().toString()).isEqualTo("TestMoreLabel"); + button.performClick(); + verify(wrappedListener, never()).onClick(eq(button)); + verify(delegate).pageScrollDown(); + + requireScrollMixin.notifyScrollabilityChange(false); + assertThat(button.getText().toString()).isEqualTo("OriginalLabel"); + button.performClick(); + verify(wrappedListener).onClick(eq(button)); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java index 429445c..b07e2a3 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java @@ -16,72 +16,65 @@ package com.android.setupwizardlib.template; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.spy; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; +import android.view.View; import com.android.setupwizardlib.view.BottomScrollView; -import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) -@RunWith(SuwLibRobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) +@RunWith(RobolectricTestRunner.class) public class ScrollViewScrollHandlingDelegateTest { - @Mock - private RequireScrollMixin mRequireScrollMixin; - - private BottomScrollView mScrollView; - private ScrollViewScrollHandlingDelegate mDelegate; - private ArgumentCaptor<BottomScrollListener> mListenerCaptor; + @Mock private RequireScrollMixin requireScrollMixin; - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + private BottomScrollView scrollView; + private ScrollViewScrollHandlingDelegate delegate; - mScrollView = spy(new BottomScrollView(application)); - mDelegate = new ScrollViewScrollHandlingDelegate(mRequireScrollMixin, mScrollView); + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); - mListenerCaptor = ArgumentCaptor.forClass(BottomScrollListener.class); - doNothing().when(mScrollView).setBottomScrollListener(mListenerCaptor.capture()); + scrollView = new BottomScrollView(application); + View childView = new View(application); + scrollView.addView(childView); + delegate = new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView); - mScrollView.layout(0, 0, 50, 50); - } + scrollView.layout(0, 0, 500, 500); + childView.layout(0, 0, 1000, 1000); + } - @Test - public void testRequireScroll() throws Throwable { - mDelegate.startListening(); + @Test + public void testRequireScroll() throws Throwable { + delegate.startListening(); - mListenerCaptor.getValue().onRequiresScroll(); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); - } + scrollView.getBottomScrollListener().onRequiresScroll(); + verify(requireScrollMixin).notifyScrollabilityChange(true); + } - @Test - public void testScrolledToBottom() throws Throwable { - mDelegate.startListening(); + @Test + public void testScrolledToBottom() throws Throwable { + delegate.startListening(); - mListenerCaptor.getValue().onRequiresScroll(); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); + scrollView.getBottomScrollListener().onRequiresScroll(); + verify(requireScrollMixin).notifyScrollabilityChange(true); - mListenerCaptor.getValue().onScrolledToBottom(); + scrollView.getBottomScrollListener().onScrolledToBottom(); - verify(mRequireScrollMixin).notifyScrollabilityChange(false); - } + verify(requireScrollMixin).notifyScrollabilityChange(false); + } - @Test - public void testPageScrollDown() throws Throwable { - mDelegate.pageScrollDown(); - verify(mScrollView).smoothScrollBy(anyInt(), eq(50)); - } + @Test + public void testPageScrollDown() throws Throwable { + delegate.pageScrollDown(); + assertThat(scrollView.getScrollY()).isEqualTo(500); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java index c10c122..a0c688c 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java @@ -16,7 +16,7 @@ package com.android.setupwizardlib.util; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertWithMessage; import static org.robolectric.RuntimeEnvironment.application; import android.content.Context; @@ -25,90 +25,88 @@ import android.content.res.TypedArray; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.ContextThemeWrapper; - import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(sdk = Config.ALL_SDKS) public class GlifDimensionTest { - private Context mContext; - - @Before - public void setUp() { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - } - - @Test - public void testDividerInsetPhone() { - assertDividerInset(); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testDividerInsetSw600dp() { - assertDividerInset(); - } - - private void assertDividerInset() { - final Resources res = mContext.getResources(); - - final TypedArray a = mContext.obtainStyledAttributes(new int[]{R.attr.suwMarginSides}); - final int marginSides = a.getDimensionPixelSize(0, 0); - a.recycle(); - - assertEquals( - "Dimensions should satisfy constraint: " - + "?attr/suwMarginSides = suw_items_glif_text_divider_inset", - marginSides, - res.getDimensionPixelSize(R.dimen.suw_items_glif_text_divider_inset)); - - assertEquals( - "Dimensions should satisfy constraint: ?attr/suwMarginSides + " - + "suw_items_icon_container_width = suw_items_glif_icon_divider_inset", - marginSides + res.getDimensionPixelSize(R.dimen.suw_items_icon_container_width), - res.getDimensionPixelSize(R.dimen.suw_items_glif_icon_divider_inset)); - } - - @Test - public void testButtonMargin() { - assertButtonMargin(); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testButtonMarginSw600dp() { - assertButtonMargin(); - } - - private void assertButtonMargin() { - final Resources res = mContext.getResources(); - - final TypedArray a = mContext.obtainStyledAttributes(new int[]{R.attr.suwMarginSides}); - final int marginSides = a.getDimensionPixelSize(0, 0); - a.recycle(); - - assertEquals( - "Dimensions should satisfy constraint: ?attr/suwMarginSides - " - + "4dp (internal padding of button) = suw_glif_button_margin_end", - marginSides - dp2Px(4), - res.getDimensionPixelSize(R.dimen.suw_glif_button_margin_end)); - - assertEquals( - "Dimensions should satisfy constraint: ?attr/suwMarginSides - " - + "suw_glif_button_padding = suw_glif_button_margin_start", - marginSides - res.getDimensionPixelSize(R.dimen.suw_glif_button_padding), - res.getDimensionPixelSize(R.dimen.suw_glif_button_margin_start)); - } - - private int dp2Px(float dp) { - DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); - } + private Context context; + + @Before + public void setUp() { + context = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + } + + @Test + public void testDividerInsetPhone() { + assertDividerInset(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testDividerInsetSw600dp() { + assertDividerInset(); + } + + private void assertDividerInset() { + final Resources res = context.getResources(); + + final TypedArray a = context.obtainStyledAttributes(new int[] {R.attr.suwMarginSides}); + final int marginSides = a.getDimensionPixelSize(0, 0); + a.recycle(); + + assertWithMessage( + "Dimensions should satisfy constraint: " + + "?attr/suwMarginSides = suw_items_glif_text_divider_inset") + .that(res.getDimensionPixelSize(R.dimen.suw_items_glif_text_divider_inset)) + .isEqualTo(marginSides); + + assertWithMessage( + "Dimensions should satisfy constraint: ?attr/suwMarginSides + " + + "suw_items_icon_container_width = suw_items_glif_icon_divider_inset") + .that(res.getDimensionPixelSize(R.dimen.suw_items_glif_icon_divider_inset)) + .isEqualTo(marginSides + res.getDimensionPixelSize(R.dimen.suw_items_icon_container_width)); + } + + @Test + public void testButtonMargin() { + assertButtonMargin(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testButtonMarginSw600dp() { + assertButtonMargin(); + } + + private void assertButtonMargin() { + final Resources res = context.getResources(); + + final TypedArray a = context.obtainStyledAttributes(new int[] {R.attr.suwMarginSides}); + final int marginSides = a.getDimensionPixelSize(0, 0); + a.recycle(); + + assertWithMessage( + "Dimensions should satisfy constraint: ?attr/suwMarginSides - " + + "4dp (internal padding of button) = suw_glif_button_margin_end") + .that(res.getDimensionPixelSize(R.dimen.suw_glif_button_margin_end)) + .isEqualTo(marginSides - dp2Px(4)); + + assertWithMessage( + "Dimensions should satisfy constraint: ?attr/suwMarginSides - " + + "suw_glif_button_padding = suw_glif_button_margin_start") + .that(res.getDimensionPixelSize(R.dimen.suw_glif_button_margin_start)) + .isEqualTo(marginSides - res.getDimensionPixelSize(R.dimen.suw_glif_button_padding)); + } + + private int dp2Px(float dp) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java index d8e318d..61b84bb 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java @@ -17,9 +17,6 @@ package com.android.setupwizardlib.util; import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.robolectric.RuntimeEnvironment.application; import android.annotation.TargetApi; @@ -28,71 +25,82 @@ import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; +import androidx.annotation.Nullable; +import android.util.AttributeSet; import android.view.ContextThemeWrapper; +import android.view.View; import android.widget.Button; import android.widget.ProgressBar; - -import androidx.annotation.Nullable; - import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; -@RunWith(SuwLibRobolectricTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class GlifStyleTest { - private Context mContext; - - @Before - public void setUp() { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + @Test + public void testSuwGlifButtonTertiary() { + Button button = + createButton( + new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light), + Robolectric.buildAttributeSet() + .setStyleAttribute("@style/SuwGlifButton.Tertiary") + .build()); + assertThat(button.getBackground()).named("background").isNotNull(); + assertThat(button.getTransformationMethod()).named("transformation method").isNull(); + if (VERSION.SDK_INT < VERSION_CODES.M) { + // Robolectric resolved the wrong theme attribute on versions >= M + // https://github.com/robolectric/robolectric/issues/2940 + assertThat(Integer.toHexString(button.getTextColors().getDefaultColor())) + .isEqualTo("ff4285f4"); } - - @Test - public void testSuwGlifButtonTertiary() { - Button button = new Button( - mContext, - Robolectric.buildAttributeSet() - .setStyleAttribute("@style/SuwGlifButton.Tertiary") - .build()); - assertThat(button.getBackground()).named("background").isNotNull(); - assertThat(button.getTransformationMethod()).named("transformation method").isNull(); - if (VERSION.SDK_INT < VERSION_CODES.M) { - // Robolectric resolved the wrong theme attribute on versions >= M - // https://github.com/robolectric/robolectric/issues/2940 - assertEquals("ff4285f4", Integer.toHexString(button.getTextColors().getDefaultColor())); - } + } + + @TargetApi(VERSION_CODES.LOLLIPOP) + @Config(sdk = Config.NEWEST_SDK) + @Test + public void glifThemeLight_statusBarColorShouldBeTransparent() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + assertThat(activity.getWindow().getStatusBarColor()).isEqualTo(0x00000000); + } + + @Test + public void glifLoadingScreen_shouldHaveProgressBar() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + activity.setContentView(R.layout.suw_glif_loading_screen); + + assertThat((View) activity.findViewById(R.id.suw_large_progress_bar)) + .isInstanceOf(ProgressBar.class); + } + + private Button createButton(Context context, AttributeSet attrs) { + Class<? extends Button> buttonClass; + try { + // Use AppCompatButton in builds that have them (i.e. gingerbreadCompat) + // noinspection unchecked + buttonClass = + (Class<? extends Button>) Class.forName("androidx.appcompat.widget.AppCompatButton"); + } catch (ClassNotFoundException e) { + buttonClass = Button.class; } - - @TargetApi(VERSION_CODES.LOLLIPOP) - @Config(sdk = Config.NEWEST_SDK) - @Test - public void glifThemeLight_statusBarColorShouldBeTransparent() { - GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); - assertEquals(0x00000000, activity.getWindow().getStatusBarColor()); - } - - @Test - public void glifLoadingScreen_shouldHaveProgressBar() { - GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); - activity.setContentView(R.layout.suw_glif_loading_screen); - - assertTrue("Progress bar should exist", - activity.findViewById(R.id.suw_large_progress_bar) instanceof ProgressBar); - } - - private static class GlifThemeActivity extends Activity { - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - setTheme(R.style.SuwThemeGlif_Light); - super.onCreate(savedInstanceState); - } + return ReflectionHelpers.callConstructor( + buttonClass, + ClassParameter.from(Context.class, context), + ClassParameter.from(AttributeSet.class, attrs)); + } + + private static class GlifThemeActivity extends Activity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(R.style.SuwThemeGlif_Light); + super.onCreate(savedInstanceState); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java index 44b8886..13e29f6 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java @@ -18,68 +18,59 @@ package com.android.setupwizardlib.util; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import android.app.Activity; import android.graphics.Color; import android.graphics.Typeface; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; +import androidx.annotation.Nullable; import android.view.View; import android.widget.Button; - -import androidx.annotation.Nullable; - import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.NEWEST_SDK) public class GlifV3StyleTest { - @Test - public void activityWithGlifV3Theme_shouldUseLightNavBarOnV27OrAbove() { - GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); - if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) { - assertEquals( - activity.getWindow().getNavigationBarColor(), - Color.WHITE); - int vis = activity.getWindow().getDecorView().getSystemUiVisibility(); - assertTrue((vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0); - } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals( - activity.getWindow().getNavigationBarColor(), - Color.BLACK); - } - // Nav bar color is not customizable pre-L + @Test + public void activityWithGlifV3Theme_shouldUseLightNavBarOnV27OrAbove() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) { + assertThat(activity.getWindow().getNavigationBarColor()).isEqualTo(Color.WHITE); + int vis = activity.getWindow().getDecorView().getSystemUiVisibility(); + assertThat((vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0).isTrue(); + } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertThat(activity.getWindow().getNavigationBarColor()).isEqualTo(Color.BLACK); } + // Nav bar color is not customizable pre-L + } - @Test - public void buttonWithGlifV3_shouldBeGoogleSans() { - GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); - Button button = new Button( - activity, - Robolectric.buildAttributeSet() - .setStyleAttribute("@style/SuwGlifButton.Primary") - .build()); - assertThat(button.getTypeface()).isEqualTo(Typeface.create("google-sans", 0)); - // Button should not be all caps - assertThat(button.getTransformationMethod()).isNull(); - } + @Test + public void buttonWithGlifV3_shouldBeGoogleSans() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + Button button = + new Button( + activity, + Robolectric.buildAttributeSet() + .setStyleAttribute("@style/SuwGlifButton.Primary") + .build()); + assertThat(button.getTypeface()).isEqualTo(Typeface.create("google-sans", 0)); + // Button should not be all caps + assertThat(button.getTransformationMethod()).isNull(); + } - private static class GlifThemeActivity extends Activity { + private static class GlifThemeActivity extends Activity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - setTheme(R.style.SuwThemeGlifV3_Light); - super.onCreate(savedInstanceState); - } + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(R.style.SuwThemeGlifV3_Light); + super.onCreate(savedInstanceState); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java index 2285cd5..f301e43 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java @@ -17,211 +17,164 @@ package com.android.setupwizardlib.util; import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; import static org.robolectric.RuntimeEnvironment.application; +import static org.robolectric.Shadows.shadowOf; -import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; - import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; +import com.android.setupwizardlib.robolectric.ExternalResources; +import com.android.setupwizardlib.robolectric.ExternalResources.Resources; import com.android.setupwizardlib.util.Partner.ResourceEntry; -import com.android.setupwizardlib.util.PartnerTest.ShadowApplicationPackageManager; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.Shadows; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.shadows.ShadowResources; -import java.util.Arrays; -import java.util.Collections; - -@RunWith(SuwLibRobolectricTestRunner.class) -@Config( - sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }, - shadows = ShadowApplicationPackageManager.class) +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class PartnerTest { - private static final String ACTION_PARTNER_CUSTOMIZATION = - "com.android.setupwizard.action.PARTNER_CUSTOMIZATION"; - - private Context mContext; - private Resources mPartnerResources; - - private ShadowApplicationPackageManager mPackageManager; - - @Before - public void setUp() throws Exception { - Partner.resetForTesting(); - - mContext = spy(application); - mPartnerResources = spy(ShadowResources.getSystem()); - - mPackageManager = - (ShadowApplicationPackageManager) Shadows.shadowOf(application.getPackageManager()); - mPackageManager.partnerResources = mPartnerResources; - } - - @Test - public void testLoadPartner() { - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList( - createResolveInfo("foo.bar", false, true), - createResolveInfo("test.partner.package", true, true)) - ); - - Partner partner = Partner.get(mContext); - assertNotNull("Partner should not be null", partner); - } - - @Test - public void testLoadNoPartner() { - Partner partner = Partner.get(mContext); - assertNull("Partner should be null", partner); - } - - @Test - public void testLoadNonSystemPartner() { - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList( - createResolveInfo("foo.bar", false, true), - createResolveInfo("test.partner.package", false, true)) - ); - - Partner partner = Partner.get(mContext); - assertNull("Partner should be null", partner); - } - - @Test - public void testLoadPartnerValue() { - doReturn(0x7f010000).when(mPartnerResources) - .getIdentifier(eq("suwTransitionDuration"), eq("integer"), anyString()); - doReturn(5000).when(mPartnerResources).getInteger(eq(0x7f010000)); - - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList( - createResolveInfo("foo.bar", false, true), - createResolveInfo("test.partner.package", true, true)) - ); - - ResourceEntry entry = Partner.getResourceEntry(mContext, R.integer.suwTransitionDuration); - int partnerValue = entry.resources.getInteger(entry.id); - assertEquals("Partner value should be overlaid to 5000", 5000, partnerValue); - assertTrue("Partner value should come from overlay", entry.isOverlay); - } - - @Test - public void getColor_shouldReturnPartnerValueIfPresent() { - final int expectedPartnerColor = 1111; - doReturn(12345).when(mPartnerResources) - .getIdentifier(eq("suw_color_accent_dark"), eq("color"), anyString()); - doReturn(expectedPartnerColor).when(mPartnerResources).getColor(eq(12345)); - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList(createResolveInfo("test.partner.package", true, true))); - final int foundColor = Partner.getColor(mContext, R.color.suw_color_accent_dark); - assertEquals("Partner color should be overlayed to: " + expectedPartnerColor, - expectedPartnerColor, foundColor); - } - - @Test - public void getText_shouldReturnPartnerValueIfPresent() { - final CharSequence expectedPartnerText = "partner"; - doReturn(12345).when(mPartnerResources) - .getIdentifier(eq("suw_next_button_label"), eq("string"), anyString()); - doReturn(expectedPartnerText).when(mPartnerResources).getText(eq(12345)); - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Collections.singletonList(createResolveInfo("test.partner.package", true, true))); - final CharSequence partnerText = Partner.getText(mContext, R.string.suw_next_button_label); - assertThat(partnerText).isEqualTo(expectedPartnerText); + private static final String ACTION_PARTNER_CUSTOMIZATION = + "com.android.setupwizard.action.PARTNER_CUSTOMIZATION"; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Partner.resetForTesting(); + } + + @Test + public void get_withPartnerPackage_shouldReturnNonNull() { + new PartnerPackageBuilder("foo.bar") + .setIsSystem(false) + .setDirectBootAware(true) + .injectResources(); + new PartnerPackageBuilder("test.partner.package").setDirectBootAware(true).injectResources(); + + Partner partner = Partner.get(application); + assertThat(partner).isNotNull(); + assertThat(partner.getPackageName()).isEqualTo("test.partner.package"); + } + + @Test + public void get_noPartnerPackage_shouldReturnNull() { + Partner partner = Partner.get(application); + assertThat(partner).isNull(); + } + + @Test + public void get_nonSystemPartnerPackage_shouldIgnoreAndReturnNull() { + new PartnerPackageBuilder("foo.bar") + .setIsSystem(false) + .setDirectBootAware(true) + .injectResources(); + new PartnerPackageBuilder("test.partner.package") + .setIsSystem(false) + .setDirectBootAware(true) + .injectResources(); + + Partner partner = Partner.get(application); + assertThat(partner).isNull(); + } + + @Test + public void getResourceEntry_hasOverlay_shouldReturnOverlayValue() { + new PartnerPackageBuilder("test.partner.package") + .injectResources() + .putInteger("suwTransitionDuration", 5000); + + ResourceEntry entry = Partner.getResourceEntry(application, R.integer.suwTransitionDuration); + int partnerValue = entry.resources.getInteger(entry.id); + assertThat(partnerValue).named("partner value").isEqualTo(5000); + assertThat(entry.isOverlay).isTrue(); + } + + @Test + public void getColor_partnerValuePresent_shouldReturnPartnerValue() { + new PartnerPackageBuilder("test.partner.package") + .injectResources() + .putColor("suw_color_accent_dark", 0xffff00ff); + + final int color = Partner.getColor(application, R.color.suw_color_accent_dark); + assertThat(color).isEqualTo(0xffff00ff); + } + + @Test + public void getText_partnerValuePresent_shouldReturnPartnerValue() { + new PartnerPackageBuilder("test.partner.package") + .injectResources() + .putText("suw_next_button_label", "partner"); + + final CharSequence partnerText = Partner.getText(application, R.string.suw_next_button_label); + assertThat(partnerText.toString()).isEqualTo("partner"); + } + + @Test + public void getResourceEntry_partnerValueNotPresent_shouldReturnDefault() { + new PartnerPackageBuilder("test.partner.package").injectResources(); + + ResourceEntry entry = Partner.getResourceEntry(application, R.color.suw_color_accent_dark); + int partnerValue = entry.resources.getColor(entry.id); + assertThat(partnerValue).isEqualTo(0xff448aff); + assertThat(entry.isOverlay).isFalse(); + } + + @Test + public void getResourceEntry_directBootUnawareNoValueDefined_shouldReturnDefaultValue() { + new PartnerPackageBuilder("test.partner.package").injectResources(); + + ResourceEntry entry = Partner.getResourceEntry(application, R.color.suw_color_accent_dark); + int partnerValue = entry.resources.getColor(entry.id); + assertThat(partnerValue).isEqualTo(0xff448aff); + assertThat(entry.isOverlay).isFalse(); + } + + private static class PartnerPackageBuilder { + private final String packageName; + private final ResolveInfo resolveInfo; + + PartnerPackageBuilder(String packageName) { + this.packageName = packageName; + + resolveInfo = new ResolveInfo(); + resolveInfo.resolvePackageName = packageName; + ActivityInfo activityInfo = new ActivityInfo(); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.flags = ApplicationInfo.FLAG_SYSTEM; + appInfo.packageName = packageName; + activityInfo.applicationInfo = appInfo; + activityInfo.packageName = packageName; + activityInfo.name = packageName; + resolveInfo.activityInfo = activityInfo; } - @Test - public void testLoadDefaultValue() { - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList( - createResolveInfo("foo.bar", false, true), - createResolveInfo("test.partner.package", true, true)) - ); - - ResourceEntry entry = Partner.getResourceEntry(mContext, R.color.suw_color_accent_dark); - int partnerValue = entry.resources.getColor(entry.id); - assertEquals("Partner value should default to 0xff448aff", 0xff448aff, partnerValue); - assertFalse("Partner value should come from fallback", entry.isOverlay); - } - - @Test - public void testNotDirectBootAware() { - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Collections.singletonList(createResolveInfo("test.partner.package", true, false))); - - ResourceEntry entry = Partner.getResourceEntry(mContext, R.color.suw_color_accent_dark); - int partnerValue = entry.resources.getColor(entry.id); - assertEquals("Partner value should default to 0xff448aff", 0xff448aff, partnerValue); - assertFalse("Partner value should come from fallback", entry.isOverlay); + PartnerPackageBuilder setIsSystem(boolean isSystem) { + if (isSystem) { + resolveInfo.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; + } else { + resolveInfo.activityInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; + } + return this; } - private ResolveInfo createResolveInfo( - String packageName, - boolean isSystem, - boolean directBootAware) { - ResolveInfo info = new ResolveInfo(); - info.resolvePackageName = packageName; - ActivityInfo activityInfo = new ActivityInfo(); - ApplicationInfo appInfo = new ApplicationInfo(); - appInfo.flags = isSystem ? ApplicationInfo.FLAG_SYSTEM : 0; - appInfo.packageName = packageName; - activityInfo.applicationInfo = appInfo; - activityInfo.packageName = packageName; - activityInfo.name = packageName; - if (VERSION.SDK_INT >= VERSION_CODES.N) { - activityInfo.directBootAware = directBootAware; - } - info.activityInfo = activityInfo; - return info; + PartnerPackageBuilder setDirectBootAware(boolean directBootAware) { + if (VERSION.SDK_INT >= VERSION_CODES.N) { + resolveInfo.activityInfo.directBootAware = directBootAware; + } + return this; } - @Implements(className = "android.app.ApplicationPackageManager") - public static class ShadowApplicationPackageManager extends - org.robolectric.shadows.ShadowApplicationPackageManager { - - public Resources partnerResources; - - @Implementation - @Override - public Resources getResourcesForApplication(ApplicationInfo app) - throws NameNotFoundException { - if (app != null && "test.partner.package".equals(app.packageName)) { - return partnerResources; - } else { - return super.getResourcesForApplication(app); - } - } + Resources injectResources() { + shadowOf(application.getPackageManager()) + .addResolveInfoForIntent(new Intent(ACTION_PARTNER_CUSTOMIZATION), resolveInfo); + return ExternalResources.injectExternalResources(packageName); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/ThemeResolverTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/ThemeResolverTest.java new file mode 100644 index 0000000..6489961 --- /dev/null +++ b/library/test/robotest/src/com/android/setupwizardlib/util/ThemeResolverTest.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2018 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 com.google.common.truth.Truth.assertThat; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Activity; +import android.content.Intent; +import androidx.annotation.StyleRes; +import com.android.setupwizardlib.R; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = Config.NEWEST_SDK) +public class ThemeResolverTest { + + @After + public void resetDefaultThemeResolver() { + ThemeResolver.setDefault(null); + } + + @Test + public void resolve_nonDayNight_shouldReturnCorrespondingTheme() { + @StyleRes int defaultTheme = 12345; + ThemeResolver themeResolver = + new ThemeResolver.Builder().setDefaultTheme(defaultTheme).setUseDayNight(false).build(); + assertThat(themeResolver.resolve("material")).isEqualTo(R.style.SuwThemeMaterial); + assertThat(themeResolver.resolve("material_light")).isEqualTo(R.style.SuwThemeMaterial_Light); + assertThat(themeResolver.resolve("glif")).isEqualTo(R.style.SuwThemeGlif); + assertThat(themeResolver.resolve("glif_light")).isEqualTo(R.style.SuwThemeGlif_Light); + assertThat(themeResolver.resolve("glif_v2")).isEqualTo(R.style.SuwThemeGlifV2); + assertThat(themeResolver.resolve("glif_v2_light")).isEqualTo(R.style.SuwThemeGlifV2_Light); + assertThat(themeResolver.resolve("glif_v3")).isEqualTo(R.style.SuwThemeGlifV3); + assertThat(themeResolver.resolve("glif_v3_light")).isEqualTo(R.style.SuwThemeGlifV3_Light); + assertThat(themeResolver.resolve("unknown_theme")).isEqualTo(defaultTheme); + } + + @Test + public void resolve_dayNight_shouldReturnDayNightTheme() { + @StyleRes int defaultTheme = 12345; + ThemeResolver themeResolver = new ThemeResolver.Builder().setDefaultTheme(defaultTheme).build(); + assertThat(themeResolver.resolve("material")).isEqualTo(R.style.SuwThemeMaterial_DayNight); + assertThat(themeResolver.resolve("material_light")) + .isEqualTo(R.style.SuwThemeMaterial_DayNight); + assertThat(themeResolver.resolve("glif")).isEqualTo(R.style.SuwThemeGlif_DayNight); + assertThat(themeResolver.resolve("glif_light")).isEqualTo(R.style.SuwThemeGlif_DayNight); + assertThat(themeResolver.resolve("glif_v2")).isEqualTo(R.style.SuwThemeGlifV2_DayNight); + assertThat(themeResolver.resolve("glif_v2_light")).isEqualTo(R.style.SuwThemeGlifV2_DayNight); + assertThat(themeResolver.resolve("glif_v3")).isEqualTo(R.style.SuwThemeGlifV3_DayNight); + assertThat(themeResolver.resolve("glif_v3_light")).isEqualTo(R.style.SuwThemeGlifV3_DayNight); + assertThat(themeResolver.resolve("unknown_theme")).isEqualTo(defaultTheme); + } + + @Test + public void resolve_newerThanOldestSupportedTheme_shouldReturnSpecifiedTheme() { + ThemeResolver themeResolver = + new ThemeResolver.Builder() + .setOldestSupportedTheme(WizardManagerHelper.THEME_GLIF_V2) + .build(); + assertThat(themeResolver.resolve("glif_v2")).isEqualTo(R.style.SuwThemeGlifV2_DayNight); + assertThat(themeResolver.resolve("glif_v2_light")).isEqualTo(R.style.SuwThemeGlifV2_DayNight); + assertThat(themeResolver.resolve("glif_v3")).isEqualTo(R.style.SuwThemeGlifV3_DayNight); + assertThat(themeResolver.resolve("glif_v3_light")).isEqualTo(R.style.SuwThemeGlifV3_DayNight); + } + + @Test + public void resolve_olderThanOldestSupportedTheme_shouldReturnDefault() { + @StyleRes int defaultTheme = 12345; + ThemeResolver themeResolver = + new ThemeResolver.Builder() + .setDefaultTheme(defaultTheme) + .setOldestSupportedTheme(WizardManagerHelper.THEME_GLIF_V2) + .build(); + assertThat(themeResolver.resolve("material")).isEqualTo(defaultTheme); + assertThat(themeResolver.resolve("material_light")).isEqualTo(defaultTheme); + assertThat(themeResolver.resolve("glif")).isEqualTo(defaultTheme); + assertThat(themeResolver.resolve("glif_light")).isEqualTo(defaultTheme); + } + + @Test + public void resolve_intentTheme_shouldReturnCorrespondingTheme() { + @StyleRes int defaultTheme = 12345; + ThemeResolver themeResolver = + new ThemeResolver.Builder().setDefaultTheme(defaultTheme).setUseDayNight(false).build(); + assertThat( + themeResolver.resolve( + new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "material"))) + .isEqualTo(R.style.SuwThemeMaterial); + assertThat( + themeResolver.resolve( + new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "material_light"))) + .isEqualTo(R.style.SuwThemeMaterial_Light); + assertThat( + themeResolver.resolve(new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "glif"))) + .isEqualTo(R.style.SuwThemeGlif); + assertThat( + themeResolver.resolve( + new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "glif_light"))) + .isEqualTo(R.style.SuwThemeGlif_Light); + assertThat( + themeResolver.resolve( + new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v2"))) + .isEqualTo(R.style.SuwThemeGlifV2); + assertThat( + themeResolver.resolve( + new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v2_light"))) + .isEqualTo(R.style.SuwThemeGlifV2_Light); + assertThat( + themeResolver.resolve( + new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v3"))) + .isEqualTo(R.style.SuwThemeGlifV3); + assertThat( + themeResolver.resolve( + new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v3_light"))) + .isEqualTo(R.style.SuwThemeGlifV3_Light); + assertThat( + themeResolver.resolve( + new Intent().putExtra(WizardManagerHelper.EXTRA_THEME, "unknown_theme"))) + .isEqualTo(defaultTheme); + } + + @Test + public void resolve_suwIntent_shouldForceNonDayNightTheme() { + @StyleRes int defaultTheme = 12345; + ThemeResolver themeResolver = + new ThemeResolver.Builder().setDefaultTheme(defaultTheme).setUseDayNight(true).build(); + Intent originalIntent = new Intent().putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true); + assertThat( + themeResolver.resolve( + new Intent(originalIntent).putExtra(WizardManagerHelper.EXTRA_THEME, "material"))) + .isEqualTo(R.style.SuwThemeMaterial); + assertThat( + themeResolver.resolve( + new Intent(originalIntent) + .putExtra(WizardManagerHelper.EXTRA_THEME, "material_light"))) + .isEqualTo(R.style.SuwThemeMaterial_Light); + assertThat( + themeResolver.resolve( + new Intent(originalIntent).putExtra(WizardManagerHelper.EXTRA_THEME, "glif"))) + .isEqualTo(R.style.SuwThemeGlif); + assertThat( + themeResolver.resolve( + new Intent(originalIntent).putExtra(WizardManagerHelper.EXTRA_THEME, "glif_light"))) + .isEqualTo(R.style.SuwThemeGlif_Light); + assertThat( + themeResolver.resolve( + new Intent(originalIntent).putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v2"))) + .isEqualTo(R.style.SuwThemeGlifV2); + assertThat( + themeResolver.resolve( + new Intent(originalIntent) + .putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v2_light"))) + .isEqualTo(R.style.SuwThemeGlifV2_Light); + assertThat( + themeResolver.resolve( + new Intent(originalIntent).putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v3"))) + .isEqualTo(R.style.SuwThemeGlifV3); + assertThat( + themeResolver.resolve( + new Intent(originalIntent) + .putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v3_light"))) + .isEqualTo(R.style.SuwThemeGlifV3_Light); + assertThat( + themeResolver.resolve( + new Intent(originalIntent) + .putExtra(WizardManagerHelper.EXTRA_THEME, "unknown_theme"))) + .isEqualTo(defaultTheme); + } + + @Test + public void applyTheme_glifV3_shouldSetActivityThemeToGlifV3() { + @StyleRes int defaultTheme = 12345; + ThemeResolver themeResolver = + new ThemeResolver.Builder().setUseDayNight(false).setDefaultTheme(defaultTheme).build(); + + Activity activity = + Robolectric.buildActivity( + Activity.class, + new Intent() + .putExtra(WizardManagerHelper.EXTRA_THEME, WizardManagerHelper.THEME_GLIF_V3)) + .setup() + .get(); + + themeResolver.applyTheme(activity); + + assertThat(shadowOf(activity).callGetThemeResId()).isEqualTo(R.style.SuwThemeGlifV3); + } + + @Test + public void setDefault_shouldSetDefaultResolver() { + ThemeResolver themeResolver = new ThemeResolver.Builder().setUseDayNight(false).build(); + + ThemeResolver.setDefault(themeResolver); + assertThat(ThemeResolver.getDefault()).isSameAs(themeResolver); + } +} diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java index 616ccdd..e302cab 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java @@ -16,10 +16,10 @@ package com.android.setupwizardlib.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.robolectric.RuntimeEnvironment.application; +import static org.robolectric.Shadows.shadowOf; import android.annotation.TargetApi; import android.app.Activity; @@ -29,299 +29,343 @@ import android.os.Bundle; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; - import androidx.annotation.StyleRes; - import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(sdk = Config.NEWEST_SDK) public class WizardManagerHelperTest { - @Test - public void testGetNextIntent() { - final Intent intent = new Intent("test.intent.ACTION"); - intent.putExtra("scriptUri", "android-resource://test-script"); - intent.putExtra("actionId", "test_action_id"); - intent.putExtra("theme", "test_theme"); - intent.putExtra("ignoreExtra", "poof"); // extra is ignored because it's not known - - final Intent data = new Intent(); - data.putExtra("extraData", "shazam"); - - final Intent nextIntent = - WizardManagerHelper.getNextIntent(intent, Activity.RESULT_OK, data); - assertEquals("Next intent action should be NEXT", "com.android.wizard.NEXT", - nextIntent.getAction()); - assertEquals("Script URI should be the same as original intent", - "android-resource://test-script", nextIntent.getStringExtra("scriptUri")); - assertEquals("Action ID should be the same as original intent", "test_action_id", - nextIntent.getStringExtra("actionId")); - assertEquals("Theme extra should be the same as original intent", "test_theme", - nextIntent.getStringExtra("theme")); - assertFalse("ignoreExtra should not be in nextIntent", nextIntent.hasExtra("ignoreExtra")); - assertEquals("Result code extra should be RESULT_OK", Activity.RESULT_OK, - nextIntent.getIntExtra("com.android.setupwizard.ResultCode", 0)); - assertEquals("Extra data should surface as extra in nextIntent", "shazam", - nextIntent.getStringExtra("extraData")); - } - - @Test - public void testIsSetupWizardTrue() { - final Intent intent = new Intent(); - intent.putExtra("firstRun", true); - assertTrue("Is setup wizard should be true", - WizardManagerHelper.isSetupWizardIntent(intent)); - } - - @Test - public void testIsDeferredSetupTrue() { - final Intent intent = new Intent(); - intent.putExtra("deferredSetup", true); - assertTrue("Is deferred setup wizard should be true", - WizardManagerHelper.isDeferredSetupWizard(intent)); - } - - @Test - public void testIsPreDeferredSetupTrue() { - final Intent intent = new Intent(); - intent.putExtra("preDeferredSetup", true); - assertTrue("Is pre-deferred setup wizard should be true", - WizardManagerHelper.isPreDeferredSetupWizard(intent)); - } - - @Test - public void testIsSetupWizardFalse() { - final Intent intent = new Intent(); - intent.putExtra("firstRun", false); - assertFalse("Is setup wizard should be true", - WizardManagerHelper.isSetupWizardIntent(intent)); - } - - @Test - public void isLightTheme_shouldReturnTrue_whenThemeIsLight() { - List<String> lightThemes = Arrays.asList( - "holo_light", - "material_light", - "glif_light", - "glif_v2_light", - "glif_v3_light" - ); - ArrayList<String> unexpectedIntentThemes = new ArrayList<>(); - ArrayList<String> unexpectedStringThemes = new ArrayList<>(); - for (final String theme : lightThemes) { - Intent intent = new Intent(); - intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); - if (!WizardManagerHelper.isLightTheme(intent, false)) { - unexpectedIntentThemes.add(theme); - } - if (!WizardManagerHelper.isLightTheme(theme, false)) { - unexpectedStringThemes.add(theme); - } - } - assertTrue("Intent themes " + unexpectedIntentThemes + " should be light", - unexpectedIntentThemes.isEmpty()); - assertTrue("String themes " + unexpectedStringThemes + " should be light", - unexpectedStringThemes.isEmpty()); - } - - @Test - public void isLightTheme_shouldReturnFalse_whenThemeIsNotLight() { - List<String> lightThemes = Arrays.asList( - "holo", - "material", - "glif", - "glif_v2", - "glif_v3" - ); - ArrayList<String> unexpectedIntentThemes = new ArrayList<>(); - ArrayList<String> unexpectedStringThemes = new ArrayList<>(); - for (final String theme : lightThemes) { - Intent intent = new Intent(); - intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); - if (WizardManagerHelper.isLightTheme(intent, true)) { - unexpectedIntentThemes.add(theme); - } - if (WizardManagerHelper.isLightTheme(theme, true)) { - unexpectedStringThemes.add(theme); - } - } - assertTrue("Intent themes " + unexpectedIntentThemes + " should not be light", - unexpectedIntentThemes.isEmpty()); - assertTrue("String themes " + unexpectedStringThemes + " should not be light", - unexpectedStringThemes.isEmpty()); - } - - @Test - public void testIsLightThemeDefault() { - final Intent intent = new Intent(); - intent.putExtra("theme", "abracadabra"); - assertTrue("isLightTheme should return default value true", - WizardManagerHelper.isLightTheme(intent, true)); - assertFalse("isLightTheme should return default value false", - WizardManagerHelper.isLightTheme(intent, false)); - } - - @Test - public void testIsLightThemeUnspecified() { - final Intent intent = new Intent(); - assertTrue("isLightTheme should return default value true", - WizardManagerHelper.isLightTheme(intent, true)); - assertFalse("isLightTheme should return default value false", - WizardManagerHelper.isLightTheme(intent, false)); - } - - @Test - public void testGetThemeResGlifV3Light() { - assertEquals(R.style.SuwThemeGlifV3_Light, - WizardManagerHelper.getThemeRes("glif_v3_light", 0)); - } - - @Test - public void testGetThemeResGlifV3() { - assertEquals(R.style.SuwThemeGlifV3, - WizardManagerHelper.getThemeRes("glif_v3", 0)); + @Test + public void testGetNextIntent() { + final Intent intent = new Intent("test.intent.ACTION"); + intent.putExtra("scriptUri", "android-resource://test-script"); + intent.putExtra("actionId", "test_action_id"); + intent.putExtra("theme", "test_theme"); + intent.putExtra("ignoreExtra", "poof"); // extra is ignored because it's not known + + final Intent data = new Intent(); + data.putExtra("extraData", "shazam"); + + final Intent nextIntent = WizardManagerHelper.getNextIntent(intent, Activity.RESULT_OK, data); + assertWithMessage("Next intent action should be NEXT") + .that(nextIntent.getAction()) + .isEqualTo("com.android.wizard.NEXT"); + assertWithMessage("Script URI should be the same as original intent") + .that(nextIntent.getStringExtra("scriptUri")) + .isEqualTo("android-resource://test-script"); + assertWithMessage("Action ID should be the same as original intent") + .that(nextIntent.getStringExtra("actionId")) + .isEqualTo("test_action_id"); + assertWithMessage("Theme extra should be the same as original intent") + .that(nextIntent.getStringExtra("theme")) + .isEqualTo("test_theme"); + assertWithMessage("ignoreExtra should not be in nextIntent") + .that(nextIntent.hasExtra("ignoreExtra")) + .isFalse(); + assertWithMessage("Result code extra should be RESULT_OK") + .that(nextIntent.getIntExtra("com.android.setupwizard.ResultCode", 0)) + .isEqualTo(Activity.RESULT_OK); + assertWithMessage("Extra data should surface as extra in nextIntent") + .that(nextIntent.getStringExtra("extraData")) + .isEqualTo("shazam"); + } + + @Test + public void testIsSetupWizardTrue() { + final Intent intent = new Intent(); + intent.putExtra("firstRun", true); + assertWithMessage("Is setup wizard should be true") + .that(WizardManagerHelper.isSetupWizardIntent(intent)) + .isTrue(); + } + + @Test + public void testIsDeferredSetupTrue() { + final Intent intent = new Intent(); + intent.putExtra("deferredSetup", true); + assertWithMessage("Is deferred setup wizard should be true") + .that(WizardManagerHelper.isDeferredSetupWizard(intent)) + .isTrue(); + } + + @Test + public void testIsPreDeferredSetupTrue() { + final Intent intent = new Intent(); + intent.putExtra("preDeferredSetup", true); + assertWithMessage("Is pre-deferred setup wizard should be true") + .that(WizardManagerHelper.isPreDeferredSetupWizard(intent)) + .isTrue(); + } + + @Test + public void testIsSetupWizardFalse() { + final Intent intent = new Intent(); + intent.putExtra("firstRun", false); + assertWithMessage("Is setup wizard should be true") + .that(WizardManagerHelper.isSetupWizardIntent(intent)) + .isFalse(); + } + + @Test + public void isLightTheme_shouldReturnTrue_whenThemeIsLight() { + List<String> lightThemes = + Arrays.asList( + "holo_light", "material_light", "glif_light", "glif_v2_light", "glif_v3_light"); + ArrayList<String> unexpectedIntentThemes = new ArrayList<>(); + ArrayList<String> unexpectedStringThemes = new ArrayList<>(); + for (final String theme : lightThemes) { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); + if (!WizardManagerHelper.isLightTheme(intent, false)) { + unexpectedIntentThemes.add(theme); + } + if (!WizardManagerHelper.isLightTheme(theme, false)) { + unexpectedStringThemes.add(theme); + } } - - @Test - public void testGetThemeResGlifV2Light() { - assertEquals(R.style.SuwThemeGlifV2_Light, - WizardManagerHelper.getThemeRes("glif_v2_light", 0)); - } - - @Test - public void testGetThemeResGlifV2() { - assertEquals(R.style.SuwThemeGlifV2, - WizardManagerHelper.getThemeRes("glif_v2", 0)); - } - - @Test - public void testGetThemeResGlifLight() { - assertEquals(R.style.SuwThemeGlif_Light, - WizardManagerHelper.getThemeRes("glif_light", 0)); - } - - @Test - public void testGetThemeResGlif() { - assertEquals(R.style.SuwThemeGlif, - WizardManagerHelper.getThemeRes("glif", 0)); - } - - @Test - public void testGetThemeResMaterialLight() { - assertEquals(R.style.SuwThemeMaterial_Light, - WizardManagerHelper.getThemeRes("material_light", 0)); - } - - @Test - public void testGetThemeResMaterial() { - assertEquals(R.style.SuwThemeMaterial, - WizardManagerHelper.getThemeRes("material", 0)); - } - - @Test - public void testGetThemeResDefault() { - @StyleRes int def = 123; - assertEquals(def, WizardManagerHelper.getThemeRes("abracadabra", def)); - } - - @Test - public void testGetThemeResNull() { - @StyleRes int def = 123; - assertEquals(def, WizardManagerHelper.getThemeRes((String) null, def)); - } - - @Test - public void testGetThemeResFromIntent() { - Intent intent = new Intent(); - intent.putExtra(WizardManagerHelper.EXTRA_THEME, "material"); - assertEquals(R.style.SuwThemeMaterial, WizardManagerHelper.getThemeRes(intent, 0)); - } - - @Test - public void testCopyWizardManagerIntent() { - Bundle wizardBundle = new Bundle(); - wizardBundle.putString("foo", "bar"); - Intent originalIntent = new Intent() - .putExtra(WizardManagerHelper.EXTRA_THEME, "test_theme") - .putExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE, wizardBundle) - .putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true) - .putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true) - .putExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, true) - // Script URI and Action ID are kept for backwards compatibility - .putExtra(WizardManagerHelper.EXTRA_SCRIPT_URI, "test_script_uri") - .putExtra(WizardManagerHelper.EXTRA_ACTION_ID, "test_action_id"); - - Intent intent = new Intent("test.intent.action"); - WizardManagerHelper.copyWizardManagerExtras(originalIntent, intent); - - assertEquals("Intent action should be kept", "test.intent.action", intent.getAction()); - assertEquals("EXTRA_THEME should be copied", - "test_theme", intent.getStringExtra(WizardManagerHelper.EXTRA_THEME)); - Bundle copiedWizardBundle = - intent.getParcelableExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE); - assertEquals("Wizard bundle should be copied", "bar", copiedWizardBundle.getString("foo")); - - assertTrue("EXTRA_IS_FIRST_RUN should be copied", - intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, false)); - assertTrue("EXTRA_IS_DEFERRED_SETUP should be copied", - intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, false)); - assertTrue("EXTRA_IS_PRE_DEFERRED_SETUP should be copied", - intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, false)); - - // Script URI and Action ID are replaced by Wizard Bundle in M, but are kept for backwards - // compatibility - assertEquals("EXTRA_SCRIPT_URI should be copied", - "test_script_uri", intent.getStringExtra(WizardManagerHelper.EXTRA_SCRIPT_URI)); - assertEquals("EXTRA_ACTION_ID should be copied", - "test_action_id", intent.getStringExtra(WizardManagerHelper.EXTRA_ACTION_ID)); - } - - @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) - @Test - public void testIsUserSetupComplete() { - Settings.Secure.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); - Settings.Secure.putInt(application.getContentResolver(), "user_setup_complete", 1); - assertTrue(WizardManagerHelper.isUserSetupComplete(application)); - - Settings.Secure.putInt(application.getContentResolver(), "user_setup_complete", 0); - assertFalse(WizardManagerHelper.isUserSetupComplete(application)); - } - - @Test - @Config(sdk = VERSION_CODES.JELLY_BEAN) - public void testIsUserSetupCompleteCompat() { - Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 1); - assertTrue(WizardManagerHelper.isUserSetupComplete(application)); - - Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 0); - assertFalse(WizardManagerHelper.isUserSetupComplete(application)); - } - - @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) - @Test - public void testIsDeviceProvisioned() { - Settings.Secure.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); - assertTrue(WizardManagerHelper.isDeviceProvisioned(application)); - Settings.Secure.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 0); - assertFalse(WizardManagerHelper.isDeviceProvisioned(application)); - } - - @Test - @Config(sdk = VERSION_CODES.JELLY_BEAN) - public void testIsDeviceProvisionedCompat() { - Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 1); - assertTrue(WizardManagerHelper.isDeviceProvisioned(application)); - Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 0); - assertFalse(WizardManagerHelper.isDeviceProvisioned(application)); + assertWithMessage("Intent themes " + unexpectedIntentThemes + " should be light") + .that(unexpectedIntentThemes.isEmpty()) + .isTrue(); + assertWithMessage("String themes " + unexpectedStringThemes + " should be light") + .that(unexpectedStringThemes.isEmpty()) + .isTrue(); + } + + @Test + public void isLightTheme_shouldReturnFalse_whenThemeIsNotLight() { + List<String> lightThemes = Arrays.asList("holo", "material", "glif", "glif_v2", "glif_v3"); + ArrayList<String> unexpectedIntentThemes = new ArrayList<>(); + ArrayList<String> unexpectedStringThemes = new ArrayList<>(); + for (final String theme : lightThemes) { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); + if (WizardManagerHelper.isLightTheme(intent, true)) { + unexpectedIntentThemes.add(theme); + } + if (WizardManagerHelper.isLightTheme(theme, true)) { + unexpectedStringThemes.add(theme); + } } + assertWithMessage("Intent themes " + unexpectedIntentThemes + " should not be light") + .that(unexpectedIntentThemes.isEmpty()) + .isTrue(); + assertWithMessage("String themes " + unexpectedStringThemes + " should not be light") + .that(unexpectedStringThemes.isEmpty()) + .isTrue(); + } + + @Test + public void getThemeRes_whenOldestSupportedThemeTakeEffect_shouldReturnDefault() { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, "material"); + assertThat(WizardManagerHelper.getThemeRes(intent, 0, WizardManagerHelper.THEME_GLIF_V2)) + .isEqualTo(0); + } + + @Test + public void getThemeRes_whenOldestSupportedThemeNotTakeEffect_shouldReturnCurrent() { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v3"); + assertThat(WizardManagerHelper.getThemeRes(intent, 0, WizardManagerHelper.THEME_GLIF_V2)) + .isEqualTo(R.style.SuwThemeGlifV3); + } + + @Test + public void testIsLightThemeDefault() { + final Intent intent = new Intent(); + intent.putExtra("theme", "abracadabra"); + assertWithMessage("isLightTheme should return default value true") + .that(WizardManagerHelper.isLightTheme(intent, true)) + .isTrue(); + assertWithMessage("isLightTheme should return default value false") + .that(WizardManagerHelper.isLightTheme(intent, false)) + .isFalse(); + } + + @Test + public void testIsLightThemeUnspecified() { + final Intent intent = new Intent(); + assertWithMessage("isLightTheme should return default value true") + .that(WizardManagerHelper.isLightTheme(intent, true)) + .isTrue(); + assertWithMessage("isLightTheme should return default value false") + .that(WizardManagerHelper.isLightTheme(intent, false)) + .isFalse(); + } + + @Test + public void testGetThemeResGlifV3Light() { + assertThat(WizardManagerHelper.getThemeRes("glif_v3_light", 0)) + .isEqualTo(R.style.SuwThemeGlifV3_Light); + } + + @Test + public void testGetThemeResGlifV3() { + assertThat(WizardManagerHelper.getThemeRes("glif_v3", 0)).isEqualTo(R.style.SuwThemeGlifV3); + } + + @Test + public void testGetThemeResGlifV2Light() { + assertThat(WizardManagerHelper.getThemeRes("glif_v2_light", 0)) + .isEqualTo(R.style.SuwThemeGlifV2_Light); + } + + @Test + public void testGetThemeResGlifV2() { + assertThat(WizardManagerHelper.getThemeRes("glif_v2", 0)).isEqualTo(R.style.SuwThemeGlifV2); + } + + @Test + public void testGetThemeResGlifLight() { + assertThat(WizardManagerHelper.getThemeRes("glif_light", 0)) + .isEqualTo(R.style.SuwThemeGlif_Light); + } + + @Test + public void testGetThemeResGlif() { + assertThat(WizardManagerHelper.getThemeRes("glif", 0)).isEqualTo(R.style.SuwThemeGlif); + } + + @Test + public void testGetThemeResMaterialLight() { + assertThat(WizardManagerHelper.getThemeRes("material_light", 0)) + .isEqualTo(R.style.SuwThemeMaterial_Light); + } + + @Test + public void testGetThemeResMaterial() { + assertThat(WizardManagerHelper.getThemeRes("material", 0)).isEqualTo(R.style.SuwThemeMaterial); + } + + @Test + public void testGetThemeResDefault() { + @StyleRes int def = 123; + assertThat(WizardManagerHelper.getThemeRes("abracadabra", def)).isEqualTo(def); + } + + @Test + public void testGetThemeResNull() { + @StyleRes int def = 123; + assertThat(WizardManagerHelper.getThemeRes((String) null, def)).isEqualTo(def); + } + + @Test + public void testGetThemeResFromIntent() { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, "material"); + assertThat(WizardManagerHelper.getThemeRes(intent, 0)).isEqualTo(R.style.SuwThemeMaterial); + } + + @Test + public void testCopyWizardManagerIntent() { + Bundle wizardBundle = new Bundle(); + wizardBundle.putString("foo", "bar"); + Intent originalIntent = + new Intent() + .putExtra(WizardManagerHelper.EXTRA_THEME, "test_theme") + .putExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE, wizardBundle) + .putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true) + .putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true) + .putExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, true) + // Script URI and Action ID are kept for backwards compatibility + .putExtra(WizardManagerHelper.EXTRA_SCRIPT_URI, "test_script_uri") + .putExtra(WizardManagerHelper.EXTRA_ACTION_ID, "test_action_id"); + + Intent intent = new Intent("test.intent.action"); + WizardManagerHelper.copyWizardManagerExtras(originalIntent, intent); + + assertWithMessage("Intent action should be kept") + .that(intent.getAction()) + .isEqualTo("test.intent.action"); + assertWithMessage("EXTRA_THEME should be copied") + .that(intent.getStringExtra(WizardManagerHelper.EXTRA_THEME)) + .isEqualTo("test_theme"); + Bundle copiedWizardBundle = intent.getParcelableExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE); + assertWithMessage("Wizard bundle should be copied") + .that(copiedWizardBundle.getString("foo")) + .isEqualTo("bar"); + + assertWithMessage("EXTRA_IS_FIRST_RUN should be copied") + .that(intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, false)) + .isTrue(); + assertWithMessage("EXTRA_IS_DEFERRED_SETUP should be copied") + .that(intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, false)) + .isTrue(); + assertWithMessage("EXTRA_IS_PRE_DEFERRED_SETUP should be copied") + .that(intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, false)) + .isTrue(); + + // Script URI and Action ID are replaced by Wizard Bundle in M, but are kept for backwards + // compatibility + assertWithMessage("EXTRA_SCRIPT_URI should be copied") + .that(intent.getStringExtra(WizardManagerHelper.EXTRA_SCRIPT_URI)) + .isEqualTo("test_script_uri"); + assertWithMessage("EXTRA_ACTION_ID should be copied") + .that(intent.getStringExtra(WizardManagerHelper.EXTRA_ACTION_ID)) + .isEqualTo("test_action_id"); + } + + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + @Test + public void testIsUserSetupComplete() { + Settings.Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + Settings.Secure.putInt(application.getContentResolver(), "user_setup_complete", 1); + assertThat(WizardManagerHelper.isUserSetupComplete(application)).isTrue(); + + Settings.Secure.putInt(application.getContentResolver(), "user_setup_complete", 0); + assertThat(WizardManagerHelper.isUserSetupComplete(application)).isFalse(); + } + + @Test + @Config(sdk = VERSION_CODES.JELLY_BEAN) + public void testIsUserSetupCompleteCompat() { + Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 1); + assertThat(WizardManagerHelper.isUserSetupComplete(application)).isTrue(); + + Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 0); + assertThat(WizardManagerHelper.isUserSetupComplete(application)).isFalse(); + } + + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + @Test + public void testIsDeviceProvisioned() { + Settings.Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + assertThat(WizardManagerHelper.isDeviceProvisioned(application)).isTrue(); + Settings.Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + assertThat(WizardManagerHelper.isDeviceProvisioned(application)).isFalse(); + } + + @Test + @Config(sdk = VERSION_CODES.JELLY_BEAN) + public void testIsDeviceProvisionedCompat() { + Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 1); + assertThat(WizardManagerHelper.isDeviceProvisioned(application)).isTrue(); + Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 0); + assertThat(WizardManagerHelper.isDeviceProvisioned(application)).isFalse(); + } + + @Test + public void applyTheme_glifDayNight_shouldApplyThemeToActivity() { + Activity activity = + Robolectric.buildActivity( + Activity.class, + new Intent() + .putExtra( + WizardManagerHelper.EXTRA_THEME, WizardManagerHelper.THEME_GLIF_LIGHT)) + .setup() + .get(); + + WizardManagerHelper.applyTheme(activity); + + assertThat(shadowOf(activity).callGetThemeResId()).isEqualTo(R.style.SuwThemeGlif_DayNight); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java index ae4f3d1..001f1da 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java @@ -16,72 +16,73 @@ package com.android.setupwizardlib.view; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; import static org.robolectric.RuntimeEnvironment.application; import android.view.View; import android.view.View.MeasureSpec; - -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(SuwLibRobolectricTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class FillContentLayoutTest { - @Test - public void testMeasureMinSize() { - FillContentLayout layout = new FillContentLayout( - application, - Robolectric.buildAttributeSet() - .addAttribute(android.R.attr.minWidth, "123dp") - .addAttribute(android.R.attr.minHeight, "123dp") - .build()); - layout.measure( - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + @Test + public void testMeasureMinSize() { + FillContentLayout layout = + new FillContentLayout( + application, + Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.minWidth, "123dp") + .addAttribute(android.R.attr.minHeight, "123dp") + .build()); + layout.measure( + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - assertEquals(123, layout.getMeasuredWidth()); - assertEquals(123, layout.getMeasuredHeight()); - } + assertThat(layout.getMeasuredWidth()).isEqualTo(123); + assertThat(layout.getMeasuredHeight()).isEqualTo(123); + } - @Test - public void testMeasureChildIsSmallerThanMaxSize() { - View child = new View(application); - FillContentLayout layout = new FillContentLayout( - application, - Robolectric.buildAttributeSet() - .addAttribute(android.R.attr.maxWidth, "123dp") - .addAttribute(android.R.attr.maxHeight, "123dp") - .build()); - layout.addView(child); - layout.measure( - MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY)); + @Test + public void testMeasureChildIsSmallerThanMaxSize() { + View child = new View(application); + FillContentLayout layout = + new FillContentLayout( + application, + Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.maxWidth, "123dp") + .addAttribute(android.R.attr.maxHeight, "123dp") + .build()); + layout.addView(child); + layout.measure( + MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY)); - assertEquals(123, child.getMeasuredWidth()); - assertEquals(123, child.getMeasuredHeight()); - } + assertThat(child.getMeasuredWidth()).isEqualTo(123); + assertThat(child.getMeasuredHeight()).isEqualTo(123); + } - @Test - public void testMeasureChildIsSmallerThanParent() { - View child = new View(application); - FillContentLayout layout = new FillContentLayout( - application, - Robolectric.buildAttributeSet() - .addAttribute(android.R.attr.maxWidth, "123dp") - .addAttribute(android.R.attr.maxHeight, "123dp") - .build()); - layout.addView(child); - layout.measure( - MeasureSpec.makeMeasureSpec(88, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(88, MeasureSpec.EXACTLY)); + @Test + public void testMeasureChildIsSmallerThanParent() { + View child = new View(application); + FillContentLayout layout = + new FillContentLayout( + application, + Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.maxWidth, "123dp") + .addAttribute(android.R.attr.maxHeight, "123dp") + .build()); + layout.addView(child); + layout.measure( + MeasureSpec.makeMeasureSpec(88, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(88, MeasureSpec.EXACTLY)); - assertEquals(88, child.getMeasuredWidth()); - assertEquals(88, child.getMeasuredHeight()); - } + assertThat(child.getMeasuredWidth()).isEqualTo(88); + assertThat(child.getMeasuredHeight()).isEqualTo(88); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java index 0e0e99c..e980563 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java @@ -17,204 +17,223 @@ package com.android.setupwizardlib.view; import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; -import android.annotation.TargetApi; -import android.content.Context; +import android.app.Activity; import android.graphics.SurfaceTexture; -import android.media.MediaPlayer; -import android.os.Build.VERSION_CODES; -import android.view.Surface; -import android.view.View; - +import android.net.Uri; import androidx.annotation.RawRes; - +import android.view.View; import com.android.setupwizardlib.R; -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; -import com.android.setupwizardlib.shadow.ShadowLog; -import com.android.setupwizardlib.shadow.ShadowLog.TerribleFailure; -import com.android.setupwizardlib.view.IllustrationVideoViewTest.ShadowMockMediaPlayer; -import com.android.setupwizardlib.view.IllustrationVideoViewTest.ShadowSurface; - -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowMediaPlayer; +import org.robolectric.shadows.ShadowMediaPlayer.InvalidStateBehavior; +import org.robolectric.shadows.ShadowMediaPlayer.MediaInfo; +import org.robolectric.shadows.util.DataSource; import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; -@RunWith(SuwLibRobolectricTestRunner.class) -@Config( - sdk = Config.NEWEST_SDK, - shadows = { - ShadowLog.class, - ShadowMockMediaPlayer.class, - ShadowSurface.class - }) +@RunWith(RobolectricTestRunner.class) +@Config(sdk = Config.NEWEST_SDK) public class IllustrationVideoViewTest { - @Mock - private SurfaceTexture mSurfaceTexture; - - private IllustrationVideoView mView; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @After - public void tearDown() { - ShadowMockMediaPlayer.reset(); - } - - @Test - public void nullMediaPlayer_shouldThrowWtf() { - ShadowMockMediaPlayer.sMediaPlayer = null; - try { - createDefaultView(); - fail("WTF should be thrown for null media player"); - } catch (TerribleFailure e) { - // pass - } - } - - @Test - public void onVisibilityChanged_notVisible_shouldRelease() { - createDefaultView(); - mView.onWindowVisibilityChanged(View.GONE); - - verify(ShadowMockMediaPlayer.sMediaPlayer).release(); - assertThat(mView.mSurface).isNull(); - assertThat(mView.mMediaPlayer).isNull(); - } - - @Test - public void onVisibilityChanged_visible_shouldPlay() { - createDefaultView(); - - mView.onWindowVisibilityChanged(View.GONE); - assertThat(mView.mSurface).isNull(); - assertThat(mView.mMediaPlayer).isNull(); - - mView.onWindowVisibilityChanged(View.VISIBLE); - - assertThat(mView.mSurface).isNotNull(); - assertThat(mView.mMediaPlayer).isNotNull(); - } - - @Test - public void testPausedWhenWindowFocusLost() { - createDefaultView(); - mView.start(); - - assertNotNull(mView.mMediaPlayer); - assertNotNull(mView.mSurface); - - mView.onWindowFocusChanged(false); - verify(ShadowMockMediaPlayer.getMock()).pause(); - } - - @Test - public void testStartedWhenWindowFocusRegained() { - testPausedWhenWindowFocusLost(); - - // Clear verifications for calls in the other test - reset(ShadowMockMediaPlayer.getMock()); - - mView.onWindowFocusChanged(true); - verify(ShadowMockMediaPlayer.getMock()).start(); - } - - @Test - public void testSurfaceReleasedWhenTextureDestroyed() { - createDefaultView(); - mView.start(); - - assertNotNull(mView.mMediaPlayer); - assertNotNull(mView.mSurface); - - mView.onSurfaceTextureDestroyed(mSurfaceTexture); - verify(ShadowMockMediaPlayer.getMock()).release(); - } - - @Test - public void testXmlSetVideoResId() { - createDefaultView(); - assertEquals(android.R.color.white, ShadowMockMediaPlayer.sResId); - } - - @Test - public void testSetVideoResId() { - createDefaultView(); - - @RawRes int black = android.R.color.black; - mView.setVideoResource(black); - - assertEquals(android.R.color.black, ShadowMockMediaPlayer.sResId); - } - - private void createDefaultView() { - mView = new IllustrationVideoView( - application, - Robolectric.buildAttributeSet() - // Any resource attribute should work, since the media player is mocked - .addAttribute(R.attr.suwVideo, "@android:color/white") - .build()); - mView.setSurfaceTexture(mock(SurfaceTexture.class)); - mView.onSurfaceTextureAvailable(mSurfaceTexture, 500, 500); - } - - @Implements(MediaPlayer.class) - public static class ShadowMockMediaPlayer extends ShadowMediaPlayer { - - private static MediaPlayer sMediaPlayer = mock(MediaPlayer.class); - private static int sResId; - - public static void reset() { - sMediaPlayer = mock(MediaPlayer.class); - sResId = 0; - } - - @Implementation - public static MediaPlayer create(Context context, int resId) { - sResId = resId; - return sMediaPlayer; - } - - public static MediaPlayer getMock() { - return sMediaPlayer; - } - } - - @Implements(Surface.class) - @TargetApi(VERSION_CODES.HONEYCOMB) - public static class ShadowSurface extends org.robolectric.shadows.ShadowSurface { - - @RealObject - private Surface mRealSurface; - - public void __constructor__(SurfaceTexture surfaceTexture) { - // Call the constructor on the real object, so that critical fields such as mLock is - // initialized properly. - Shadow.invokeConstructor(Surface.class, mRealSurface, - ReflectionHelpers.ClassParameter.from(SurfaceTexture.class, surfaceTexture)); - super.__constructor__(surfaceTexture); - } - } + @Mock private SurfaceTexture surfaceTexture; + + private IllustrationVideoView view; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowMediaPlayer.addMediaInfo( + DataSource.toDataSource( + "android.resource://" + application.getPackageName() + "/" + android.R.color.white), + new ShadowMediaPlayer.MediaInfo(100, 10)); + ShadowMediaPlayer.addMediaInfo( + DataSource.toDataSource( + "android.resource://" + application.getPackageName() + "/" + android.R.color.black), + new ShadowMediaPlayer.MediaInfo(100, 10)); + } + + @Test + public void testPausedWhenWindowFocusLost() { + createDefaultView(); + Robolectric.flushForegroundThreadScheduler(); + view.start(); + + assertThat(view.mMediaPlayer).isNotNull(); + assertThat(view.surface).isNotNull(); + + view.onWindowFocusChanged(false); + assertThat(getShadowMediaPlayer().getState()).isEqualTo(ShadowMediaPlayer.State.PAUSED); + } + + @Test + public void testStartedWhenWindowFocusRegained() { + testPausedWhenWindowFocusLost(); + Robolectric.flushForegroundThreadScheduler(); + + view.onWindowFocusChanged(true); + assertThat(getShadowMediaPlayer().getState()).isEqualTo(ShadowMediaPlayer.State.STARTED); + } + + @Test + public void testSurfaceReleasedWhenTextureDestroyed() { + createDefaultView(); + view.start(); + + assertThat(view.mMediaPlayer).isNotNull(); + assertThat(view.surface).isNotNull(); + + // MediaPlayer is set to null after destroy. Retrieve it first before we call destroy. + ShadowMediaPlayer shadowMediaPlayer = getShadowMediaPlayer(); + view.onSurfaceTextureDestroyed(surfaceTexture); + assertThat(shadowMediaPlayer.getState()).isEqualTo(ShadowMediaPlayer.State.END); + } + + @Test + public void testXmlSetVideoResId() { + createDefaultView(); + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://com.android.setupwizardlib/" + android.R.color.white); + } + + @Test + public void testSetVideoResId() { + createDefaultView(); + + @RawRes int black = android.R.color.black; + view.setVideoResource(black); + + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://com.android.setupwizardlib/" + android.R.color.black); + } + + @Test + public void prepareVideo_shouldSetAspectRatio() { + createDefaultView(); + + ReflectionHelpers.setField(getShadowMediaPlayer(), "videoWidth", 720); + ReflectionHelpers.setField(getShadowMediaPlayer(), "videoHeight", 1280); + + Robolectric.flushForegroundThreadScheduler(); + view.start(); + + view.measure( + View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY)); + + final float aspectRatio = (float) view.getMeasuredHeight() / view.getMeasuredWidth(); + assertThat(aspectRatio).isWithin(0.001f).of(1280f / 720f); + } + + @Test + public void prepareVideo_zeroHeight_shouldSetAspectRatioToZero() { + createDefaultView(); + + ReflectionHelpers.setField(getShadowMediaPlayer(), "videoWidth", 720); + ReflectionHelpers.setField(getShadowMediaPlayer(), "videoHeight", 0); + + Robolectric.flushForegroundThreadScheduler(); + view.start(); + + final float aspectRatio = (float) view.getHeight() / view.getWidth(); + assertThat(aspectRatio).isEqualTo(0.0f); + } + + @Test + public void setVideoResId_resetDiffVideoResFromDiffPackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + // reset different videoRes from different package + String newPackageName = "com.android.fakepackage"; + @RawRes int black = android.R.color.black; + addMediaInfo(black, newPackageName); + view.setVideoResource(black, newPackageName); + + // should be reset to black with the new package + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://" + newPackageName + "/" + android.R.color.black); + } + + @Test + public void setVideoResId_resetDiffVideoResFromSamePackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + // reset different videoRes from the same package(default package) + String defaultPackageName = "com.android.setupwizardlib"; + @RawRes int black = android.R.color.black; + addMediaInfo(black, defaultPackageName); + view.setVideoResource(black, defaultPackageName); + + // should be reset to black with the same package(default package) + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://" + defaultPackageName + "/" + android.R.color.black); + } + + @Test + public void setVideoResId_resetSameVideoResFromDifferentPackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + // reset same videoRes from different package + @RawRes int white = android.R.color.white; + String newPackageName = "com.android.fakepackage"; + addMediaInfo(white, newPackageName); + view.setVideoResource(white, newPackageName); + + // should be white with the new package + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://" + newPackageName + "/" + android.R.color.white); + } + + private ShadowMediaPlayer getShadowMediaPlayer() { + return Shadows.shadowOf(view.mMediaPlayer); + } + + private void createDefaultView() { + view = + new IllustrationVideoView( + application, + Robolectric.buildAttributeSet() + // Any resource attribute should work, since the data source is fake + .addAttribute(R.attr.suwVideo, "@android:color/white") + .build()); + + Activity activity = Robolectric.setupActivity(Activity.class); + activity.setContentView(view); + setWindowVisible(); + + view.setSurfaceTexture(mock(SurfaceTexture.class)); + view.onSurfaceTextureAvailable(surfaceTexture, 500, 500); + getShadowMediaPlayer().setInvalidStateBehavior(InvalidStateBehavior.EMULATE); + } + + private void setWindowVisible() { + Object viewRootImpl = ReflectionHelpers.callInstanceMethod(view, "getViewRootImpl"); + ReflectionHelpers.callInstanceMethod( + viewRootImpl, "handleAppVisibility", ClassParameter.from(boolean.class, true)); + assertThat(view.isAttachedToWindow()).isTrue(); + assertThat(view.getWindowVisibility()).isEqualTo(View.VISIBLE); + } + + private void addMediaInfo(@RawRes int res, String packageName) { + ShadowMediaPlayer.addMediaInfo( + DataSource.toDataSource( + application, Uri.parse("android.resource://" + packageName + "/" + res), null), + new MediaInfo(5000, 1)); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java index f77de68..477c42a 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java @@ -17,15 +17,10 @@ package com.android.setupwizardlib.view; import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; @@ -39,201 +34,196 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.TextAppearanceSpan; import android.view.MotionEvent; - -import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod; - import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import java.util.Arrays; - -@RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class RichTextViewTest { - @Test - public void testLinkAnnotation() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); - - final CharSequence text = textView.getText(); - assertTrue("Text should be spanned", text instanceof Spanned); - - assertThat(textView.getMovementMethod()).isInstanceOf(TouchableLinkMovementMethod.class); - - Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); - assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); - - spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); - assertTrue("The span should be a LinkSpan", spans[0] instanceof LinkSpan); - assertEquals("The LinkSpan should have id \"foobar\"", - "foobar", ((LinkSpan) spans[0]).getId()); - } - - @Test - public void testOnLinkClickListener() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); + @Test + public void testLinkAnnotation() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + final CharSequence text = textView.getText(); + assertThat(text).isInstanceOf(Spanned.class); + + assertThat(textView.getMovementMethod()).isInstanceOf(TouchableLinkMovementMethod.class); - RichTextView textView = new RichTextView(application); - textView.setText(ssb); + Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); + assertThat(spans).isEmpty(); - OnLinkClickListener listener = mock(OnLinkClickListener.class); - textView.setOnLinkClickListener(listener); + spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + assertThat(spans).hasLength(1); + assertThat(spans[0]).isInstanceOf(LinkSpan.class); + assertWithMessage("The LinkSpan should have id \"foobar\"") + .that(((LinkSpan) spans[0]).getId()) + .isEqualTo("foobar"); + } + + @Test + public void testOnLinkClickListener() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); - assertSame(listener, textView.getOnLinkClickListener()); - - CharSequence text = textView.getText(); - LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - spans[0].onClick(textView); - - verify(listener).onLinkClick(eq(spans[0])); + OnLinkClickListener listener = mock(OnLinkClickListener.class); + textView.setOnLinkClickListener(listener); + + assertThat(textView.getOnLinkClickListener()).isSameAs(listener); + + CharSequence text = textView.getText(); + LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + spans[0].onClick(textView); + + verify(listener).onLinkClick(eq(spans[0])); + } + + @Test + public void testLegacyContextOnClickListener() { + // Click listener implemented by context should still be invoked for compatibility. + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + TestContext context = new TestContext(application); + context.delegate = mock(LinkSpan.OnClickListener.class); + RichTextView textView = new RichTextView(context); + textView.setText(ssb); + + CharSequence text = textView.getText(); + LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + spans[0].onClick(textView); + + verify(context.delegate).onClick(eq(spans[0])); + } + + @Test + public void onTouchEvent_clickOnLinks_shouldReturnTrue() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 0, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); + textView.setMovementMethod(mockMovementMethod); + + MotionEvent motionEvent = MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); + doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); + doReturn(true).when(mockMovementMethod).isLastTouchEventHandled(); + assertThat(textView.onTouchEvent(motionEvent)).isTrue(); + } + + @Test + public void onTouchEvent_clickOutsideLinks_shouldReturnFalse() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 0, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); + textView.setMovementMethod(mockMovementMethod); + + MotionEvent motionEvent = MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); + doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); + doReturn(false).when(mockMovementMethod).isLastTouchEventHandled(); + assertThat(textView.onTouchEvent(motionEvent)).isFalse(); + } + + @Test + public void testTextStyle() { + Annotation link = new Annotation("textAppearance", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + final CharSequence text = textView.getText(); + assertThat(text).isInstanceOf(Spanned.class); + + Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); + assertThat(spans).isEmpty(); + + spans = ((Spanned) text).getSpans(0, text.length(), TextAppearanceSpan.class); + assertThat(spans).hasLength(1); + assertThat(spans[0]).isInstanceOf(TextAppearanceSpan.class); + } + + @Test + public void testTextContainingLinksAreFocusable() { + Annotation testLink = new Annotation("link", "value"); + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); + spannableStringBuilder.setSpan(testLink, 0, 3, 0); + + RichTextView view = new RichTextView(application); + view.setText(spannableStringBuilder); + + assertThat(view.isFocusable()).named("view focusable").isTrue(); + } + + @SuppressLint("SetTextI18n") // It's OK. This is just a test. + @Test + public void testTextContainingNoLinksAreNotFocusable() { + RichTextView textView = new RichTextView(application); + textView.setText("Thou shall not be focusable!"); + + assertThat(textView.isFocusable()).named("view focusable").isFalse(); + } + + // Based on the text contents of the text view, the "focusable" property of the element + // should also be automatically changed. + @SuppressLint("SetTextI18n") // It's OK. This is just a test. + @Test + public void testRichTextViewFocusChangesWithTextChange() { + RichTextView textView = new RichTextView(application); + textView.setText("Thou shall not be focusable!"); + + assertThat(textView.isFocusable()).isFalse(); + assertThat(textView.isFocusableInTouchMode()).isFalse(); + + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("I am focusable"); + spannableStringBuilder.setSpan(new Annotation("link", "focus:on_me"), 0, 1, 0); + textView.setText(spannableStringBuilder); + assertThat(textView.isFocusable()).isTrue(); + if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { + assertThat(textView.isFocusableInTouchMode()).isTrue(); + assertThat(textView.getRevealOnFocusHint()).isFalse(); + } else { + assertThat(textView.isFocusableInTouchMode()).isFalse(); } + } - @Test - public void testLegacyContextOnClickListener() { - // Click listener implemented by context should still be invoked for compatibility. - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - TestContext context = spy(new TestContext(application)); - RichTextView textView = new RichTextView(context); - textView.setText(ssb); + public static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { - CharSequence text = textView.getText(); - LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - spans[0].onClick(textView); + LinkSpan.OnClickListener delegate; - verify(context).onClick(eq(spans[0])); + public TestContext(Context base) { + super(base); } - @Test - public void onTouchEvent_clickOnLinks_shouldReturnTrue() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 0, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); - - TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); - textView.setMovementMethod(mockMovementMethod); - - MotionEvent motionEvent = - MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); - doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); - doReturn(true).when(mockMovementMethod).isLastTouchEventHandled(); - assertThat(textView.onTouchEvent(motionEvent)).isTrue(); - } - - @Test - public void onTouchEvent_clickOutsideLinks_shouldReturnFalse() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 0, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); - - TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); - textView.setMovementMethod(mockMovementMethod); - - MotionEvent motionEvent = - MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); - doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); - doReturn(false).when(mockMovementMethod).isLastTouchEventHandled(); - assertThat(textView.onTouchEvent(motionEvent)).isFalse(); - } - - @Test - public void testTextStyle() { - Annotation link = new Annotation("textAppearance", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); - - final CharSequence text = textView.getText(); - assertTrue("Text should be spanned", text instanceof Spanned); - - Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); - assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); - - spans = ((Spanned) text).getSpans(0, text.length(), TextAppearanceSpan.class); - assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); - assertTrue("The span should be a TextAppearanceSpan", - spans[0] instanceof TextAppearanceSpan); - } - - @Test - public void testTextContainingLinksAreFocusable() { - Annotation testLink = new Annotation("link", "value"); - SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); - spannableStringBuilder.setSpan(testLink, 0, 3, 0); - - RichTextView view = new RichTextView(application); - view.setText(spannableStringBuilder); - - assertTrue("TextView should be focusable since it contains spans", view.isFocusable()); - } - - - @SuppressLint("SetTextI18n") // It's OK. This is just a test. - @Test - public void testTextContainingNoLinksAreNotFocusable() { - RichTextView textView = new RichTextView(application); - textView.setText("Thou shall not be focusable!"); - - assertFalse("TextView should not be focusable since it does not contain any span", - textView.isFocusable()); - } - - - // Based on the text contents of the text view, the "focusable" property of the element - // should also be automatically changed. - @SuppressLint("SetTextI18n") // It's OK. This is just a test. - @Test - public void testRichTextViewFocusChangesWithTextChange() { - RichTextView textView = new RichTextView(application); - textView.setText("Thou shall not be focusable!"); - - assertFalse(textView.isFocusable()); - assertFalse(textView.isFocusableInTouchMode()); - - SpannableStringBuilder spannableStringBuilder = - new SpannableStringBuilder("I am focusable"); - spannableStringBuilder.setSpan(new Annotation("link", "focus:on_me"), 0, 1, 0); - textView.setText(spannableStringBuilder); - assertTrue(textView.isFocusable()); - if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { - assertTrue(textView.isFocusableInTouchMode()); - assertFalse(textView.getRevealOnFocusHint()); - } else { - assertFalse(textView.isFocusableInTouchMode()); - } - } - - public static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { - - public TestContext(Context base) { - super(base); - } - - @Override - public void onClick(LinkSpan span) { - // Ignore. Can be verified using Mockito - } + @Override + public void onClick(LinkSpan span) { + if (delegate != null) { + delegate.onClick(span); + } } + } } diff --git a/navigationbar/Android.bp b/navigationbar/Android.bp deleted file mode 100644 index 3868fe1..0000000 --- a/navigationbar/Android.bp +++ /dev/null @@ -1,7 +0,0 @@ -android_library { - name: "setup-wizard-navbar", - - sdk_version: "current", - resource_dirs: ["res"], - srcs: ["src/**/*.java"], -} diff --git a/navigationbar/AndroidManifest.xml b/navigationbar/AndroidManifest.xml deleted file mode 100644 index f1029cf..0000000 --- a/navigationbar/AndroidManifest.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.setupwizard.navigationbar"> -</manifest> diff --git a/navigationbar/common.mk b/navigationbar/common.mk deleted file mode 100644 index bacc931..0000000 --- a/navigationbar/common.mk +++ /dev/null @@ -1,17 +0,0 @@ -# -# Include this make file to build your application against this module. -# -# Make sure to include it after you've set all your desired LOCAL variables. -# Note that you must explicitly set your LOCAL_RESOURCE_DIR before including this file. -# -# For example: -# -# LOCAL_RESOURCE_DIR := \ -# $(LOCAL_PATH)/res -# -# include frameworks/opt/setupwizard/navigationbar/common.mk -# - -LOCAL_RESOURCE_DIR += $(call my-dir)/res -LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages com.android.setupwizard.navigationbar -LOCAL_STATIC_JAVA_LIBRARIES += setup-wizard-navbar diff --git a/navigationbar/project.properties b/navigationbar/project.properties deleted file mode 100644 index 823f52e..0000000 --- a/navigationbar/project.properties +++ /dev/null @@ -1 +0,0 @@ -android.library=true diff --git a/navigationbar/res/drawable/setup_wizard_navbar_btn_bg.xml b/navigationbar/res/drawable/setup_wizard_navbar_btn_bg.xml deleted file mode 100644 index 0fc46be..0000000 --- a/navigationbar/res/drawable/setup_wizard_navbar_btn_bg.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?android:attr/colorControlHighlight"> -</ripple> diff --git a/navigationbar/res/layout/setup_wizard_navbar_layout.xml b/navigationbar/res/layout/setup_wizard_navbar_layout.xml deleted file mode 100644 index 6bdb02d..0000000 --- a/navigationbar/res/layout/setup_wizard_navbar_layout.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - style="@style/setup_wizard_navbar_style" > - - <view - class="com.android.setupwizard.navigationbar.SetupWizardNavBar$NavButton" - style="@style/setup_wizard_navbar_button_style" - android:id="@+id/setup_wizard_navbar_back" - android:contentDescription="@string/setup_wizard_back_button_label" - android:drawableStart="@drawable/setup_wizard_navbar_ic_back" /> - - <View - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:visibility="invisible" /> - - <view - class="com.android.setupwizard.navigationbar.SetupWizardNavBar$NavButton" - style="@style/setup_wizard_navbar_button_style" - android:text="@string/setup_wizard_next_button_label" - android:gravity="end|center_vertical" - android:drawableEnd="@drawable/setup_wizard_navbar_ic_next" - android:id="@+id/setup_wizard_navbar_next" /> - -</LinearLayout>
\ No newline at end of file diff --git a/navigationbar/res/values-af/strings.xml b/navigationbar/res/values-af/strings.xml deleted file mode 100644 index 4f7af72..0000000 --- a/navigationbar/res/values-af/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Volgende"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Terug"</string> -</resources> diff --git a/navigationbar/res/values-am/strings.xml b/navigationbar/res/values-am/strings.xml deleted file mode 100644 index d7c3353..0000000 --- a/navigationbar/res/values-am/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"ቀጣይ"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"ተመለስ"</string> -</resources> diff --git a/navigationbar/res/values-ar/strings.xml b/navigationbar/res/values-ar/strings.xml deleted file mode 100644 index b84a0a7..0000000 --- a/navigationbar/res/values-ar/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"التالي"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"رجوع"</string> -</resources> diff --git a/navigationbar/res/values-az/strings.xml b/navigationbar/res/values-az/strings.xml deleted file mode 100644 index 99f211c..0000000 --- a/navigationbar/res/values-az/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Növbəti"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Geri"</string> -</resources> diff --git a/navigationbar/res/values-b+sr+Latn/strings.xml b/navigationbar/res/values-b+sr+Latn/strings.xml deleted file mode 100644 index 4dbd175..0000000 --- a/navigationbar/res/values-b+sr+Latn/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Dalje"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Nazad"</string> -</resources> diff --git a/navigationbar/res/values-be/strings.xml b/navigationbar/res/values-be/strings.xml deleted file mode 100644 index 3612fce..0000000 --- a/navigationbar/res/values-be/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Далей"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Назад"</string> -</resources> diff --git a/navigationbar/res/values-bg/strings.xml b/navigationbar/res/values-bg/strings.xml deleted file mode 100644 index e28abf9..0000000 --- a/navigationbar/res/values-bg/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Напред"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Назад"</string> -</resources> diff --git a/navigationbar/res/values-bn/strings.xml b/navigationbar/res/values-bn/strings.xml deleted file mode 100644 index b7db1f2..0000000 --- a/navigationbar/res/values-bn/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"পরবর্তী"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"পিছনে"</string> -</resources> diff --git a/navigationbar/res/values-bs/strings.xml b/navigationbar/res/values-bs/strings.xml deleted file mode 100644 index 1475017..0000000 --- a/navigationbar/res/values-bs/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Naprijed"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Nazad"</string> -</resources> diff --git a/navigationbar/res/values-ca/strings.xml b/navigationbar/res/values-ca/strings.xml deleted file mode 100644 index 697af97..0000000 --- a/navigationbar/res/values-ca/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Següent"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Enrere"</string> -</resources> diff --git a/navigationbar/res/values-cs/strings.xml b/navigationbar/res/values-cs/strings.xml deleted file mode 100644 index 2f3938a..0000000 --- a/navigationbar/res/values-cs/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Další"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Zpět"</string> -</resources> diff --git a/navigationbar/res/values-da/strings.xml b/navigationbar/res/values-da/strings.xml deleted file mode 100644 index 487c575..0000000 --- a/navigationbar/res/values-da/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Næste"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Tilbage"</string> -</resources> diff --git a/navigationbar/res/values-de/strings.xml b/navigationbar/res/values-de/strings.xml deleted file mode 100644 index e1e009f..0000000 --- a/navigationbar/res/values-de/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Weiter"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Zurück"</string> -</resources> diff --git a/navigationbar/res/values-el/strings.xml b/navigationbar/res/values-el/strings.xml deleted file mode 100644 index 77aa32b..0000000 --- a/navigationbar/res/values-el/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Επόμενο"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Πίσω"</string> -</resources> diff --git a/navigationbar/res/values-en-rAU/strings.xml b/navigationbar/res/values-en-rAU/strings.xml deleted file mode 100644 index b06dc86..0000000 --- a/navigationbar/res/values-en-rAU/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Next"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Back"</string> -</resources> diff --git a/navigationbar/res/values-en-rCA/strings.xml b/navigationbar/res/values-en-rCA/strings.xml deleted file mode 100644 index b06dc86..0000000 --- a/navigationbar/res/values-en-rCA/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Next"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Back"</string> -</resources> diff --git a/navigationbar/res/values-en-rGB/strings.xml b/navigationbar/res/values-en-rGB/strings.xml deleted file mode 100644 index b06dc86..0000000 --- a/navigationbar/res/values-en-rGB/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Next"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Back"</string> -</resources> diff --git a/navigationbar/res/values-en-rIN/strings.xml b/navigationbar/res/values-en-rIN/strings.xml deleted file mode 100644 index b06dc86..0000000 --- a/navigationbar/res/values-en-rIN/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Next"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Back"</string> -</resources> diff --git a/navigationbar/res/values-en-rXC/strings.xml b/navigationbar/res/values-en-rXC/strings.xml deleted file mode 100644 index 8107182..0000000 --- a/navigationbar/res/values-en-rXC/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Next"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Back"</string> -</resources> diff --git a/navigationbar/res/values-es-rUS/strings.xml b/navigationbar/res/values-es-rUS/strings.xml deleted file mode 100644 index 3a23344..0000000 --- a/navigationbar/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Siguiente"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Atrás"</string> -</resources> diff --git a/navigationbar/res/values-es/strings.xml b/navigationbar/res/values-es/strings.xml deleted file mode 100644 index 3a23344..0000000 --- a/navigationbar/res/values-es/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Siguiente"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Atrás"</string> -</resources> diff --git a/navigationbar/res/values-et/strings.xml b/navigationbar/res/values-et/strings.xml deleted file mode 100644 index 7413fec..0000000 --- a/navigationbar/res/values-et/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Järgmine"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Tagasi"</string> -</resources> diff --git a/navigationbar/res/values-eu/strings.xml b/navigationbar/res/values-eu/strings.xml deleted file mode 100644 index c108751..0000000 --- a/navigationbar/res/values-eu/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Hurrengoa"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Aurrekoa"</string> -</resources> diff --git a/navigationbar/res/values-fa/strings.xml b/navigationbar/res/values-fa/strings.xml deleted file mode 100644 index 88c2a83..0000000 --- a/navigationbar/res/values-fa/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"بعدی"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"برگشت"</string> -</resources> diff --git a/navigationbar/res/values-fi/strings.xml b/navigationbar/res/values-fi/strings.xml deleted file mode 100644 index 1bf32f2..0000000 --- a/navigationbar/res/values-fi/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Seuraava"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Edellinen"</string> -</resources> diff --git a/navigationbar/res/values-fr-rCA/strings.xml b/navigationbar/res/values-fr-rCA/strings.xml deleted file mode 100644 index cea38c0..0000000 --- a/navigationbar/res/values-fr-rCA/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Suivant"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Précédent"</string> -</resources> diff --git a/navigationbar/res/values-fr/strings.xml b/navigationbar/res/values-fr/strings.xml deleted file mode 100644 index cea38c0..0000000 --- a/navigationbar/res/values-fr/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Suivant"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Précédent"</string> -</resources> diff --git a/navigationbar/res/values-gl/strings.xml b/navigationbar/res/values-gl/strings.xml deleted file mode 100644 index 52e9248..0000000 --- a/navigationbar/res/values-gl/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Seguinte"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Atrás"</string> -</resources> diff --git a/navigationbar/res/values-gu/strings.xml b/navigationbar/res/values-gu/strings.xml deleted file mode 100644 index aa3efe8..0000000 --- a/navigationbar/res/values-gu/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"આગલું"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"પાછળ"</string> -</resources> diff --git a/navigationbar/res/values-hi/strings.xml b/navigationbar/res/values-hi/strings.xml deleted file mode 100644 index f8e9f9d..0000000 --- a/navigationbar/res/values-hi/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"आगे बढ़ें"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"पीछे"</string> -</resources> diff --git a/navigationbar/res/values-hr/strings.xml b/navigationbar/res/values-hr/strings.xml deleted file mode 100644 index 071e451..0000000 --- a/navigationbar/res/values-hr/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Dalje"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Natrag"</string> -</resources> diff --git a/navigationbar/res/values-hu/strings.xml b/navigationbar/res/values-hu/strings.xml deleted file mode 100644 index ed8349d..0000000 --- a/navigationbar/res/values-hu/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Következő"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Vissza"</string> -</resources> diff --git a/navigationbar/res/values-hy/strings.xml b/navigationbar/res/values-hy/strings.xml deleted file mode 100644 index 4dd2fd5..0000000 --- a/navigationbar/res/values-hy/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Հաջորդը"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Հետ"</string> -</resources> diff --git a/navigationbar/res/values-in/strings.xml b/navigationbar/res/values-in/strings.xml deleted file mode 100644 index bc6bc1e..0000000 --- a/navigationbar/res/values-in/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Berikutnya"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Kembali"</string> -</resources> diff --git a/navigationbar/res/values-is/strings.xml b/navigationbar/res/values-is/strings.xml deleted file mode 100644 index 22d32b7..0000000 --- a/navigationbar/res/values-is/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Áfram"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Til baka"</string> -</resources> diff --git a/navigationbar/res/values-it/strings.xml b/navigationbar/res/values-it/strings.xml deleted file mode 100644 index 0b61a83..0000000 --- a/navigationbar/res/values-it/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Avanti"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Indietro"</string> -</resources> diff --git a/navigationbar/res/values-iw/strings.xml b/navigationbar/res/values-iw/strings.xml deleted file mode 100644 index 93f956e..0000000 --- a/navigationbar/res/values-iw/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"הבא"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"הקודם"</string> -</resources> diff --git a/navigationbar/res/values-ja/strings.xml b/navigationbar/res/values-ja/strings.xml deleted file mode 100644 index 430a0cf..0000000 --- a/navigationbar/res/values-ja/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"次へ"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"戻る"</string> -</resources> diff --git a/navigationbar/res/values-ka/strings.xml b/navigationbar/res/values-ka/strings.xml deleted file mode 100644 index 8263905..0000000 --- a/navigationbar/res/values-ka/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"შემდეგი"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"უკან"</string> -</resources> diff --git a/navigationbar/res/values-kk/strings.xml b/navigationbar/res/values-kk/strings.xml deleted file mode 100644 index 358dadb..0000000 --- a/navigationbar/res/values-kk/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Келесі"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Артқа"</string> -</resources> diff --git a/navigationbar/res/values-km/strings.xml b/navigationbar/res/values-km/strings.xml deleted file mode 100644 index 480e4d4..0000000 --- a/navigationbar/res/values-km/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"បន្ទាប់"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"ថយក្រោយ"</string> -</resources> diff --git a/navigationbar/res/values-kn/strings.xml b/navigationbar/res/values-kn/strings.xml deleted file mode 100644 index 333e1a2..0000000 --- a/navigationbar/res/values-kn/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"ಮುಂದೆ"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"ಹಿಂದೆ"</string> -</resources> diff --git a/navigationbar/res/values-ko/strings.xml b/navigationbar/res/values-ko/strings.xml deleted file mode 100644 index 66510cc..0000000 --- a/navigationbar/res/values-ko/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"다음"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"뒤로"</string> -</resources> diff --git a/navigationbar/res/values-ky/strings.xml b/navigationbar/res/values-ky/strings.xml deleted file mode 100644 index ee8e164..0000000 --- a/navigationbar/res/values-ky/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Кийинки"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Артка"</string> -</resources> diff --git a/navigationbar/res/values-lo/strings.xml b/navigationbar/res/values-lo/strings.xml deleted file mode 100644 index fc2ce83..0000000 --- a/navigationbar/res/values-lo/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"ຕໍ່ໄປ"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"ກັບຄືນ"</string> -</resources> diff --git a/navigationbar/res/values-lt/strings.xml b/navigationbar/res/values-lt/strings.xml deleted file mode 100644 index 183404c..0000000 --- a/navigationbar/res/values-lt/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Kitas"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Atgal"</string> -</resources> diff --git a/navigationbar/res/values-lv/strings.xml b/navigationbar/res/values-lv/strings.xml deleted file mode 100644 index 2aa72ee..0000000 --- a/navigationbar/res/values-lv/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Tālāk"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Atpakaļ"</string> -</resources> diff --git a/navigationbar/res/values-mk/strings.xml b/navigationbar/res/values-mk/strings.xml deleted file mode 100644 index 52c8324..0000000 --- a/navigationbar/res/values-mk/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Следно"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Назад"</string> -</resources> diff --git a/navigationbar/res/values-ml/strings.xml b/navigationbar/res/values-ml/strings.xml deleted file mode 100644 index a1373bc..0000000 --- a/navigationbar/res/values-ml/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"അടുത്തത്"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"മടങ്ങുക"</string> -</resources> diff --git a/navigationbar/res/values-mn/strings.xml b/navigationbar/res/values-mn/strings.xml deleted file mode 100644 index e8099ce..0000000 --- a/navigationbar/res/values-mn/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Дараах"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Буцах"</string> -</resources> diff --git a/navigationbar/res/values-mr/strings.xml b/navigationbar/res/values-mr/strings.xml deleted file mode 100644 index 3668c64..0000000 --- a/navigationbar/res/values-mr/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"पुढील"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"मागे"</string> -</resources> diff --git a/navigationbar/res/values-ms/strings.xml b/navigationbar/res/values-ms/strings.xml deleted file mode 100644 index d801b6d..0000000 --- a/navigationbar/res/values-ms/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Seterusnya"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Kembali"</string> -</resources> diff --git a/navigationbar/res/values-my/strings.xml b/navigationbar/res/values-my/strings.xml deleted file mode 100644 index 8c5944f..0000000 --- a/navigationbar/res/values-my/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"ရှေ့သို့"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"နောက်သို့"</string> -</resources> diff --git a/navigationbar/res/values-nb/strings.xml b/navigationbar/res/values-nb/strings.xml deleted file mode 100644 index c1b7b0c..0000000 --- a/navigationbar/res/values-nb/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Neste"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Tilbake"</string> -</resources> diff --git a/navigationbar/res/values-ne/strings.xml b/navigationbar/res/values-ne/strings.xml deleted file mode 100644 index d806032..0000000 --- a/navigationbar/res/values-ne/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"अर्को"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"पछाडि"</string> -</resources> diff --git a/navigationbar/res/values-nl/strings.xml b/navigationbar/res/values-nl/strings.xml deleted file mode 100644 index 4f7af72..0000000 --- a/navigationbar/res/values-nl/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Volgende"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Terug"</string> -</resources> diff --git a/navigationbar/res/values-pa/strings.xml b/navigationbar/res/values-pa/strings.xml deleted file mode 100644 index f1a53bc..0000000 --- a/navigationbar/res/values-pa/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"ਅੱਗੇ"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"ਪਿੱਛੇ"</string> -</resources> diff --git a/navigationbar/res/values-pl/strings.xml b/navigationbar/res/values-pl/strings.xml deleted file mode 100644 index f47a9a7..0000000 --- a/navigationbar/res/values-pl/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Dalej"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Wstecz"</string> -</resources> diff --git a/navigationbar/res/values-pt-rBR/strings.xml b/navigationbar/res/values-pt-rBR/strings.xml deleted file mode 100644 index 181fafe..0000000 --- a/navigationbar/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Próxima"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Voltar"</string> -</resources> diff --git a/navigationbar/res/values-pt-rPT/strings.xml b/navigationbar/res/values-pt-rPT/strings.xml deleted file mode 100644 index 6be511f..0000000 --- a/navigationbar/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Seguinte"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Anterior"</string> -</resources> diff --git a/navigationbar/res/values-pt/strings.xml b/navigationbar/res/values-pt/strings.xml deleted file mode 100644 index 181fafe..0000000 --- a/navigationbar/res/values-pt/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Próxima"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Voltar"</string> -</resources> diff --git a/navigationbar/res/values-ro/strings.xml b/navigationbar/res/values-ro/strings.xml deleted file mode 100644 index 1c75ba8..0000000 --- a/navigationbar/res/values-ro/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Înainte"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Înapoi"</string> -</resources> diff --git a/navigationbar/res/values-ru/strings.xml b/navigationbar/res/values-ru/strings.xml deleted file mode 100644 index 454613a..0000000 --- a/navigationbar/res/values-ru/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Далее"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Назад"</string> -</resources> diff --git a/navigationbar/res/values-si/strings.xml b/navigationbar/res/values-si/strings.xml deleted file mode 100644 index 892a100..0000000 --- a/navigationbar/res/values-si/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"මීළඟ"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"ආපසු"</string> -</resources> diff --git a/navigationbar/res/values-sk/strings.xml b/navigationbar/res/values-sk/strings.xml deleted file mode 100644 index 6d8e36d..0000000 --- a/navigationbar/res/values-sk/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Ďalej"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Späť"</string> -</resources> diff --git a/navigationbar/res/values-sl/strings.xml b/navigationbar/res/values-sl/strings.xml deleted file mode 100644 index ebc916a..0000000 --- a/navigationbar/res/values-sl/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Naprej"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Nazaj"</string> -</resources> diff --git a/navigationbar/res/values-sq/strings.xml b/navigationbar/res/values-sq/strings.xml deleted file mode 100644 index 7f4f95d..0000000 --- a/navigationbar/res/values-sq/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Përpara"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Prapa"</string> -</resources> diff --git a/navigationbar/res/values-sr/strings.xml b/navigationbar/res/values-sr/strings.xml deleted file mode 100644 index 531d772..0000000 --- a/navigationbar/res/values-sr/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Даље"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Назад"</string> -</resources> diff --git a/navigationbar/res/values-sv/strings.xml b/navigationbar/res/values-sv/strings.xml deleted file mode 100644 index db88fdc..0000000 --- a/navigationbar/res/values-sv/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Nästa"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Föregående"</string> -</resources> diff --git a/navigationbar/res/values-sw/strings.xml b/navigationbar/res/values-sw/strings.xml deleted file mode 100644 index c02d768..0000000 --- a/navigationbar/res/values-sw/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Endelea"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Rudi nyuma"</string> -</resources> diff --git a/navigationbar/res/values-ta/strings.xml b/navigationbar/res/values-ta/strings.xml deleted file mode 100644 index 3e3ef59..0000000 --- a/navigationbar/res/values-ta/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"அடுத்து"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"பின்செல்"</string> -</resources> diff --git a/navigationbar/res/values-te/strings.xml b/navigationbar/res/values-te/strings.xml deleted file mode 100644 index fdf5c75..0000000 --- a/navigationbar/res/values-te/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"తర్వాత"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"వెనుకకు"</string> -</resources> diff --git a/navigationbar/res/values-th/strings.xml b/navigationbar/res/values-th/strings.xml deleted file mode 100644 index d926d6d..0000000 --- a/navigationbar/res/values-th/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"ถัดไป"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"กลับ"</string> -</resources> diff --git a/navigationbar/res/values-tl/strings.xml b/navigationbar/res/values-tl/strings.xml deleted file mode 100644 index 68b8a43..0000000 --- a/navigationbar/res/values-tl/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Susunod"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Bumalik"</string> -</resources> diff --git a/navigationbar/res/values-tr/strings.xml b/navigationbar/res/values-tr/strings.xml deleted file mode 100644 index 5e667b6..0000000 --- a/navigationbar/res/values-tr/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"İleri"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Geri"</string> -</resources> diff --git a/navigationbar/res/values-uk/strings.xml b/navigationbar/res/values-uk/strings.xml deleted file mode 100644 index 9133704..0000000 --- a/navigationbar/res/values-uk/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Далі"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Назад"</string> -</resources> diff --git a/navigationbar/res/values-ur/strings.xml b/navigationbar/res/values-ur/strings.xml deleted file mode 100644 index 00825ab..0000000 --- a/navigationbar/res/values-ur/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"آگے"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"پیچھے"</string> -</resources> diff --git a/navigationbar/res/values-uz/strings.xml b/navigationbar/res/values-uz/strings.xml deleted file mode 100644 index 0f9e086..0000000 --- a/navigationbar/res/values-uz/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Keyingisi"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Orqaga"</string> -</resources> diff --git a/navigationbar/res/values-vi/strings.xml b/navigationbar/res/values-vi/strings.xml deleted file mode 100644 index 3e822ec..0000000 --- a/navigationbar/res/values-vi/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Tiếp theo"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Quay lại"</string> -</resources> diff --git a/navigationbar/res/values-zh-rCN/strings.xml b/navigationbar/res/values-zh-rCN/strings.xml deleted file mode 100644 index e7e2394..0000000 --- a/navigationbar/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"下一步"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"返回"</string> -</resources> diff --git a/navigationbar/res/values-zh-rHK/strings.xml b/navigationbar/res/values-zh-rHK/strings.xml deleted file mode 100644 index e7e2394..0000000 --- a/navigationbar/res/values-zh-rHK/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"下一步"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"返回"</string> -</resources> diff --git a/navigationbar/res/values-zh-rTW/strings.xml b/navigationbar/res/values-zh-rTW/strings.xml deleted file mode 100644 index 56ae800..0000000 --- a/navigationbar/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"繼續"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"返回"</string> -</resources> diff --git a/navigationbar/res/values-zu/strings.xml b/navigationbar/res/values-zu/strings.xml deleted file mode 100644 index b5b0eee..0000000 --- a/navigationbar/res/values-zu/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Okulandelayo"</string> - <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Phindela emuva"</string> -</resources> diff --git a/navigationbar/res/values/colors.xml b/navigationbar/res/values/colors.xml deleted file mode 100644 index 1cdf964..0000000 --- a/navigationbar/res/values/colors.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <color name="setup_wizard_navbar_bg_dark">#ff21272b</color> - <color name="setup_wizard_navbar_bg_light">#ffe4e7e9</color> - <color name="setup_wizard_navbar_text_dark">#deffffff</color> - <color name="setup_wizard_navbar_text_light">#de000000</color> -</resources> diff --git a/navigationbar/res/values/dimens.xml b/navigationbar/res/values/dimens.xml deleted file mode 100644 index df67d9f..0000000 --- a/navigationbar/res/values/dimens.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <dimen name="setup_wizard_navbar_button_drawable_padding">6dp</dimen> - <dimen name="setup_wizard_navbar_button_padding_sides">10dp</dimen> - <dimen name="setup_wizard_navbar_height">56dp</dimen> - <dimen name="setup_wizard_navbar_ic_intrinsic_size">24dp</dimen> - <dimen name="setup_wizard_navbar_padding_sides">24dp</dimen> - <dimen name="setup_wizard_navbar_text_size">16sp</dimen> -</resources> diff --git a/navigationbar/res/values/strings.xml b/navigationbar/res/values/strings.xml deleted file mode 100644 index 7f01c4e..0000000 --- a/navigationbar/res/values/strings.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <!-- Button for going to the next screen or step [CHAR LIMIT=40] --> - <string name="setup_wizard_next_button_label">Next</string> - - <!-- Button for going to the previous screen or step [CHAR LIMIT=40] --> - <string name="setup_wizard_back_button_label">Back</string> - -</resources> diff --git a/navigationbar/res/values/styles.xml b/navigationbar/res/values/styles.xml deleted file mode 100644 index 9aa9a85..0000000 --- a/navigationbar/res/values/styles.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - - <attr name="setup_wizard_navbar_bg_color" format="color" /> - <attr name="setup_wizard_navbar_text_color" format="color" /> - <attr name="setup_wizard_navbar_theme" format="reference" /> - - <style name="setup_wizard_navbar_style"> - <item name="android:orientation">horizontal</item> - <item name="android:layout_height">@dimen/setup_wizard_navbar_height</item> - <item name="android:layout_width">match_parent</item> - <item name="android:layout_alignParentBottom">true</item> - <item name="android:background">?attr/setup_wizard_navbar_bg_color</item> - <item name="android:paddingStart">@dimen/setup_wizard_navbar_padding_sides</item> - <item name="android:paddingEnd">@dimen/setup_wizard_navbar_padding_sides</item> - </style> - - <style name="setup_wizard_navbar_theme_dark"> - <item name="setup_wizard_navbar_bg_color">@color/setup_wizard_navbar_bg_dark</item> - <item name="setup_wizard_navbar_text_color">@color/setup_wizard_navbar_text_dark</item> - </style> - - <style name="setup_wizard_navbar_theme_light"> - <item name="setup_wizard_navbar_bg_color">@color/setup_wizard_navbar_bg_light</item> - <item name="setup_wizard_navbar_text_color">@color/setup_wizard_navbar_text_light</item> - </style> - - <style name="setup_wizard_navbar_button_style" parent="@android:style/Widget.Material.Button.Borderless"> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">match_parent</item> - <item name="android:layout_weight">0</item> - <item name="android:background">@drawable/setup_wizard_navbar_btn_bg</item> - <item name="android:drawablePadding">@dimen/setup_wizard_navbar_button_drawable_padding</item> - <item name="android:fontFamily">sans-serif</item> - <item name="android:minWidth">0dp</item> - <item name="android:paddingEnd">@dimen/setup_wizard_navbar_button_padding_sides</item> - <item name="android:paddingStart">@dimen/setup_wizard_navbar_button_padding_sides</item> - <item name="android:textAllCaps">true</item> - <item name="android:textColor">?attr/setup_wizard_navbar_text_color</item> - <item name="android:textSize">@dimen/setup_wizard_navbar_text_size</item> - </style> - -</resources> diff --git a/navigationbar/src/com/android/setupwizard/navigationbar/SetupWizardNavBar.java b/navigationbar/src/com/android/setupwizard/navigationbar/SetupWizardNavBar.java deleted file mode 100644 index e34ad6c..0000000 --- a/navigationbar/src/com/android/setupwizard/navigationbar/SetupWizardNavBar.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.setupwizard.navigationbar; - -import android.app.Activity; -import android.app.Fragment; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.os.Bundle; -import android.util.AttributeSet; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnPreDrawListener; -import android.widget.Button; - -/** - * Fragment class for controlling the custom navigation bar shown during setup wizard. Apps in the - * Android tree can use this by including the common.mk makefile. Apps outside of the tree can - * create a library project out of the source. - */ -public class SetupWizardNavBar extends Fragment implements OnPreDrawListener, OnClickListener { - private static final String TAG = "SetupWizardNavBar"; - private static final int IMMERSIVE_FLAGS = - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - private int mSystemUiFlags = IMMERSIVE_FLAGS | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - - private ViewGroup mNavigationBarView; - private Button mNextButton; - private Button mBackButton; - private NavigationBarListener mCallback; - - public interface NavigationBarListener { - public void onNavigationBarCreated(SetupWizardNavBar bar); - public void onNavigateBack(); - public void onNavigateNext(); - } - - public SetupWizardNavBar() { - // no-arg constructor for fragments - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mCallback = (NavigationBarListener) activity; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Context context = new ContextThemeWrapper(getActivity(), getNavbarTheme()); - inflater = LayoutInflater.from(context); - mNavigationBarView = (ViewGroup) inflater.inflate(R.layout.setup_wizard_navbar_layout, - container, false); - mNextButton = (Button) mNavigationBarView.findViewById(R.id.setup_wizard_navbar_next); - mBackButton = (Button) mNavigationBarView.findViewById(R.id.setup_wizard_navbar_back); - mNextButton.setOnClickListener(this); - mBackButton.setOnClickListener(this); - return mNavigationBarView; - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mCallback.onNavigationBarCreated(this); - mNavigationBarView.setSystemUiVisibility(mSystemUiFlags); - - // Set the UI flags before draw because the visibility might change in unexpected / - // undetectable times, like transitioning from a finishing activity that had a keyboard - ViewTreeObserver viewTreeObserver = mNavigationBarView.getViewTreeObserver(); - viewTreeObserver.addOnPreDrawListener(this); - } - - @Override - public boolean onPreDraw() { - // View.setSystemUiVisibility checks if the visibility changes before applying them - // so the performance impact is contained - mNavigationBarView.setSystemUiVisibility(mSystemUiFlags); - return true; - } - - /** - * Sets whether system navigation bar should be hidden. - * @param useImmersiveMode True to activate immersive mode and hide the system navigation bar - */ - public void setUseImmersiveMode(boolean useImmersiveMode) { - // By default, enable layoutHideNavigation if immersive mode is used - setUseImmersiveMode(useImmersiveMode, useImmersiveMode); - } - - public void setUseImmersiveMode(boolean useImmersiveMode, boolean layoutHideNavigation) { - if (useImmersiveMode) { - mSystemUiFlags |= IMMERSIVE_FLAGS; - if (layoutHideNavigation) { - mSystemUiFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - } - } else { - mSystemUiFlags &= ~(IMMERSIVE_FLAGS | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - } - mNavigationBarView.setSystemUiVisibility(mSystemUiFlags); - } - - private int getNavbarTheme() { - // Normally we can automatically guess the theme by comparing the foreground color against - // the background color. But we also allow specifying explicitly using - // setup_wizard_navbar_theme. - TypedArray attributes = getActivity().obtainStyledAttributes( - new int[] { - R.attr.setup_wizard_navbar_theme, - 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.setup_wizard_navbar_theme_dark : - R.style.setup_wizard_navbar_theme_light; - } - attributes.recycle(); - return theme; - } - - @Override - public void onClick(View v) { - if (v == mBackButton) { - mCallback.onNavigateBack(); - } else if (v == mNextButton) { - mCallback.onNavigateNext(); - } - } - - public Button getBackButton() { - return mBackButton; - } - - public Button getNextButton() { - return mNextButton; - } - - public static class NavButton extends Button { - - public NavButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public NavButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public NavButton(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public NavButton(Context context) { - super(context); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - // The color of the button is #de000000 / #deffffff when enabled. When disabled, apply - // additional 23% alpha, so the overall opacity is 20%. - setAlpha(enabled ? 1.0f : 0.23f); - } - } - -} diff --git a/tools/build_for_build_server.sh b/tools/build_for_build_server.sh index 7a8c942..1ed4ea7 100755 --- a/tools/build_for_build_server.sh +++ b/tools/build_for_build_server.sh @@ -6,4 +6,4 @@ export TARGET_BUILD_DENSITY="alldpi" export TARGET_BUILD_TYPE="release" export TARGET_BUILD_APPS="setup-wizard-lib" -./gradlew buildProjectFull test coverage +./gradlew buildProjectFull test coverage --info diff --git a/tools/checkstyle/checkstyle.xml b/tools/checkstyle/checkstyle.xml deleted file mode 100644 index 0dbccae..0000000 --- a/tools/checkstyle/checkstyle.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd" [ - <!ENTITY defaultCopyrightCheck SYSTEM "../../../../../prebuilts/checkstyle/default-copyright-check.xml"> - <!ENTITY defaultJavadocChecks SYSTEM "../../../../../prebuilts/checkstyle/default-javadoc-checks.xml"> - <!ENTITY defaultTreewalkerChecks SYSTEM "../../../../../prebuilts/checkstyle/default-treewalker-checks.xml"> - <!ENTITY defaultModuleChecks SYSTEM "../../../../../prebuilts/checkstyle/default-module-checks.xml"> -]> - -<module name="Checker"> - &defaultModuleChecks; - &defaultCopyrightCheck; - <module name="TreeWalker"> - &defaultJavadocChecks; - &defaultTreewalkerChecks; - </module> - - <module name="SuppressionFilter"> - <property name="file" value="tools/checkstyle/checkstyle_suppression.xml" /> - </module> -</module> diff --git a/tools/checkstyle/checkstyle_suppression.xml b/tools/checkstyle/checkstyle_suppression.xml deleted file mode 100644 index 6bf7b21..0000000 --- a/tools/checkstyle/checkstyle_suppression.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN" "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> -<suppressions> - - <!-- Note: Checkstyle puts the absolute path of files through the suppress filter, so the - patterns below will match sub-directories. Notably, for overlays where the path is - something like overlay/frameworks/opt/setupwizard will match the regex filter - "frameworks/opt/setupwizard". This is probably OK for most cases since they are overlay - of the original app and should have the same coding style. --> - - <!-- Robolectric uses magic method names like `__constructor__` --> - <suppress files="/robotest/" checks="MethodName" /> - -</suppressions> |