diff options
author | Maurice Lam <yukl@google.com> | 2017-03-28 12:48:40 -0700 |
---|---|---|
committer | Maurice Lam <yukl@google.com> | 2017-03-28 14:23:21 -0700 |
commit | 83862bb59558fc044de9aa0d6e9407be53af8b81 (patch) | |
tree | 032855cc188420699c048478a6a24c44731a3151 /library/gingerbread | |
parent | 9955331ed7bda114488b1a4701456ec478ff63bf (diff) | |
download | setupwizard-83862bb59558fc044de9aa0d6e9407be53af8b81.tar.gz |
Rename SuwLib directories
Rename eclair-mr1 to gingerbread to reflect the min SDK version
change, and full-support to recyclerview to better reflect what's
inside the directory
Also added comments and applied style fixes to keep checkstyle happy.
Test: Existing tests pass
Change-Id: I20332f718f2aae04092d5e45de944b1efce1a596
Diffstat (limited to 'library/gingerbread')
44 files changed, 2613 insertions, 0 deletions
diff --git a/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_down_arrow.png b/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_down_arrow.png Binary files differnew file mode 100644 index 0000000..3d7f83f --- /dev/null +++ b/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_down_arrow.png diff --git a/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_left_arrow.png b/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_left_arrow.png Binary files differnew file mode 100644 index 0000000..decccac --- /dev/null +++ b/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_left_arrow.png diff --git a/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_right_arrow.png b/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_right_arrow.png Binary files differnew file mode 100644 index 0000000..5ba56b7 --- /dev/null +++ b/library/gingerbread/res/drawable-hdpi/suw_navbar_ic_right_arrow.png diff --git a/library/gingerbread/res/drawable-ldrtl/suw_navbar_ic_back.xml b/library/gingerbread/res/drawable-ldrtl/suw_navbar_ic_back.xml new file mode 100644 index 0000000..e164e8a --- /dev/null +++ b/library/gingerbread/res/drawable-ldrtl/suw_navbar_ic_back.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/suw_navbar_ic_right_arrow" /> diff --git a/library/gingerbread/res/drawable-ldrtl/suw_navbar_ic_next.xml b/library/gingerbread/res/drawable-ldrtl/suw_navbar_ic_next.xml new file mode 100644 index 0000000..8a6e137 --- /dev/null +++ b/library/gingerbread/res/drawable-ldrtl/suw_navbar_ic_next.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/suw_navbar_ic_left_arrow" /> diff --git a/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_down_arrow.png b/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_down_arrow.png Binary files differnew file mode 100644 index 0000000..5b1fa06 --- /dev/null +++ b/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_down_arrow.png diff --git a/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_left_arrow.png b/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_left_arrow.png Binary files differnew file mode 100644 index 0000000..1e2984a --- /dev/null +++ b/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_left_arrow.png diff --git a/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_right_arrow.png b/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_right_arrow.png Binary files differnew file mode 100644 index 0000000..b8365e6 --- /dev/null +++ b/library/gingerbread/res/drawable-mdpi/suw_navbar_ic_right_arrow.png 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 differnew file mode 100644 index 0000000..94016f4 --- /dev/null +++ b/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_down_arrow.png diff --git a/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_left_arrow.png b/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_left_arrow.png Binary files differnew file mode 100644 index 0000000..2d2046c --- /dev/null +++ b/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_left_arrow.png diff --git a/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_right_arrow.png b/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_right_arrow.png Binary files differnew file mode 100644 index 0000000..46abe31 --- /dev/null +++ b/library/gingerbread/res/drawable-xhdpi/suw_navbar_ic_right_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 differnew file mode 100644 index 0000000..17811ae --- /dev/null +++ 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 differnew file mode 100644 index 0000000..97fed92 --- /dev/null +++ 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 differnew file mode 100644 index 0000000..f874955 --- /dev/null +++ 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 differnew file mode 100644 index 0000000..cb6a422 --- /dev/null +++ b/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_down_arrow.png diff --git a/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_left_arrow.png b/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_left_arrow.png Binary files differnew file mode 100644 index 0000000..a0ebbb9 --- /dev/null +++ b/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_left_arrow.png diff --git a/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_right_arrow.png b/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_right_arrow.png Binary files differnew file mode 100644 index 0000000..c98b882 --- /dev/null +++ b/library/gingerbread/res/drawable-xxxhdpi/suw_navbar_ic_right_arrow.png diff --git a/library/gingerbread/res/drawable/suw_card_bg_dark.xml b/library/gingerbread/res/drawable/suw_card_bg_dark.xml new file mode 100644 index 0000000..cd115cf --- /dev/null +++ b/library/gingerbread/res/drawable/suw_card_bg_dark.xml @@ -0,0 +1,28 @@ +<?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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:shape="rectangle"> + + <corners + android:topLeftRadius="@dimen/suw_card_corner_radius" + android:topRightRadius="@dimen/suw_card_corner_radius" /> + + <!-- Ignore PrivateResource: This should be fixed --> + <solid android:color="@color/background_material_dark" tools:ignore="PrivateResource" /> + +</shape> diff --git a/library/gingerbread/res/drawable/suw_card_bg_light.xml b/library/gingerbread/res/drawable/suw_card_bg_light.xml new file mode 100644 index 0000000..cd40dbe --- /dev/null +++ b/library/gingerbread/res/drawable/suw_card_bg_light.xml @@ -0,0 +1,28 @@ +<?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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:shape="rectangle"> + + <corners + android:topLeftRadius="@dimen/suw_card_corner_radius" + android:topRightRadius="@dimen/suw_card_corner_radius" /> + + <!-- Ignore PrivateResource: This should be fixed --> + <solid android:color="@color/background_material_light" tools:ignore="PrivateResource" /> + +</shape> diff --git a/library/gingerbread/res/drawable/suw_ic_expand.xml b/library/gingerbread/res/drawable/suw_ic_expand.xml new file mode 100644 index 0000000..7e79f7d --- /dev/null +++ b/library/gingerbread/res/drawable/suw_ic_expand.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" android:drawable="@drawable/suw_ic_expand_less" /> + <item android:drawable="@drawable/suw_ic_expand_more" /> +</selector> diff --git a/library/gingerbread/res/drawable/suw_ic_expand_less.xml b/library/gingerbread/res/drawable/suw_ic_expand_less.xml new file mode 100644 index 0000000..e639a61 --- /dev/null +++ b/library/gingerbread/res/drawable/suw_ic_expand_less.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="18dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#ff000000" + android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z" /> +</vector> diff --git a/library/gingerbread/res/drawable/suw_ic_expand_more.xml b/library/gingerbread/res/drawable/suw_ic_expand_more.xml new file mode 100644 index 0000000..ed63e21 --- /dev/null +++ b/library/gingerbread/res/drawable/suw_ic_expand_more.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="18dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#ff000000" + android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/> +</vector> diff --git a/library/gingerbread/res/drawable/suw_navbar_btn_bg_dark.xml b/library/gingerbread/res/drawable/suw_navbar_btn_bg_dark.xml new file mode 100644 index 0000000..f4131e5 --- /dev/null +++ b/library/gingerbread/res/drawable/suw_navbar_btn_bg_dark.xml @@ -0,0 +1,30 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> + + <item android:state_pressed="true"> + <!-- Ignore PrivateResource: This should be fixed --> + <color android:color="@color/ripple_material_dark" tools:ignore="PrivateResource" /> + </item> + + <item> + <color android:color="@android:color/transparent" /> + </item> + +</selector> diff --git a/library/gingerbread/res/drawable/suw_navbar_btn_bg_light.xml b/library/gingerbread/res/drawable/suw_navbar_btn_bg_light.xml new file mode 100644 index 0000000..5d3bcfa --- /dev/null +++ b/library/gingerbread/res/drawable/suw_navbar_btn_bg_light.xml @@ -0,0 +1,31 @@ +<?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. +--> + +<selector + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> + + <item android:state_pressed="true"> + <!-- Ignore PrivateResource: This should be fixed --> + <color android:color="@color/ripple_material_light" tools:ignore="PrivateResource" /> + </item> + + <item> + <color android:color="@android:color/transparent" /> + </item> + +</selector> diff --git a/library/gingerbread/res/drawable/suw_navbar_ic_back.xml b/library/gingerbread/res/drawable/suw_navbar_ic_back.xml new file mode 100644 index 0000000..8a6e137 --- /dev/null +++ b/library/gingerbread/res/drawable/suw_navbar_ic_back.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/suw_navbar_ic_left_arrow" /> diff --git a/library/gingerbread/res/drawable/suw_navbar_ic_more.xml b/library/gingerbread/res/drawable/suw_navbar_ic_more.xml new file mode 100644 index 0000000..603e08d --- /dev/null +++ b/library/gingerbread/res/drawable/suw_navbar_ic_more.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/suw_navbar_ic_down_arrow" /> diff --git a/library/gingerbread/res/drawable/suw_navbar_ic_next.xml b/library/gingerbread/res/drawable/suw_navbar_ic_next.xml new file mode 100644 index 0000000..e164e8a --- /dev/null +++ b/library/gingerbread/res/drawable/suw_navbar_ic_next.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/suw_navbar_ic_right_arrow" /> diff --git a/library/gingerbread/res/layout/suw_items_expandable_switch.xml b/library/gingerbread/res/layout/suw_items_expandable_switch.xml new file mode 100644 index 0000000..91e482d --- /dev/null +++ b/library/gingerbread/res/layout/suw_items_expandable_switch.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:colorBackground" + android:descendantFocusability="blocksDescendants" + android:orientation="horizontal" + android:tag="noBackground"> + + <com.android.setupwizardlib.view.CheckableLinearLayout + android:id="@+id/suw_items_expandable_switch_content" + android:layout_width="0dp" + android:layout_height="wrap_content" + style="@style/SuwItemContainer" + android:layout_weight="1" + android:background="?attr/selectableItemBackground" + android:baselineAligned="false" + android:orientation="horizontal" + android:paddingEnd="@dimen/suw_switch_content_padding_end" + android:paddingLeft="?attr/listPreferredItemPaddingLeft" + android:paddingRight="@dimen/suw_switch_content_padding_end" + android:paddingStart="?attr/listPreferredItemPaddingLeft"> + + <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_padding_bottom_extra" + android:layout_weight="1" + android:duplicateParentState="true" + android:orientation="vertical"> + + <com.android.setupwizardlib.view.RichTextView + android:id="@+id/suw_items_title" + style="@style/SuwItemTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:drawablePadding="@dimen/suw_expand_arrow_drawable_padding" + android:drawableEnd="@drawable/suw_ic_expand" + android:drawableRight="@drawable/suw_ic_expand" + android:duplicateParentState="true" + 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:duplicateParentState="true" + android:gravity="start" + android:textAlignment="viewStart" + android:visibility="gone" + tools:ignore="UnusedAttribute" /> + + </LinearLayout> + + </com.android.setupwizardlib.view.CheckableLinearLayout> + + <View + android:id="@+id/suw_items_switch_divider" + android:layout_width="1dp" + android:layout_height="@dimen/suw_switch_divider_height" + android:layout_gravity="center_vertical" + android:background="?android:attr/listDivider" /> + + <android.support.v7.widget.SwitchCompat + android:id="@+id/suw_items_switch" + style="@style/SuwSwitchStyle.Divided" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center_vertical" /> + +</LinearLayout> diff --git a/library/gingerbread/res/layout/suw_items_switch.xml b/library/gingerbread/res/layout/suw_items_switch.xml new file mode 100644 index 0000000..af326b2 --- /dev/null +++ b/library/gingerbread/res/layout/suw_items_switch.xml @@ -0,0 +1,77 @@ +<?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:descendantFocusability="blocksDescendants" + 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: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> + + <android.support.v7.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/layout/suw_progress_bar.xml b/library/gingerbread/res/layout/suw_progress_bar.xml new file mode 100644 index 0000000..35e0faf --- /dev/null +++ b/library/gingerbread/res/layout/suw_progress_bar.xml @@ -0,0 +1,25 @@ +<?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. +--> + +<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/progress_bar" + style="@style/Widget.AppCompat.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/suw_progress_bar_margin_vertical" + android:layout_marginTop="@dimen/suw_progress_bar_margin_vertical" + android:indeterminate="true" /> diff --git a/library/gingerbread/res/values/attrs.xml b/library/gingerbread/res/values/attrs.xml new file mode 100644 index 0000000..2a7b5cd --- /dev/null +++ b/library/gingerbread/res/values/attrs.xml @@ -0,0 +1,29 @@ +<?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. +--> + +<resources> + + <declare-styleable name="SuwSwitchItem"> + <attr name="android:checked" /> + </declare-styleable> + + <declare-styleable name="SuwExpandableSwitchItem"> + <attr name="suwCollapsedSummary" format="string" localization="suggested" /> + <attr name="suwExpandedSummary" format="string" localization="suggested" /> + </declare-styleable> + +</resources> diff --git a/library/gingerbread/res/values/dimens.xml b/library/gingerbread/res/values/dimens.xml new file mode 100644 index 0000000..d40d7de --- /dev/null +++ b/library/gingerbread/res/values/dimens.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> + +<resources> + + <!-- SwitchItem --> + <dimen name="suw_switch_padding_start">16dp</dimen> + <dimen name="suw_switch_padding_end">0dp</dimen> + <dimen name="suw_switch_divider_height">32dp</dimen> + <dimen name="suw_switch_content_padding_end">16dp</dimen> + + <!-- ExpandableSwithItem --> + <dimen name="suw_expand_arrow_drawable_padding">6dp</dimen> + +</resources> diff --git a/library/gingerbread/res/values/styles.xml b/library/gingerbread/res/values/styles.xml new file mode 100644 index 0000000..1ea468d --- /dev/null +++ b/library/gingerbread/res/values/styles.xml @@ -0,0 +1,238 @@ +<?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:tools="http://schemas.android.com/tools"> + + <!-- General styles --> + + <style name="SuwThemeMaterial" parent="Theme.AppCompat.NoActionBar"> + <item name="android:indeterminateTint" tools:ignore="NewApi">@color/suw_progress_bar_color_dark</item> + <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> + <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> + <item name="android:listPreferredItemHeight">@dimen/suw_items_preferred_height</item> + <item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item> + <item name="android:statusBarColor" tools:ignore="NewApi">@android:color/black</item> + <item name="android:textAppearanceListItemSmall" tools:ignore="NewApi">?attr/textAppearanceListItemSmall</item> + <item name="android:textColorLink">@color/suw_link_color_dark</item> + <item name="android:windowAnimationStyle">@style/Animation.SuwWindowAnimation</item> + <item name="android:windowDisablePreview">true</item> + <item name="android:windowSoftInputMode">adjustResize</item> + + <item name="colorAccent">@color/suw_color_accent_dark</item> + <item name="listPreferredItemPaddingLeft">?attr/suwMarginSides</item> + <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> + <item name="suwCardBackground">@drawable/suw_card_bg_dark</item> + <item name="suwItemDescriptionStyle">@style/SuwItemContainer.Description</item> + <item name="suwListItemIconColor">@color/suw_list_item_icon_color_dark</item> + <item name="suwMarginSides">@dimen/suw_layout_margin_sides</item> + <item name="suwNavBarTheme">@style/SuwNavBarThemeDark</item> + <item name="textAppearanceListItemSmall">@style/TextAppearance.SuwItemSummary</item> + </style> + + <style name="SuwThemeMaterial.Light" parent="Theme.AppCompat.Light.NoActionBar"> + <item name="android:indeterminateTint" tools:ignore="NewApi">@color/suw_progress_bar_color_light</item> + <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> + <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> + <item name="android:listPreferredItemHeight">@dimen/suw_items_preferred_height</item> + <item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item> + <item name="android:statusBarColor" tools:ignore="NewApi">@android:color/black</item> + <item name="android:textAppearanceListItemSmall" tools:ignore="NewApi">?attr/textAppearanceListItemSmall</item> + <item name="android:textColorLink">@color/suw_link_color_light</item> + <item name="android:windowAnimationStyle">@style/Animation.SuwWindowAnimation</item> + <item name="android:windowDisablePreview">true</item> + <item name="android:windowSoftInputMode">adjustResize</item> + + <item name="colorAccent">@color/suw_color_accent_light</item> + <item name="listPreferredItemPaddingLeft">?attr/suwMarginSides</item> + <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> + <item name="suwCardBackground">@drawable/suw_card_bg_light</item> + <item name="suwItemDescriptionStyle">@style/SuwItemContainer.Description</item> + <item name="suwListItemIconColor">@color/suw_list_item_icon_color_light</item> + <item name="suwMarginSides">@dimen/suw_layout_margin_sides</item> + <item name="suwNavBarTheme">@style/SuwNavBarThemeLight</item> + <item name="textAppearanceListItemSmall">@style/TextAppearance.SuwItemSummary</item> + </style> + + <style name="SuwThemeGlif" parent="Theme.AppCompat.NoActionBar"> + <item name="android:indeterminateTint" tools:ignore="NewApi">?attr/colorControlActivated</item> + <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> + <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> + <item name="android:listPreferredItemHeight">@dimen/suw_items_preferred_height</item> + <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/suwMarginSides</item> + <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/suwMarginSides</item> + <item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item> + <item name="android:statusBarColor" tools:ignore="NewApi">@android:color/transparent</item> + <item name="android:textAppearanceListItemSmall" tools:ignore="NewApi">?attr/textAppearanceListItemSmall</item> + <item name="android:textColorLink">@color/suw_link_color_dark</item> + <item name="android:windowAnimationStyle">@style/Animation.SuwWindowAnimation</item> + <item name="android:windowDisablePreview">true</item> + <item name="android:windowSoftInputMode">adjustResize</item> + + <item name="colorAccent">@color/suw_color_accent_glif_dark</item> + <item name="colorPrimary">@color/suw_color_accent_glif_dark</item> + <item name="listPreferredItemPaddingLeft">?attr/suwMarginSides</item> + <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> + <item name="suwColorPrimary">?attr/colorPrimary</item> + <item name="suwDividerInset">@dimen/suw_items_glif_icon_divider_inset</item> + <item name="suwGlifHeaderGravity">start</item> + <item name="suwItemDescriptionStyle">@style/SuwItemContainer.Description.Glif</item> + <item name="suwListItemIconColor">@color/suw_list_item_icon_color_dark</item> + <item name="suwMarginSides">@dimen/suw_glif_margin_sides</item> + <item name="suwScrollIndicators">bottom</item> + <item name="textAppearanceListItem">@style/TextAppearance.SuwGlifItemTitle</item> + <item name="textAppearanceListItemSmall">@style/TextAppearance.SuwGlifItemSummary</item> + </style> + + <style name="SuwThemeGlif.Light" parent="Theme.AppCompat.Light.NoActionBar"> + <item name="android:indeterminateTint" tools:ignore="NewApi">?attr/colorControlActivated</item> + <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> + <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> + <item name="android:listPreferredItemHeight">@dimen/suw_items_preferred_height</item> + <item name="android:listPreferredItemPaddingEnd" tools:ignore="NewApi">?attr/suwMarginSides</item> + <item name="android:listPreferredItemPaddingStart" tools:ignore="NewApi">?attr/suwMarginSides</item> + <item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item> + <item name="android:statusBarColor" tools:ignore="NewApi">@android:color/transparent</item> + <item name="android:textAppearanceListItemSmall" tools:ignore="NewApi">?attr/textAppearanceListItemSmall</item> + <item name="android:textColorLink">@color/suw_link_color_light</item> + <item name="android:windowAnimationStyle">@style/Animation.SuwWindowAnimation</item> + <item name="android:windowDisablePreview">true</item> + <item name="android:windowSoftInputMode">adjustResize</item> + + <item name="colorAccent">@color/suw_color_accent_glif_light</item> + <item name="colorPrimary">@color/suw_color_accent_glif_light</item> + <item name="listPreferredItemPaddingLeft">?attr/suwMarginSides</item> + <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> + <item name="suwColorPrimary">?attr/colorPrimary</item> + <item name="suwDividerInset">@dimen/suw_items_glif_icon_divider_inset</item> + <item name="suwGlifHeaderGravity">start</item> + <item name="suwItemDescriptionStyle">@style/SuwItemContainer.Description.Glif</item> + <item name="suwListItemIconColor">@color/suw_list_item_icon_color_light</item> + <item name="suwMarginSides">@dimen/suw_glif_margin_sides</item> + <item name="suwScrollIndicators">bottom</item> + <item name="textAppearanceListItem">@style/TextAppearance.SuwGlifItemTitle</item> + <item name="textAppearanceListItemSmall">@style/TextAppearance.SuwGlifItemSummary</item> + </style> + + <!-- Content styles --> + + <style name="TextAppearance.SuwDescription" parent="TextAppearance.AppCompat.Medium"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textSize">@dimen/suw_description_text_size</item> + </style> + + <!-- Items styles --> + + <style name="SuwItemContainer"> + <item name="android:minHeight">?android:attr/listPreferredItemHeight</item> + <item name="android:paddingBottom">@dimen/suw_items_padding_vertical</item> + <item name="android:paddingEnd" tools:ignore="NewApi">?attr/listPreferredItemPaddingRight</item> + <item name="android:paddingLeft">?attr/listPreferredItemPaddingLeft</item> + <item name="android:paddingRight">?attr/listPreferredItemPaddingRight</item> + <item name="android:paddingStart" tools:ignore="NewApi">?attr/listPreferredItemPaddingLeft</item> + <item name="android:paddingTop">@dimen/suw_items_padding_vertical</item> + </style> + + <style name="SuwItemTitle"> + <item name="android:textAppearance">?attr/textAppearanceListItem</item> + </style> + + <style name="SuwItemSummary"> + <item name="android:textAppearance">?attr/textAppearanceListItemSmall</item> + </style> + + <style name="SuwSwitchStyle"> + <item name="android:paddingEnd" tools:ignore="NewApi">@dimen/suw_switch_padding_end</item> + <item name="android:paddingLeft">@dimen/suw_switch_padding_start</item> + <item name="android:paddingRight">@dimen/suw_switch_padding_end</item> + <item name="android:paddingStart" tools:ignore="NewApi">@dimen/suw_switch_padding_start</item> + </style> + + <style name="SuwSwitchStyle.Divided" parent="SuwSwitchStyle"> + <item name="android:paddingEnd" tools:ignore="NewApi">?attr/listPreferredItemPaddingRight</item> + <item name="android:paddingRight">?attr/listPreferredItemPaddingRight</item> + </style> + + <!-- Button styles --> + + <style name="SuwGlifButton.Primary" parent="Widget.AppCompat.Button.Colored"> + <!-- This style can be applied to a button either as a "style" in XML, or as a theme in + ContextThemeWrapper. These self-referencing attributes make sure this is applied as + both to the button. --> + <item name="android:buttonStyle">@style/SuwGlifButton.Primary</item> + <item name="buttonStyle">@style/SuwGlifButton.Primary</item> + + <!-- Values used in styles --> + <item name="android:minWidth">0dp</item> + <item name="android:paddingLeft">@dimen/suw_glif_button_padding</item> + <item name="android:paddingRight">@dimen/suw_glif_button_padding</item> + </style> + + <style name="SuwGlifButton.Secondary" parent="Widget.AppCompat.Button.Borderless.Colored"> + <!-- This style can be applied to a button either as a "style" in XML, or as a theme in + ContextThemeWrapper. These self-referencing attributes make sure this is applied as + both to the button. --> + <item name="android:buttonStyle">@style/SuwGlifButton.Secondary</item> + <item name="android:theme">@style/SuwGlifButton.Secondary</item> + <item name="buttonStyle">@style/SuwGlifButton.Secondary</item> + + <!-- Values used in styles --> + <item name="android:minWidth">0dp</item> + <item name="android:paddingLeft">@dimen/suw_glif_button_padding</item> + <item name="android:paddingRight">@dimen/suw_glif_button_padding</item> + + <!-- Values used in themes --> + <item name="android:colorControlHighlight" tools:targetApi="lollipop">@color/suw_flat_button_highlight</item> + <item name="colorControlHighlight">@color/suw_flat_button_highlight</item> + </style> + + <!-- Card layout (for tablets) --> + + <style name="TextAppearance.SuwCardTitle" parent="@style/TextAppearance.AppCompat.Display1"> + <item name="android:textColor">@android:color/white</item> + </style> + + <!-- GLIF Card layout (for tablets) --> + + <style name="SuwGlifCardBackground"> + <item name="android:background">?attr/colorPrimary</item> + </style> + + <!-- Navigation bar styles --> + + <style name="SuwNavBarButtonStyle" parent="@android:style/Widget.Button"> + <item name="android:background">?attr/suwNavBarButtonBackground</item> + <item name="android:drawablePadding">@dimen/suw_navbar_button_drawable_padding</item> + <item name="android:fontFamily" tools:ignore="NewApi">sans-serif</item> + <item name="android:minWidth">0dp</item> + <item name="android:paddingLeft">@dimen/suw_navbar_button_padding_sides</item> + <item name="android:paddingRight">@dimen/suw_navbar_button_padding_sides</item> + <item name="android:textAllCaps" tools:ignore="NewApi">true</item> + <item name="android:textColor">?attr/suwNavBarTextColor</item> + <item name="android:textSize">@dimen/suw_navbar_text_size</item> + </style> + + <style name="SuwNavBarThemeDark" parent="SuwNavBarTheme"> + <item name="suwNavBarBackgroundColor">@color/suw_navbar_bg_dark</item> + <item name="suwNavBarButtonBackground">@drawable/suw_navbar_btn_bg_dark</item> + </style> + + <style name="SuwNavBarThemeLight" parent="SuwNavBarTheme"> + <item name="suwNavBarBackgroundColor">@color/suw_navbar_bg_light</item> + <item name="suwNavBarButtonBackground">@drawable/suw_navbar_btn_bg_light</item> + </style> + +</resources> diff --git a/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java b/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java new file mode 100644 index 0000000..be9916e --- /dev/null +++ b/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java @@ -0,0 +1,172 @@ +/* + * 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.items; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.PorterDuff.Mode; +import android.graphics.drawable.Drawable; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +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; + +/** + * A switch item which is divided into two parts: the start (left for LTR) side shows the title and + * 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. + */ +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; + } + + @Override + public CharSequence getSummary() { + return mIsExpanded ? getExpandedSummary() : getCollapsedSummary(); + } + + /** + * @return True if the item is currently expanded. + */ + public boolean isExpanded() { + return mIsExpanded; + } + + /** + * Sets whether the item should be expanded. + */ + public void setExpanded(boolean expanded) { + if (mIsExpanded == expanded) { + return; + } + mIsExpanded = expanded; + notifyItemChanged(); + } + + /** + * @return The summary shown when in collapsed state. + */ + public CharSequence getCollapsedSummary() { + return mCollapsedSummary; + } + + /** + * 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(); + } + } + + /** + * @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); + } + + @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); + } + } + } + + } + } +} diff --git a/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java b/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java new file mode 100644 index 0000000..7459d77 --- /dev/null +++ b/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.items; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v7.widget.SwitchCompat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.CompoundButton; + +import com.android.setupwizardlib.R; + +/** + * An item that is displayed with a switch, with methods to manipulate and listen to the checked + * state of the switch. Note that by default, only click on the switch will change the on-off state. + * To change the switch state when tapping on the text, use the click handlers of list view or + * RecyclerItemAdapter with {@link #toggle(View)}. + */ +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; + + /** + * 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); + mChecked = a.getBoolean(R.styleable.SuwSwitchItem_android_checked, false); + a.recycle(); + } + + /** + * 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); + } + } +} diff --git a/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java new file mode 100644 index 0000000..e6fa497 --- /dev/null +++ b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.util; + +import android.graphics.Rect; +import android.os.Bundle; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.widget.ExploreByTouchHelper; +import android.text.Layout; +import android.text.Spanned; +import android.text.style.ClickableSpan; +import android.util.Log; +import android.view.accessibility.AccessibilityEvent; +import android.widget.TextView; + +import java.util.List; + +/** + * An accessibility delegate that allows {@link android.text.style.ClickableSpan} to be focused and + * clicked by accessibility services. + * + * <p />Sample usage: + * <pre> + * LinkAccessibilityHelper mAccessibilityHelper; + * + * private void init() { + * mAccessibilityHelper = new LinkAccessibilityHelper(myTextView); + * ViewCompat.setAccessibilityDelegate(myTextView, mLinkHelper); + * } + * + * {@literal @}Override + * protected boolean dispatchHoverEvent({@literal @}NonNull MotionEvent event) { + * if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) { + * return true; + * } + * return super.dispatchHoverEvent(event); + * } + * </pre> + * + * @see com.android.setupwizardlib.view.RichTextView + * @see android.support.v4.widget.ExploreByTouchHelper + */ +public class LinkAccessibilityHelper extends ExploreByTouchHelper { + + private static final String TAG = "LinkAccessibilityHelper"; + + private final TextView mView; + private final Rect mTempRect = new Rect(); + + public LinkAccessibilityHelper(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 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)); + } else { + Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); + event.setContentDescription(mView.getText()); + } + } + + @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; + } + + // 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 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 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 new file mode 100644 index 0000000..5172c47 --- /dev/null +++ b/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.view; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Build; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.widget.Button; + +/** + * Button for navigation bar, which includes tinting of its compound drawables to be used for dark + * and light themes. + */ +public class NavigationBarButton extends Button { + + 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]); + } + } + + @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(); + } + + @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()); + } + + 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; + } + } +} diff --git a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java new file mode 100644 index 0000000..6ccedf0 --- /dev/null +++ b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.view; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.support.v4.view.ViewCompat; +import android.text.Annotation; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.TextAppearanceSpan; +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.SpanHelper; +import com.android.setupwizardlib.util.LinkAccessibilityHelper; + +/** + * 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 TextView { + + /* 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; + } + return text; + } + + /* non-static section */ + + private LinkAccessibilityHelper mAccessibilityHelper; + + public RichTextView(Context context) { + super(context); + init(); + } + + public RichTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + mAccessibilityHelper = new LinkAccessibilityHelper(this); + ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper); + } + + @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(LinkMovementMethod.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); + } + + 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; + } + + @Override + protected boolean dispatchHoverEvent(MotionEvent event) { + if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) { + return true; + } + 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); + } + } + } + } + } +} 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 new file mode 100644 index 0000000..74d3be6 --- /dev/null +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java @@ -0,0 +1,85 @@ +/* + * 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.items; + +import static org.junit.Assert.assertTrue; + +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 com.android.setupwizardlib.R; +import com.android.setupwizardlib.test.util.DrawingTestHelper; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ButtonItemDrawingTest { + + private static final int GOOGLE_BLUE = 0xff4285f4; + + // These tests need to be run on UI thread because button uses ValueAnimator + @Rule + public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); + + private ViewGroup mParent; + + @Before + public void setUp() throws Exception { + mParent = new LinearLayout( + DrawingTestHelper.createCanvasActivity(R.style.SuwThemeGlif_Light)); + } + + @Test + @UiThreadTest + public void testColoredButtonTheme() { + TestButtonItem item = new TestButtonItem(); + item.setTheme(R.style.SuwButtonItem_Colored); + item.setText("foobar"); + + final Button button = item.createButton(mParent); + + DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); + drawingTestHelper.drawView(button); + + int googleBluePixelCount = 0; + for (int pixel : drawingTestHelper.getPixels()) { + if (pixel == GOOGLE_BLUE) { + googleBluePixelCount++; + } + } + assertTrue("> 10 pixels should be Google blue. Found " + googleBluePixelCount, + googleBluePixelCount > 10); + } + + 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/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java new file mode 100644 index 0000000..a1d01fd --- /dev/null +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +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 android.support.v4.text.BidiFormatter; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.widget.ExploreByTouchHelper; +import android.text.SpannableStringBuilder; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.widget.TextView; + +import com.android.setupwizardlib.span.LinkSpan; +import com.android.setupwizardlib.util.LinkAccessibilityHelper; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LinkAccessibilityHelperTest { + + private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); + + private TextView mTextView; + private TestLinkAccessibilityHelper 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(); + } + + @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(); + } + + 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 TestLinkAccessibilityHelper(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); + } + + private static class TestLinkAccessibilityHelper extends LinkAccessibilityHelper { + + TestLinkAccessibilityHelper(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); + } + } +} diff --git a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java new file mode 100644 index 0000000..2e2b01e --- /dev/null +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.SuppressLint; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.text.Annotation; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.TextAppearanceSpan; + +import com.android.setupwizardlib.span.LinkSpan; +import com.android.setupwizardlib.view.RichTextView; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +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(InstrumentationRegistry.getContext()); + 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(), 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 testTextStyle() { + Annotation link = new Annotation("textAppearance", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); + 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 testTextContaininingLinksAreFocusable() { + Annotation testLink = new Annotation("link", "value"); + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); + spannableStringBuilder.setSpan(testLink, 0, 3, 0); + + RichTextView view = new RichTextView(InstrumentationRegistry.getContext()); + 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(InstrumentationRegistry.getContext()); + 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 testRichTxtViewFocusChangesWithTextChange() { + RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); + textView.setText("Thou shall not be focusable!"); + + assertFalse(textView.isFocusable()); + + SpannableStringBuilder spannableStringBuilder = + new SpannableStringBuilder("I am focusable"); + spannableStringBuilder.setSpan(new Annotation("link", "focus:on_me"), 0, 1, 0); + textView.setText(spannableStringBuilder); + assertTrue(textView.isFocusable()); + } +} 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 new file mode 100644 index 0000000..154339a --- /dev/null +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java @@ -0,0 +1,30 @@ +/* + * 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.test.util; + +import android.support.v7.app.AppCompatActivity; + +/** + * Activity to test view and drawable drawing behaviors. This is used to make sure that the drawing + * behavior tested is the same as it would when inflated as part of an {@link AppCompatActivity}, + * including custom layout inflaters and theme values that the support library injects to the + * activity. + * + * @see DrawingTestHelper + */ +public class DrawingTestActivity extends AppCompatActivity { +} 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 new file mode 100644 index 0000000..0ae0737 --- /dev/null +++ b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java @@ -0,0 +1,189 @@ +/* + * 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.items; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.robolectric.RuntimeEnvironment.application; + +import android.support.v7.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.TextView; + +import com.android.setupwizardlib.BuildConfig; +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.annotation.Config; + +import java.util.ArrayList; + +@RunWith(SuwLibRobolectricTestRunner.class) +@Config(constants = BuildConfig.class, 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()); + } + + @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("state_checked should not be set initially", + toArrayList(titleView.getDrawableState()), + not(hasItem(android.R.attr.state_checked))); + + mItem.setExpanded(true); + mItem.onBindView(view); + assertThat("state_checked should not be set initially", + toArrayList(titleView.getDrawableState()), + hasItem(android.R.attr.state_checked)); + + mItem.setExpanded(false); + mItem.onBindView(view); + assertThat("state_checked should not be set initially", + toArrayList(titleView.getDrawableState()), + not(hasItem(android.R.attr.state_checked))); + } + + private ArrayList<Integer> toArrayList(int[] array) { + ArrayList<Integer> arrayList = new ArrayList<>(array.length); + for (int i : array) { + arrayList.add(i); + } + return arrayList; + } + + private ViewGroup createLayout() { + ViewGroup root = new FrameLayout(application); + + ViewGroup content = new FrameLayout(application); + content.setId(R.id.suw_items_expandable_switch_content); + root.addView(content); + + TextView titleView = new TextView(application); + titleView.setId(R.id.suw_items_title); + content.addView(titleView); + + mSummaryView = new TextView(application); + mSummaryView.setId(R.id.suw_items_summary); + content.addView(mSummaryView); + + FrameLayout iconContainer = new FrameLayout(application); + iconContainer.setId(R.id.suw_items_icon_container); + content.addView(iconContainer); + + ImageView iconView = new ImageView(application); + iconView.setId(R.id.suw_items_icon); + iconContainer.addView(iconView); + + SwitchCompat switchView = new SwitchCompat(application); + switchView.setId(R.id.suw_items_switch); + root.addView(switchView); + + 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 new file mode 100644 index 0000000..d391d80 --- /dev/null +++ b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.items; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.robolectric.RuntimeEnvironment.application; + +import android.annotation.TargetApi; +import android.os.Build.VERSION_CODES; +import android.support.v7.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.TextView; + +import com.android.setupwizardlib.BuildConfig; +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; + +@RunWith(SuwLibRobolectricTestRunner.class) +@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +public class SwitchItemTest { + + private SwitchCompat mSwitch; + + @Test + public void testChecked() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); + + item.setChecked(true); + + item.onBindView(view); + + assertTrue("Switch should be checked", mSwitch.isChecked()); + } + + @Test + public void testNotChecked() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); + + item.setChecked(false); + + item.onBindView(view); + + assertFalse("Switch should be unchecked", mSwitch.isChecked()); + } + + @Test + public void testListener() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); + + item.setChecked(true); + + final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); + item.setOnCheckedChangeListener(listener); + + item.onBindView(view); + + 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); + + mSwitch.setChecked(true); + + 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); + + SwitchItem item2 = new SwitchItem(); + item2.setTitle("TestTitle2"); + item2.setSummary("TestSummary2"); + item2.setChecked(true); + + View view = createLayout(); + + item1.onBindView(view); + item2.onBindView(view); + + // 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 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. + + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); + + item.setChecked(true); + + final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); + item.setOnCheckedChangeListener(listener); + + item.onBindView(view); + + 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); + + item.setChecked(true); + + 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(); + + item.setChecked(true); + item.onBindView(view); + + assertTrue("Switch should be checked", mSwitch.isChecked()); + + item.toggle(view); + + 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"); + + 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()); + } + + private ViewGroup createLayout() { + ViewGroup root = new FrameLayout(application); + + 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); + + 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); + + mSwitch = new SwitchCompat(application); + mSwitch.setId(R.id.suw_items_switch); + root.addView(mSwitch); + + return root; + } + + private static class TestOnCheckedChangeListener implements SwitchItem.OnCheckedChangeListener { + + boolean mCalled = false; + boolean mChecked = false; + + @Override + public void onCheckedChange(SwitchItem item, boolean isChecked) { + mCalled = true; + mChecked = isChecked; + } + } +} |