summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArthur Hsu <arthurhsu@google.com>2016-05-05 11:06:16 -0700
committerArthur Hsu <arthurhsu@google.com>2016-05-05 11:06:16 -0700
commitd572fa97fb6bae8e3089241645e98dd38b75f345 (patch)
tree04ec463a978c02655712b8d1c0a945e08e1e2127
parent365509ca70a12218fae9df7030152a0c0dd9e5df (diff)
parent7ce5036f2c1a3486fde5c849378a74ba926ba968 (diff)
downloadsetupwizard-d572fa97fb6bae8e3089241645e98dd38b75f345.tar.gz
Merge remote-tracking branch 'goog/nyc-andromeda-dev'; commit '7ce5036f2c1a3486fde5c849378a74ba926ba968' into merge
-rw-r--r--library/eclair-mr1/res/layout/suw_items_switch.xml75
-rw-r--r--library/eclair-mr1/res/values/attrs.xml24
-rw-r--r--library/eclair-mr1/res/values/styles.xml26
-rw-r--r--library/eclair-mr1/src/com/android/setupwizardlib/items/SwitchItem.java100
-rw-r--r--library/eclair-mr1/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java224
-rw-r--r--library/eclair-mr1/src/com/android/setupwizardlib/view/NavigationBarButton.java20
-rw-r--r--library/eclair-mr1/src/com/android/setupwizardlib/view/RichTextView.java118
-rw-r--r--library/eclair-mr1/test/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java164
-rw-r--r--library/eclair-mr1/test/src/com/android/setupwizardlib/test/RichTextViewTest.java75
-rw-r--r--library/eclair-mr1/test/src/com/android/setupwizardlib/test/SwitchItemTest.java171
-rw-r--r--library/full-support/res/layout/suw_glif_preference_recycler_view.xml26
-rw-r--r--library/full-support/res/layout/suw_glif_preference_template_header.xml35
-rw-r--r--library/full-support/res/layout/suw_preference_recycler_view_header.xml26
-rw-r--r--library/full-support/res/layout/suw_preference_recycler_view_normal.xml23
-rw-r--r--library/full-support/res/layout/suw_preference_template_header.xml35
-rw-r--r--library/full-support/res/layout/suw_recycler_template_header.xml1
-rw-r--r--library/full-support/res/values-land/layouts.xml2
-rw-r--r--library/full-support/res/values-sw600dp-land/layouts.xml2
-rw-r--r--library/full-support/res/values-sw600dp/layouts.xml3
-rw-r--r--library/full-support/res/values/attrs.xml10
-rw-r--r--library/full-support/res/values/layouts.xml3
-rw-r--r--library/full-support/src/com/android/setupwizardlib/DividerItemDecoration.java83
-rw-r--r--library/full-support/src/com/android/setupwizardlib/GlifPreferenceLayout.java121
-rw-r--r--library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java42
-rw-r--r--library/full-support/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java120
-rw-r--r--library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerItemsLayout.java116
-rw-r--r--library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java212
-rw-r--r--library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java29
-rw-r--r--library/full-support/src/com/android/setupwizardlib/util/GlifPreferenceDelegate.java7
-rw-r--r--library/full-support/test/res/layout/test_recycler_layout.xml20
-rw-r--r--library/full-support/test/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java133
-rw-r--r--library/full-support/test/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java109
-rw-r--r--library/full-support/test/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java17
-rw-r--r--library/full-support/test/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java109
-rw-r--r--library/full-support/test/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java134
-rw-r--r--library/main/res/layout/suw_items_button_bar.xml23
-rw-r--r--library/main/res/layout/suw_items_description.xml3
-rw-r--r--library/main/res/layout/suw_list_header.xml4
-rw-r--r--library/main/res/layout/suw_no_scroll_template_card.xml9
-rw-r--r--library/main/res/layout/suw_no_scroll_template_card_wide.xml9
-rw-r--r--library/main/res/layout/suw_no_scroll_template_header.xml9
-rw-r--r--library/main/res/layout/suw_no_scroll_template_header_collapsed.xml9
-rw-r--r--library/main/res/values-be-rBY/strings.xml23
-rw-r--r--library/main/res/values-bs-rBA/strings.xml23
-rw-r--r--library/main/res/values-v21/styles.xml34
-rw-r--r--library/main/res/values/attrs.xml21
-rw-r--r--library/main/res/values/colors.xml4
-rw-r--r--library/main/res/values/dimens.xml10
-rw-r--r--library/main/res/values/styles.xml5
-rw-r--r--library/main/src/com/android/setupwizardlib/GlifLayout.java37
-rw-r--r--library/main/src/com/android/setupwizardlib/GlifListLayout.java6
-rw-r--r--library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java13
-rw-r--r--library/main/src/com/android/setupwizardlib/SetupWizardLayout.java106
-rw-r--r--library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java66
-rw-r--r--library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java114
-rw-r--r--library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java127
-rw-r--r--library/main/src/com/android/setupwizardlib/items/ButtonItem.java137
-rw-r--r--library/main/src/com/android/setupwizardlib/items/Item.java3
-rw-r--r--library/main/src/com/android/setupwizardlib/items/ItemGroup.java4
-rw-r--r--library/main/src/com/android/setupwizardlib/items/ItemInflater.java9
-rw-r--r--library/main/src/com/android/setupwizardlib/span/LinkSpan.java87
-rw-r--r--library/main/src/com/android/setupwizardlib/span/SpanHelper.java36
-rw-r--r--library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java24
-rw-r--r--library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java2
-rw-r--r--library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java10
-rw-r--r--library/platform/res/values-v21/styles.xml32
-rw-r--r--library/rules.gradle6
-rw-r--r--library/self.gradle2
-rw-r--r--library/test/src/com/android/setupwizardlib/test/ButtonBarItemTest.java111
-rw-r--r--library/test/src/com/android/setupwizardlib/test/ButtonItemTest.java136
-rw-r--r--library/test/src/com/android/setupwizardlib/test/GlifLayoutTest.java16
-rw-r--r--library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java41
-rw-r--r--library/test/src/com/android/setupwizardlib/test/ItemTest.java7
-rw-r--r--library/test/src/com/android/setupwizardlib/test/LinkSpanTest.java65
-rw-r--r--library/test/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java70
-rw-r--r--library/test/src/com/android/setupwizardlib/test/SpanHelperTest.java41
-rw-r--r--library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java34
77 files changed, 3621 insertions, 322 deletions
diff --git a/library/eclair-mr1/res/layout/suw_items_switch.xml b/library/eclair-mr1/res/layout/suw_items_switch.xml
new file mode 100644
index 0000000..14c7650
--- /dev/null
+++ b/library/eclair-mr1/res/layout/suw_items_switch.xml
@@ -0,0 +1,75 @@
+<?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">
+
+ <TextView
+ android:id="@+id/suw_items_title"
+ style="@style/SuwItemTitle.Verbose"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:textAlignment="viewStart"
+ tools:ignore="UnusedAttribute" />
+
+ <TextView
+ 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"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical" />
+
+</LinearLayout>
diff --git a/library/eclair-mr1/res/values/attrs.xml b/library/eclair-mr1/res/values/attrs.xml
new file mode 100644
index 0000000..34cf3da
--- /dev/null
+++ b/library/eclair-mr1/res/values/attrs.xml
@@ -0,0 +1,24 @@
+<?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>
+
+</resources>
diff --git a/library/eclair-mr1/res/values/styles.xml b/library/eclair-mr1/res/values/styles.xml
index 7e467ac..0b2dcda 100644
--- a/library/eclair-mr1/res/values/styles.xml
+++ b/library/eclair-mr1/res/values/styles.xml
@@ -26,6 +26,7 @@
<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>
@@ -38,7 +39,7 @@
<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.AppCompat.Body1</item>
+ <item name="textAppearanceListItemSmall">@style/TextAppearance.SuwItemSummary</item>
</style>
<style name="SuwThemeMaterial.Light" parent="Theme.AppCompat.Light.NoActionBar">
@@ -48,6 +49,7 @@
<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>
@@ -60,7 +62,7 @@
<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.AppCompat.Body1</item>
+ <item name="textAppearanceListItemSmall">@style/TextAppearance.SuwItemSummary</item>
</style>
<!-- Placeholder for GLIF dark theme, colors are not updated yet -->
@@ -71,13 +73,14 @@
<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/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_light</item>
- <item name="colorPrimary">@color/suw_color_accent_light</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="suwListItemIconColor">@color/suw_list_item_icon_color_dark</item>
@@ -95,13 +98,14 @@
<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_light</item>
- <item name="colorPrimary">@color/suw_color_accent_light</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="suwListItemIconColor">@color/suw_list_item_icon_color_light</item>
@@ -137,6 +141,16 @@
<item name="android:textAppearance">?attr/textAppearanceListItemSmall</item>
</style>
+ <!-- Button styles -->
+
+ <style name="SuwButtonItem" />
+
+ <style name="SuwButtonItem.Colored">
+ <item name="android:buttonStyle">@style/Widget.AppCompat.Button</item>
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="colorButtonNormal">?attr/colorAccent</item>
+ </style>
+
<!-- Card layout (for tablets) -->
<style name="TextAppearance.SuwCardTitle" parent="@style/TextAppearance.AppCompat.Display1">
diff --git a/library/eclair-mr1/src/com/android/setupwizardlib/items/SwitchItem.java b/library/eclair-mr1/src/com/android/setupwizardlib/items/SwitchItem.java
new file mode 100644
index 0000000..aaeaf34
--- /dev/null
+++ b/library/eclair-mr1/src/com/android/setupwizardlib/items/SwitchItem.java
@@ -0,0 +1,100 @@
+/*
+ * 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 with a switch, which the user can
+ */
+public class SwitchItem extends Item implements CompoundButton.OnCheckedChangeListener {
+
+ public interface OnCheckedChangeListener {
+ void onCheckedChange(SwitchItem item, boolean isChecked);
+ }
+
+ private boolean mChecked = false;
+ private OnCheckedChangeListener mListener;
+
+ public SwitchItem() {
+ super();
+ }
+
+ 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();
+ }
+
+ public void setChecked(boolean checked) {
+ if (mChecked != checked) {
+ mChecked = checked;
+ notifyChanged();
+ if (mListener != null) {
+ mListener.onCheckedChange(this, 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.setChecked(mChecked);
+ switchView.setOnCheckedChangeListener(this);
+ switchView.setEnabled(isEnabled());
+ }
+
+ public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (mListener != null) {
+ mListener.onCheckedChange(this, isChecked);
+ }
+ }
+}
diff --git a/library/eclair-mr1/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/library/eclair-mr1/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
new file mode 100644
index 0000000..2c53ee7
--- /dev/null
+++ b/library/eclair-mr1/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
@@ -0,0 +1,224 @@
+/*
+ * 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()) {
+ info.setBoundsInParent(getBoundsForSpan(span, mTempRect));
+ } else {
+ 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) {
+ Spanned spannedText = (Spanned) text;
+ final int spanStart = spannedText.getSpanStart(span);
+ final int spanEnd = spannedText.getSpanEnd(span);
+ final Layout layout = mView.getLayout();
+ 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);
+ outRect.left = (int) xStart;
+ if (lineEnd == lineStart) {
+ outRect.right = (int) xEnd;
+ } // otherwise just leave it at the end of the start line
+
+ // 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/eclair-mr1/src/com/android/setupwizardlib/view/NavigationBarButton.java b/library/eclair-mr1/src/com/android/setupwizardlib/view/NavigationBarButton.java
index 1ffc034..6e555a1 100644
--- a/library/eclair-mr1/src/com/android/setupwizardlib/view/NavigationBarButton.java
+++ b/library/eclair-mr1/src/com/android/setupwizardlib/view/NavigationBarButton.java
@@ -44,7 +44,7 @@ public class NavigationBarButton extends Button {
Drawable[] drawables = getCompoundDrawablesRelative();
for (int i = 0; i < drawables.length; i++) {
if (drawables[i] != null) {
- drawables[i] = TintedDrawable.wrap(drawables[i].mutate());
+ drawables[i] = TintedDrawable.wrap(drawables[i]);
}
}
setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1],
@@ -54,10 +54,10 @@ public class NavigationBarButton extends Button {
@Override
public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
- if (left != null) left = TintedDrawable.wrap(left.mutate());
- if (top != null) top = TintedDrawable.wrap(top.mutate());
- if (right != null) right = TintedDrawable.wrap(right.mutate());
- if (bottom != null) bottom = TintedDrawable.wrap(bottom.mutate());
+ 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();
}
@@ -65,10 +65,10 @@ public class NavigationBarButton extends Button {
@Override
public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end,
Drawable bottom) {
- if (start != null) start = TintedDrawable.wrap(start.mutate());
- if (top != null) top = TintedDrawable.wrap(top.mutate());
- if (end != null) end = TintedDrawable.wrap(end.mutate());
- if (bottom != null) bottom = TintedDrawable.wrap(bottom.mutate());
+ 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();
}
@@ -114,7 +114,7 @@ public class NavigationBarButton extends Button {
if (drawable instanceof TintedDrawable) {
return (TintedDrawable) drawable;
}
- return new TintedDrawable(drawable);
+ return new TintedDrawable(drawable.mutate());
}
private ColorStateList mTintList = null;
diff --git a/library/eclair-mr1/src/com/android/setupwizardlib/view/RichTextView.java b/library/eclair-mr1/src/com/android/setupwizardlib/view/RichTextView.java
new file mode 100644
index 0000000..d79d149
--- /dev/null
+++ b/library/eclair-mr1/src/com/android/setupwizardlib/view/RichTextView.java
@@ -0,0 +1,118 @@
+/*
+ * 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.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.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 &lt;annotation&gt; tags in strings to become their respective types. Currently 2
+ * types are supported:
+ * <ol>
+ * <li>&lt;annotation link="foobar"&gt; will create a
+ * {@link com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key
+ * "foobar"</li>
+ * <li>&lt;annotation textAppearance="TextAppearance.FooBar"&gt; 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);
+ setMovementMethod(LinkMovementMethod.getInstance());
+ }
+
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ text = getRichText(getContext(), text);
+ super.setText(text, type);
+ }
+
+ @Override
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+}
diff --git a/library/eclair-mr1/test/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java b/library/eclair-mr1/test/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java
new file mode 100644
index 0000000..8d42fa3
--- /dev/null
+++ b/library/eclair-mr1/test/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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 android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.SpannableStringBuilder;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.TextView;
+
+import com.android.setupwizardlib.span.LinkSpan;
+import com.android.setupwizardlib.util.LinkAccessibilityHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class LinkAccessibilityHelperTest extends AndroidTestCase {
+
+ private TextView mTextView;
+ private TestLinkAccessibilityHelper mHelper;
+ private LinkSpan mSpan;
+
+ private DisplayMetrics mDisplayMetrics;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSpan = new LinkSpan("foobar");
+ SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world");
+ ssb.setSpan(mSpan, 1, 2, 0 /* flags */);
+
+ mTextView = new TextView(getContext());
+ mTextView.setText(ssb);
+ mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
+ mHelper = new TestLinkAccessibilityHelper(mTextView);
+
+ mTextView.measure(dp2Px(500), dp2Px(500));
+ mTextView.layout(dp2Px(0), dp2Px(0), dp2Px(500), dp2Px(500));
+ }
+
+ @SmallTest
+ public void testGetVirtualViewAt() {
+ final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10));
+ assertEquals("Virtual view ID should be 1", 1, virtualViewId);
+ }
+
+ @SmallTest
+ public void testGetVirtualViewAtHost() {
+ final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100));
+ assertEquals("Virtual view ID should be INVALID_ID",
+ ExploreByTouchHelper.INVALID_ID, virtualViewId);
+ }
+
+ @SmallTest
+ public void testGetVisibleVirtualViews() {
+ List<Integer> virtualViewIds = new ArrayList<>();
+ mHelper.getVisibleVirtualViews(virtualViewIds);
+
+ assertEquals("VisibleVirtualViews should be [1]",
+ Collections.singletonList(1), virtualViewIds);
+ }
+
+ @SmallTest
+ public void testOnPopulateEventForVirtualView() {
+ 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();
+ }
+
+ @SmallTest
+ public void testOnPopulateEventForVirtualViewHost() {
+ 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();
+ }
+
+ @SmallTest
+ public void testOnPopulateNodeForVirtualView() {
+ 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();
+ }
+
+ private int dp2Px(float dp) {
+ if (mDisplayMetrics == null) {
+ mDisplayMetrics = getContext().getResources().getDisplayMetrics();
+ }
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics);
+ }
+
+ private static class TestLinkAccessibilityHelper extends LinkAccessibilityHelper {
+
+ public 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/eclair-mr1/test/src/com/android/setupwizardlib/test/RichTextViewTest.java b/library/eclair-mr1/test/src/com/android/setupwizardlib/test/RichTextViewTest.java
new file mode 100644
index 0000000..c591580
--- /dev/null
+++ b/library/eclair-mr1/test/src/com/android/setupwizardlib/test/RichTextViewTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+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 java.util.Arrays;
+
+public class RichTextViewTest extends AndroidTestCase {
+
+ @SmallTest
+ 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(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());
+ }
+
+ @SmallTest
+ 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(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);
+ }
+}
diff --git a/library/eclair-mr1/test/src/com/android/setupwizardlib/test/SwitchItemTest.java b/library/eclair-mr1/test/src/com/android/setupwizardlib/test/SwitchItemTest.java
new file mode 100644
index 0000000..e7c93ba
--- /dev/null
+++ b/library/eclair-mr1/test/src/com/android/setupwizardlib/test/SwitchItemTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.test;
+
+import android.support.v7.widget.SwitchCompat;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.setupwizardlib.R;
+import com.android.setupwizardlib.items.SwitchItem;
+
+public class SwitchItemTest extends AndroidTestCase {
+
+ private SwitchCompat mSwitch;
+
+ @SmallTest
+ 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());
+ }
+
+ @SmallTest
+ 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());
+ }
+
+ @SmallTest
+ 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.called);
+ assertFalse("Listener should not be checked", listener.checked);
+
+ mSwitch.setChecked(true);
+
+ assertTrue("Listener should be called", listener.called);
+ assertTrue("Listener should be checked", listener.checked);
+ }
+
+ @SmallTest
+ 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.called);
+ assertFalse("Listener should not be checked", listener.checked);
+
+ item.setChecked(true);
+
+ assertTrue("Listener should be called", listener.called);
+ assertTrue("Listener should be checked", listener.checked);
+ }
+
+ @SmallTest
+ 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());
+ }
+
+ private ViewGroup createLayout() {
+ ViewGroup root = new FrameLayout(mContext);
+
+ TextView titleView = new TextView(mContext);
+ titleView.setId(R.id.suw_items_title);
+ root.addView(titleView);
+
+ TextView summaryView = new TextView(mContext);
+ summaryView.setId(R.id.suw_items_summary);
+ root.addView(summaryView);
+
+ FrameLayout iconContainer = new FrameLayout(mContext);
+ iconContainer.setId(R.id.suw_items_icon_container);
+ root.addView(iconContainer);
+
+ ImageView iconView = new ImageView(mContext);
+ iconView.setId(R.id.suw_items_icon);
+ iconContainer.addView(iconView);
+
+ mSwitch = new SwitchCompat(mContext);
+ mSwitch.setId(R.id.suw_items_switch);
+ root.addView(mSwitch);
+
+ return root;
+ }
+
+ private static class TestOnCheckedChangeListener implements SwitchItem.OnCheckedChangeListener {
+
+ public boolean called = false;
+ public boolean checked = false;
+
+ @Override
+ public void onCheckedChange(SwitchItem item, boolean isChecked) {
+ called = true;
+ checked = isChecked;
+ }
+ }
+}
diff --git a/library/full-support/res/layout/suw_glif_preference_recycler_view.xml b/library/full-support/res/layout/suw_glif_preference_recycler_view.xml
new file mode 100644
index 0000000..af00160
--- /dev/null
+++ b/library/full-support/res/layout/suw_glif_preference_recycler_view.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<com.android.setupwizardlib.view.HeaderRecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/suw_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:scrollbars="vertical"
+ app:suwHeader="@layout/suw_glif_header" />
diff --git a/library/full-support/res/layout/suw_glif_preference_template_header.xml b/library/full-support/res/layout/suw_glif_preference_template_header.xml
new file mode 100644
index 0000000..6377616
--- /dev/null
+++ b/library/full-support/res/layout/suw_glif_preference_template_header.xml
@@ -0,0 +1,35 @@
+<?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"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/suw_layout_content"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <com.android.setupwizardlib.view.NavigationBar
+ android:id="@+id/suw_layout_navigation_bar"
+ style="@style/SuwNavBarTheme"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/suw_navbar_height" />
+
+</LinearLayout>
diff --git a/library/full-support/res/layout/suw_preference_recycler_view_header.xml b/library/full-support/res/layout/suw_preference_recycler_view_header.xml
new file mode 100644
index 0000000..20e1d19
--- /dev/null
+++ b/library/full-support/res/layout/suw_preference_recycler_view_header.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<com.android.setupwizardlib.view.StickyHeaderRecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/suw_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:scrollbars="vertical"
+ app:suwHeader="@layout/suw_list_header" />
diff --git a/library/full-support/res/layout/suw_preference_recycler_view_normal.xml b/library/full-support/res/layout/suw_preference_recycler_view_normal.xml
new file mode 100644
index 0000000..0979d91
--- /dev/null
+++ b/library/full-support/res/layout/suw_preference_recycler_view_normal.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<android.support.v7.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/suw_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="vertical" />
diff --git a/library/full-support/res/layout/suw_preference_template_header.xml b/library/full-support/res/layout/suw_preference_template_header.xml
new file mode 100644
index 0000000..6377616
--- /dev/null
+++ b/library/full-support/res/layout/suw_preference_template_header.xml
@@ -0,0 +1,35 @@
+<?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"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/suw_layout_content"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <com.android.setupwizardlib.view.NavigationBar
+ android:id="@+id/suw_layout_navigation_bar"
+ style="@style/SuwNavBarTheme"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/suw_navbar_height" />
+
+</LinearLayout>
diff --git a/library/full-support/res/layout/suw_recycler_template_header.xml b/library/full-support/res/layout/suw_recycler_template_header.xml
index 1d453f7..d2c9622 100644
--- a/library/full-support/res/layout/suw_recycler_template_header.xml
+++ b/library/full-support/res/layout/suw_recycler_template_header.xml
@@ -26,6 +26,7 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
+ android:clipChildren="false"
android:scrollbars="vertical"
app:suwHeader="@layout/suw_list_header" />
diff --git a/library/full-support/res/values-land/layouts.xml b/library/full-support/res/values-land/layouts.xml
index 5bd3ea8..3aacec9 100644
--- a/library/full-support/res/values-land/layouts.xml
+++ b/library/full-support/res/values-land/layouts.xml
@@ -17,6 +17,8 @@
<resources>
+ <item name="suw_preference_recycler_view" type="layout">@layout/suw_preference_recycler_view_normal</item>
+ <item name="suw_preference_template" type="layout">@layout/suw_no_scroll_template_header_collapsed</item>
<item name="suw_recycler_template" type="layout">@layout/suw_recycler_template_header_collapsed</item>
<item name="suw_recycler_template_short" type="layout">@layout/suw_recycler_template_header_collapsed</item>
diff --git a/library/full-support/res/values-sw600dp-land/layouts.xml b/library/full-support/res/values-sw600dp-land/layouts.xml
index 3ac3597..0feed90 100644
--- a/library/full-support/res/values-sw600dp-land/layouts.xml
+++ b/library/full-support/res/values-sw600dp-land/layouts.xml
@@ -17,6 +17,8 @@
<resources>
+ <item name="suw_preference_recycler_view" type="layout">@layout/suw_preference_recycler_view_normal</item>
+ <item name="suw_preference_template" type="layout">@layout/suw_no_scroll_template_card_wide</item>
<item name="suw_recycler_template" type="layout">@layout/suw_recycler_template_card_wide</item>
<item name="suw_recycler_template_short" type="layout">@layout/suw_recycler_template_card_wide</item>
diff --git a/library/full-support/res/values-sw600dp/layouts.xml b/library/full-support/res/values-sw600dp/layouts.xml
index 98b1a79..bfd4863 100644
--- a/library/full-support/res/values-sw600dp/layouts.xml
+++ b/library/full-support/res/values-sw600dp/layouts.xml
@@ -17,9 +17,12 @@
<resources>
+ <item name="suw_preference_recycler_view" type="layout">@layout/suw_preference_recycler_view_normal</item>
+ <item name="suw_preference_template" type="layout">@layout/suw_no_scroll_template_card</item>
<item name="suw_recycler_template" type="layout">@layout/suw_recycler_template_card</item>
<item name="suw_recycler_template_short" type="layout">@layout/suw_recycler_template_card</item>
+ <item name="suw_glif_preference_template" type="layout">@layout/suw_glif_blank_template_card</item>
<item name="suw_glif_recycler_template" type="layout">@layout/suw_glif_recycler_template_card</item>
</resources>
diff --git a/library/full-support/res/values/attrs.xml b/library/full-support/res/values/attrs.xml
index 059da5f..86b2a56 100644
--- a/library/full-support/res/values/attrs.xml
+++ b/library/full-support/res/values/attrs.xml
@@ -21,12 +21,20 @@
<declare-styleable name="SuwSetupWizardRecyclerItemsLayout">
<attr name="android:entries" />
+ <attr name="suwDividerInset" />
<attr name="suwHasStableIds" />
</declare-styleable>
<declare-styleable name="SuwGlifRecyclerLayout">
<attr name="android:entries" />
- <attr name="suwHasStableIds" />
<attr name="suwDividerInset" />
+ <attr name="suwHasStableIds" />
</declare-styleable>
+
+ <declare-styleable name="SuwRecyclerItemAdapter">
+ <attr name="android:colorBackground" />
+ <attr name="android:selectableItemBackground" />
+ <attr name="selectableItemBackground" />
+ </declare-styleable>
+
</resources>
diff --git a/library/full-support/res/values/layouts.xml b/library/full-support/res/values/layouts.xml
index 291d8d7..957d044 100644
--- a/library/full-support/res/values/layouts.xml
+++ b/library/full-support/res/values/layouts.xml
@@ -17,9 +17,12 @@
<resources>
+ <item name="suw_preference_recycler_view" type="layout">@layout/suw_preference_recycler_view_header</item>
+ <item name="suw_preference_template" type="layout">@layout/suw_preference_template_header</item>
<item name="suw_recycler_template" type="layout">@layout/suw_recycler_template_header</item>
<item name="suw_recycler_template_short" type="layout">@layout/suw_recycler_template_header_collapsed</item>
+ <item name="suw_glif_preference_template" type="layout">@layout/suw_glif_blank_template_compact</item>
<item name="suw_glif_recycler_template" type="layout">@layout/suw_glif_recycler_template_compact</item>
</resources>
diff --git a/library/full-support/src/com/android/setupwizardlib/DividerItemDecoration.java b/library/full-support/src/com/android/setupwizardlib/DividerItemDecoration.java
index ad2037d..8c937a1 100644
--- a/library/full-support/src/com/android/setupwizardlib/DividerItemDecoration.java
+++ b/library/full-support/src/com/android/setupwizardlib/DividerItemDecoration.java
@@ -21,10 +21,14 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.support.annotation.IntDef;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* An {@link android.support.v7.widget.RecyclerView.ItemDecoration} for RecyclerView to draw
* dividers between items. This ItemDecoration will draw the drawable specified by
@@ -53,10 +57,14 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration {
boolean isDividerAllowedBelow();
}
- private static final int[] ATTRS = new int[]{
- android.R.attr.listDivider,
- android.R.attr.dividerHeight
- };
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DIVIDER_CONDITION_EITHER,
+ DIVIDER_CONDITION_BOTH})
+ public @interface DividerCondition {}
+
+ public static final int DIVIDER_CONDITION_EITHER = 0;
+ public static final int DIVIDER_CONDITION_BOTH = 1;
/**
* Creates a default instance of {@link DividerItemDecoration}, using
@@ -64,14 +72,20 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration {
* divider height.
*/
public static DividerItemDecoration getDefault(Context context) {
- final TypedArray a = context.obtainStyledAttributes(ATTRS);
- final Drawable divider = a.getDrawable(0);
- final int dividerHeight = a.getDimensionPixelSize(1, 0);
+ final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration);
+ final Drawable divider = a.getDrawable(
+ R.styleable.SuwDividerItemDecoration_android_listDivider);
+ final int dividerHeight = a.getDimensionPixelSize(
+ R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0);
+ @DividerCondition final int dividerCondition = a.getInt(
+ R.styleable.SuwDividerItemDecoration_suwDividerCondition,
+ DIVIDER_CONDITION_EITHER);
a.recycle();
final DividerItemDecoration decoration = new DividerItemDecoration();
decoration.setDivider(divider);
decoration.setDividerHeight(dividerHeight);
+ decoration.setDividerCondition(dividerCondition);
return decoration;
}
@@ -80,6 +94,8 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mDividerHeight;
private int mDividerIntrinsicHeight;
+ @DividerCondition
+ private int mDividerCondition;
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
@@ -109,21 +125,29 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
- if ((holder instanceof DividedViewHolder)
- && !((DividedViewHolder) holder).isDividerAllowedBelow()) {
- // Don't draw if the current view holder doesn't allow drawing below
- return false;
- }
- final int index = parent.indexOfChild(view);
- final int lastItemIndex = parent.getChildCount() - 1;
- if (index == lastItemIndex) {
- return false;
+ final int index = holder.getLayoutPosition();
+ final int lastItemIndex = parent.getAdapter().getItemCount() - 1;
+ if ((holder instanceof DividedViewHolder)) {
+ if (((DividedViewHolder) holder).isDividerAllowedBelow()) {
+ if (mDividerCondition == DIVIDER_CONDITION_EITHER) {
+ // Draw the divider without consulting the next item if we only
+ // need permission for either above or below.
+ return true;
+ }
+ } else if (mDividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) {
+ // Don't draw if the current view holder doesn't allow drawing below
+ // and the current theme requires permission for both the item below and above.
+ // Also, if this is the last item, there is no item below to ask permission
+ // for whether to draw a divider above, so don't draw it.
+ return false;
+ }
}
+ // Require permission from index below to draw the divider.
if (index < lastItemIndex) {
- final View nextView = parent.getChildAt(index + 1);
- final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
- if ((nextHolder instanceof DividedViewHolder) &&
- !((DividedViewHolder) nextHolder).isDividerAllowedAbove()) {
+ final RecyclerView.ViewHolder nextHolder =
+ parent.findViewHolderForLayoutPosition(index + 1);
+ if ((nextHolder instanceof DividedViewHolder)
+ && !((DividedViewHolder) nextHolder).isDividerAllowedAbove()) {
// Don't draw if the next view holder doesn't allow drawing above
return false;
}
@@ -163,4 +187,23 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration {
public int getDividerHeight() {
return mDividerHeight;
}
+
+ /**
+ * Sets whether the divider needs permission from both the item view holder below
+ * and above from where the divider would draw itself or just needs permission from
+ * one or the other before drawing itself.
+ */
+ public void setDividerCondition(@DividerCondition int dividerCondition) {
+ mDividerCondition = dividerCondition;
+ }
+
+ /**
+ * Gets whether the divider needs permission from both the item view holder below
+ * and above from where the divider would draw itself or just needs permission from
+ * one or the other before drawing itself.
+ */
+ @DividerCondition
+ public int getDividerCondition() {
+ return mDividerCondition;
+ }
}
diff --git a/library/full-support/src/com/android/setupwizardlib/GlifPreferenceLayout.java b/library/full-support/src/com/android/setupwizardlib/GlifPreferenceLayout.java
new file mode 100644
index 0000000..d334d7d
--- /dev/null
+++ b/library/full-support/src/com/android/setupwizardlib/GlifPreferenceLayout.java
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A layout to be used with {@code PreferenceFragment} in v14 support library. This can be specified
+ * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in
+ * {@code app:preferenceTheme}.
+ *
+ * <p />Example:
+ * <pre>{@code
+ * &lt;style android:name="MyActivityTheme">
+ * &lt;item android:name="preferenceTheme">@style/MyPreferenceTheme&lt;/item>
+ * &lt;/style>
+ *
+ * &lt;style android:name="MyPreferenceTheme">
+ * &lt;item android:name="preferenceFragmentStyle">@style/MyPreferenceFragmentStyle&lt;/item>
+ * &lt;/style>
+ *
+ * &lt;style android:name="MyPreferenceFragmentStyle">
+ * &lt;item android:name="android:layout">@layout/my_preference_layout&lt;/item>
+ * &lt;/style>
+ * }</pre>
+ *
+ * where {@code my_preference_layout} is a layout that contains
+ * {@link com.android.setupwizardlib.GlifPreferenceLayout}.
+ *
+ * <p />Example:
+ * <pre>{@code
+ * &lt;com.android.setupwizardlib.GlifPreferenceLayout
+ * xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:id="@id/list_container"
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent" />
+ * }</pre>
+ *
+ * <p />Fragments using this layout <em>must</em> delegate {@code onCreateRecyclerView} to the
+ * implementation in this class:
+ * {@link #onCreateRecyclerView(android.view.LayoutInflater, android.view.ViewGroup,
+ * android.os.Bundle)}
+ */
+public class GlifPreferenceLayout extends GlifRecyclerLayout {
+
+ private RecyclerView mRecyclerView;
+
+ public GlifPreferenceLayout(Context context) {
+ super(context);
+ }
+
+ public GlifPreferenceLayout(Context context, int template, int containerId) {
+ super(context, template, containerId);
+ }
+
+ public GlifPreferenceLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public GlifPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public RecyclerView getRecyclerView() {
+ return mRecyclerView;
+ }
+
+ @Override
+ protected ViewGroup findContainer(int containerId) {
+ if (containerId == 0) {
+ containerId = R.id.suw_layout_content;
+ }
+ return super.findContainer(containerId);
+ }
+
+ /**
+ * This method must be called in {@code PreferenceFragment#onCreateRecyclerView}.
+ */
+ public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ return mRecyclerView;
+ }
+
+ @Override
+ protected View onInflateTemplate(LayoutInflater inflater, int template) {
+ if (template == 0) {
+ template = R.layout.suw_glif_preference_template;
+ }
+ return super.onInflateTemplate(inflater, template);
+ }
+
+ @Override
+ protected void onTemplateInflated() {
+ // Inflate the recycler view here, so attributes on the decoration views can be applied
+ // immediately.
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ mRecyclerView = (RecyclerView) inflater.inflate(R.layout.suw_glif_preference_recycler_view,
+ this, false);
+ initRecyclerView(mRecyclerView);
+ }
+}
diff --git a/library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java b/library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java
index fd8a974..509532f 100644
--- a/library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java
+++ b/library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java
@@ -28,8 +28,6 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.android.setupwizardlib.items.ItemGroup;
import com.android.setupwizardlib.items.ItemInflater;
@@ -44,8 +42,8 @@ import com.android.setupwizardlib.view.HeaderRecyclerView;
public class GlifRecyclerLayout extends GlifLayout {
private RecyclerView mRecyclerView;
- private TextView mHeaderTextView;
- private ImageView mIconView;
+ private View mHeader;
+
private DividerItemDecoration mDividerDecoration;
private Drawable mDefaultDivider;
private Drawable mDivider;
@@ -90,7 +88,7 @@ public class GlifRecyclerLayout extends GlifLayout {
a.getDimensionPixelSize(R.styleable.SuwGlifRecyclerLayout_suwDividerInset, 0);
if (dividerInset == 0) {
dividerInset = getResources()
- .getDimensionPixelSize(R.dimen.suw_items_icon_divider_inset);
+ .getDimensionPixelSize(R.dimen.suw_items_glif_icon_divider_inset);
}
setDividerInset(dividerInset);
a.recycle();
@@ -123,26 +121,28 @@ public class GlifRecyclerLayout extends GlifLayout {
@Override
protected void onTemplateInflated() {
- final Context context = getContext();
- mRecyclerView = (RecyclerView) findViewById(R.id.suw_recycler_view);
- mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
+ initRecyclerView((RecyclerView) findViewById(R.id.suw_recycler_view));
+ }
+
+ protected void initRecyclerView(RecyclerView recyclerView) {
+ mRecyclerView = recyclerView;
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
if (mRecyclerView instanceof HeaderRecyclerView) {
- final View header = ((HeaderRecyclerView) mRecyclerView).getHeader();
- mHeaderTextView = (TextView) header.findViewById(R.id.suw_layout_title);
- mIconView = (ImageView) header.findViewById(R.id.suw_layout_icon);
+ mHeader = ((HeaderRecyclerView) mRecyclerView).getHeader();
}
- mDividerDecoration = DividerItemDecoration.getDefault(context);
+ mDividerDecoration = DividerItemDecoration.getDefault(getContext());
mRecyclerView.addItemDecoration(mDividerDecoration);
}
@Override
- protected TextView getHeaderTextView() {
- return mHeaderTextView;
- }
-
- @Override
- protected ImageView getIconView() {
- return mIconView;
+ protected View findManagedViewById(int id) {
+ if (mHeader != null) {
+ final View view = mHeader.findViewById(id);
+ if (view != null) {
+ return view;
+ }
+ }
+ return super.findViewById(id);
}
public RecyclerView getRecyclerView() {
@@ -166,8 +166,8 @@ public class GlifRecyclerLayout extends GlifLayout {
* theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
*
* @param inset The number of pixels to inset on the "start" side of the list divider. Typically
- * this will be either {@code @dimen/suw_items_icon_divider_inset} or
- * {@code @dimen/suw_items_text_divider_inset}.
+ * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or
+ * {@code @dimen/suw_items_glif_text_divider_inset}.
*/
public void setDividerInset(int inset) {
mDividerInset = inset;
diff --git a/library/full-support/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java b/library/full-support/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java
new file mode 100644
index 0000000..3d3b5d3
--- /dev/null
+++ b/library/full-support/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A layout to be used with {@code PreferenceFragment} in v14 support library. This can be specified
+ * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in
+ * {@code app:preferenceTheme}.
+ *
+ * <p />Example:
+ * <pre>{@code
+ * &lt;style android:name="MyActivityTheme">
+ * &lt;item android:name="preferenceTheme">@style/MyPreferenceTheme&lt;/item>
+ * &lt;/style>
+ *
+ * &lt;style android:name="MyPreferenceTheme">
+ * &lt;item android:name="preferenceFragmentStyle">@style/MyPreferenceFragmentStyle&lt;/item>
+ * &lt;/style>
+ *
+ * &lt;style android:name="MyPreferenceFragmentStyle">
+ * &lt;item android:name="android:layout">@layout/my_preference_layout&lt;/item>
+ * &lt;/style>
+ * }</pre>
+ *
+ * where {@code my_preference_layout} is a layout that contains
+ * {@link com.android.setupwizardlib.SetupWizardPreferenceLayout}.
+ *
+ * <p />Example:
+ * <pre>{@code
+ * &lt;com.android.setupwizardlib.SetupWizardPreferenceLayout
+ * xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:id="@id/list_container"
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent" />
+ * }</pre>
+ *
+ * <p />Fragments using this layout <em>must</em> delegate {@code onCreateRecyclerView} to the
+ * implementation in this class:
+ * {@link #onCreateRecyclerView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)}
+ */
+public class SetupWizardPreferenceLayout extends SetupWizardRecyclerLayout {
+
+ private RecyclerView mRecyclerView;
+
+ public SetupWizardPreferenceLayout(Context context) {
+ super(context);
+ }
+
+ public SetupWizardPreferenceLayout(Context context, int template, int containerId) {
+ super(context, template, containerId);
+ }
+
+ public SetupWizardPreferenceLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SetupWizardPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public RecyclerView getRecyclerView() {
+ return mRecyclerView;
+ }
+
+ @Override
+ protected ViewGroup findContainer(int containerId) {
+ if (containerId == 0) {
+ containerId = R.id.suw_layout_content;
+ }
+ return super.findContainer(containerId);
+ }
+
+ /**
+ * This method must be called in {@code PreferenceFragment#onCreateRecyclerView}.
+ */
+ public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ return mRecyclerView;
+ }
+
+ @Override
+ protected View onInflateTemplate(LayoutInflater inflater, int template) {
+ if (template == 0) {
+ template = R.layout.suw_preference_template;
+ }
+ return super.onInflateTemplate(inflater, template);
+ }
+
+ @Override
+ protected void onTemplateInflated() {
+ // Inflate the recycler view here, so attributes on the decoration views can be applied
+ // immediately.
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ mRecyclerView = (RecyclerView) inflater.inflate(R.layout.suw_preference_recycler_view,
+ this, false);
+ initRecyclerView(mRecyclerView);
+ }
+}
diff --git a/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerItemsLayout.java b/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerItemsLayout.java
index 97f6589..23e7cef 100644
--- a/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerItemsLayout.java
+++ b/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerItemsLayout.java
@@ -17,133 +17,25 @@
package com.android.setupwizardlib;
import android.content.Context;
-import android.content.res.TypedArray;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import com.android.setupwizardlib.items.ItemGroup;
-import com.android.setupwizardlib.items.ItemInflater;
import com.android.setupwizardlib.items.RecyclerItemAdapter;
-import com.android.setupwizardlib.util.RecyclerViewRequireScrollHelper;
-import com.android.setupwizardlib.view.HeaderRecyclerView;
-import com.android.setupwizardlib.view.NavigationBar;
/**
- * A SetupWizardLayout for use with {@link com.android.setupwizardlib.items.RecyclerItemAdapter},
- * which displays a list of items using a RecyclerView. The items XML file can be specified through
- * {@code android:entries} attribute in the layout.
- *
- * @see com.android.setupwizardlib.SetupWizardItemsLayout
+ * @deprecated Use {@link com.android.setupwizardlib.SetupWizardRecyclerLayout}
*/
-public class SetupWizardRecyclerItemsLayout extends SetupWizardLayout {
-
- private static final String TAG = "RecyclerItemsLayout";
-
- private RecyclerItemAdapter mAdapter;
- private RecyclerView mRecyclerView;
-
- private TextView mHeaderTextView;
- private View mDecorationView;
+@Deprecated
+public class SetupWizardRecyclerItemsLayout extends SetupWizardRecyclerLayout {
public SetupWizardRecyclerItemsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- init(context, attrs, 0);
}
public SetupWizardRecyclerItemsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- init(context, attrs, defStyleAttr);
- }
-
- private void init(Context context, AttributeSet attrs, int defStyleAttr) {
- final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.SuwSetupWizardRecyclerItemsLayout, defStyleAttr, 0);
- final int xml = a.getResourceId(
- R.styleable.SuwSetupWizardRecyclerItemsLayout_android_entries, 0);
- if (xml != 0) {
- final ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(xml);
- mAdapter = new RecyclerItemAdapter(inflated);
- mAdapter.setHasStableIds(a.getBoolean(
- R.styleable.SuwSetupWizardRecyclerItemsLayout_suwHasStableIds, false));
- setAdapter(mAdapter);
- }
- a.recycle();
}
public RecyclerItemAdapter getAdapter() {
- return mAdapter;
- }
-
- public void setAdapter(RecyclerItemAdapter adapter) {
- mAdapter = adapter;
- getRecyclerView().setAdapter(adapter);
- }
-
- public RecyclerView getRecyclerView() {
- return mRecyclerView;
- }
-
- @Override
- protected ViewGroup findContainer(int containerId) {
- if (containerId == 0) {
- containerId = R.id.suw_recycler_view;
- }
- return super.findContainer(containerId);
- }
-
- @Override
- protected void onTemplateInflated() {
- mRecyclerView = (RecyclerView) findViewById(R.id.suw_recycler_view);
- mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mRecyclerView.addItemDecoration(DividerItemDecoration.getDefault(getContext()));
- if (mRecyclerView instanceof HeaderRecyclerView) {
- final View header = ((HeaderRecyclerView) mRecyclerView).getHeader();
- mHeaderTextView = (TextView) header.findViewById(R.id.suw_layout_title);
- mDecorationView = header.findViewById(R.id.suw_layout_decor);
- }
- }
-
- @Override
- protected View onInflateTemplate(LayoutInflater inflater, int template) {
- if (template == 0) {
- template = R.layout.suw_recycler_template;
- }
- return super.onInflateTemplate(inflater, template);
- }
-
- @Override
- protected TextView getHeaderTextView() {
- if (mHeaderTextView != null) {
- return mHeaderTextView;
- } else {
- return super.getHeaderTextView();
- }
- }
-
- @Override
- protected View getDecorationView() {
- if (mDecorationView != null) {
- return mDecorationView;
- } else {
- return super.getDecorationView();
- }
- }
-
- @Override
- public void requireScrollToBottom() {
- final NavigationBar navigationBar = getNavigationBar();
- final RecyclerView recyclerView = getRecyclerView();
- if (navigationBar != null && recyclerView != null) {
- RecyclerViewRequireScrollHelper.requireScroll(navigationBar, recyclerView);
- } else {
- Log.e(TAG, "Both suw_layout_navigation_bar and suw_recycler_view must exist in"
- + " the template to require scrolling.");
- }
+ return (RecyclerItemAdapter) super.getAdapter();
}
}
diff --git a/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java b/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java
new file mode 100644
index 0000000..5159bae
--- /dev/null
+++ b/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.setupwizardlib;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.setupwizardlib.items.ItemGroup;
+import com.android.setupwizardlib.items.ItemInflater;
+import com.android.setupwizardlib.items.RecyclerItemAdapter;
+import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper;
+import com.android.setupwizardlib.util.RecyclerViewRequireScrollHelper;
+import com.android.setupwizardlib.view.HeaderRecyclerView;
+import com.android.setupwizardlib.view.NavigationBar;
+
+/**
+ * A setup wizard layout for use with {@link android.support.v7.widget.RecyclerView}.
+ * {@code android:entries} can also be used to specify an
+ * {@link com.android.setupwizardlib.items.ItemHierarchy} to be used with this layout in XML.
+ *
+ * @see SetupWizardItemsLayout
+ */
+public class SetupWizardRecyclerLayout extends SetupWizardLayout {
+
+ private static final String TAG = "RecyclerLayout";
+
+ private RecyclerView.Adapter mAdapter;
+ private RecyclerView mRecyclerView;
+ private View mHeader;
+
+ private DividerItemDecoration mDividerDecoration;
+ private Drawable mDefaultDivider;
+ private Drawable mDivider;
+ private int mDividerInset;
+
+ public SetupWizardRecyclerLayout(Context context) {
+ this(context, 0, 0);
+ }
+
+ public SetupWizardRecyclerLayout(Context context, int template, int containerId) {
+ super(context, template, containerId);
+ init(context, null, 0);
+ }
+
+ public SetupWizardRecyclerLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs, 0);
+ }
+
+ public SetupWizardRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.SuwSetupWizardRecyclerItemsLayout, defStyleAttr, 0);
+ final int xml = a.getResourceId(
+ R.styleable.SuwSetupWizardRecyclerItemsLayout_android_entries, 0);
+ if (xml != 0) {
+ final ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(xml);
+ mAdapter = new RecyclerItemAdapter(inflated);
+ mAdapter.setHasStableIds(a.getBoolean(
+ R.styleable.SuwSetupWizardRecyclerItemsLayout_suwHasStableIds, false));
+ setAdapter(mAdapter);
+ }
+ int dividerInset = a.getDimensionPixelSize(
+ R.styleable.SuwSetupWizardRecyclerItemsLayout_suwDividerInset, 0);
+ if (dividerInset == 0) {
+ dividerInset = getResources()
+ .getDimensionPixelSize(R.dimen.suw_items_icon_divider_inset);
+ }
+ setDividerInset(dividerInset);
+ a.recycle();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (mDivider == null) {
+ // Update divider in case layout direction has just been resolved
+ updateDivider();
+ }
+ }
+
+ public RecyclerView.Adapter getAdapter() {
+ return mAdapter;
+ }
+
+ public void setAdapter(RecyclerView.Adapter adapter) {
+ mAdapter = adapter;
+ getRecyclerView().setAdapter(adapter);
+ }
+
+ public RecyclerView getRecyclerView() {
+ return mRecyclerView;
+ }
+
+ @Override
+ protected ViewGroup findContainer(int containerId) {
+ if (containerId == 0) {
+ containerId = R.id.suw_recycler_view;
+ }
+ return super.findContainer(containerId);
+ }
+
+ @Override
+ protected void onTemplateInflated() {
+ initRecyclerView((RecyclerView) findViewById(R.id.suw_recycler_view));
+ }
+
+ protected void initRecyclerView(RecyclerView recyclerView) {
+ mRecyclerView = recyclerView;
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ if (mRecyclerView instanceof HeaderRecyclerView) {
+ mHeader = ((HeaderRecyclerView) mRecyclerView).getHeader();
+ }
+ mDividerDecoration = DividerItemDecoration.getDefault(getContext());
+ mRecyclerView.addItemDecoration(mDividerDecoration);
+ }
+
+ @Override
+ protected View onInflateTemplate(LayoutInflater inflater, int template) {
+ if (template == 0) {
+ template = R.layout.suw_recycler_template;
+ }
+ return super.onInflateTemplate(inflater, template);
+ }
+
+ @Override
+ protected View findManagedViewById(int id) {
+ if (mHeader != null) {
+ final View view = mHeader.findViewById(id);
+ if (view != null) {
+ return view;
+ }
+ }
+ return super.findViewById(id);
+ }
+
+ @Override
+ public void requireScrollToBottom() {
+ final NavigationBar navigationBar = getNavigationBar();
+ final RecyclerView recyclerView = getRecyclerView();
+ if (navigationBar != null && recyclerView != null) {
+ RecyclerViewRequireScrollHelper.requireScroll(navigationBar, recyclerView);
+ } else {
+ Log.e(TAG, "Both suw_layout_navigation_bar and suw_recycler_view must exist in"
+ + " the template to require scrolling.");
+ }
+ }
+
+ /**
+ * Sets the start inset of the divider. This will use the default divider drawable set in the
+ * theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
+ *
+ * @param inset The number of pixels to inset on the "start" side of the list divider. Typically
+ * this will be either {@code @dimen/suw_items_icon_divider_inset} or
+ * {@code @dimen/suw_items_text_divider_inset}.
+ */
+ public void setDividerInset(int inset) {
+ mDividerInset = inset;
+ updateDivider();
+ }
+
+ public int getDividerInset() {
+ return mDividerInset;
+ }
+
+ private void updateDivider() {
+ boolean shouldUpdate = true;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ shouldUpdate = isLayoutDirectionResolved();
+ }
+ if (shouldUpdate) {
+ if (mDefaultDivider == null) {
+ mDefaultDivider = mDividerDecoration.getDivider();
+ }
+ mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable(mDefaultDivider,
+ mDividerInset /* start */, 0 /* top */, 0 /* end */, 0 /* bottom */, this);
+ mDividerDecoration.setDivider(mDivider);
+ }
+ }
+
+ public Drawable getDivider() {
+ return mDivider;
+ }
+}
diff --git a/library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java b/library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java
index ae39cc0..632fee2 100644
--- a/library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java
+++ b/library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java
@@ -17,7 +17,10 @@
package com.android.setupwizardlib.items;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.support.v7.widget.RecyclerView;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -32,9 +35,7 @@ import com.android.setupwizardlib.R;
public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder>
implements ItemHierarchy.Observer {
- private static final int[] SELECTABLE_ITEM_BACKGROUND = new int[] {
- R.attr.selectableItemBackground
- };
+ private static final String TAG = "RecyclerItemAdapter";
public interface OnItemSelectedListener {
void onItemSelected(IItem item);
@@ -75,8 +76,26 @@ public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder>
final ItemViewHolder viewHolder = new ItemViewHolder(view);
final TypedArray typedArray = parent.getContext()
- .obtainStyledAttributes(SELECTABLE_ITEM_BACKGROUND);
- view.setBackgroundDrawable(typedArray.getDrawable(0));
+ .obtainStyledAttributes(R.styleable.SuwRecyclerItemAdapter);
+ Drawable selectableItemBackground = typedArray.getDrawable(
+ R.styleable.SuwRecyclerItemAdapter_android_selectableItemBackground);
+ if (selectableItemBackground == null) {
+ selectableItemBackground = typedArray.getDrawable(
+ R.styleable.SuwRecyclerItemAdapter_selectableItemBackground);
+ }
+
+ final Drawable background = typedArray.getDrawable(
+ R.styleable.SuwRecyclerItemAdapter_android_colorBackground);
+
+ if (selectableItemBackground == null || background == null) {
+ Log.e(TAG, "Cannot resolve required attributes."
+ + " selectableItemBackground=" + selectableItemBackground
+ + " background=" + background);
+ } else {
+ final Drawable[] layers = { background, selectableItemBackground };
+ view.setBackgroundDrawable(new LayerDrawable(layers));
+ }
+
typedArray.recycle();
view.setOnClickListener(new View.OnClickListener() {
diff --git a/library/full-support/src/com/android/setupwizardlib/util/GlifPreferenceDelegate.java b/library/full-support/src/com/android/setupwizardlib/util/GlifPreferenceDelegate.java
index 914dca9..1c16168 100644
--- a/library/full-support/src/com/android/setupwizardlib/util/GlifPreferenceDelegate.java
+++ b/library/full-support/src/com/android/setupwizardlib/util/GlifPreferenceDelegate.java
@@ -36,7 +36,10 @@ import com.android.setupwizardlib.view.HeaderRecyclerView;
* instance and delegate {@code PreferenceFragment#onCreateRecyclerView} to it. Then call
* {@code PreferenceFragment#setDivider} to {@link #getDividerDrawable(android.content.Context)} in
* order to make sure the correct inset is applied to the dividers.
+ *
+ * @deprecated Use {@link com.android.setupwizardlib.GlifPreferenceLayout}
*/
+@Deprecated
public class GlifPreferenceDelegate {
public static final int[] ATTRS_LIST_DIVIDER = new int[]{ android.R.attr.listDivider };
@@ -64,8 +67,8 @@ public class GlifPreferenceDelegate {
a.recycle();
final int dividerInset = context.getResources().getDimensionPixelSize(
- mHasIcons ? R.dimen.suw_items_icon_divider_inset
- : R.dimen.suw_items_text_divider_inset);
+ mHasIcons ? R.dimen.suw_items_glif_icon_divider_inset
+ : R.dimen.suw_items_glif_text_divider_inset);
return DrawableLayoutDirectionHelper.createRelativeInsetDrawable(defaultDivider,
dividerInset /* start */, 0 /* top */, 0 /* end */, 0 /* bottom */,
context);
diff --git a/library/full-support/test/res/layout/test_recycler_layout.xml b/library/full-support/test/res/layout/test_recycler_layout.xml
new file mode 100644
index 0000000..8b7602e
--- /dev/null
+++ b/library/full-support/test/res/layout/test_recycler_layout.xml
@@ -0,0 +1,20 @@
+<!--
+ 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.
+-->
+
+<com.android.setupwizardlib.SetupWizardRecyclerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/library/full-support/test/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java b/library/full-support/test/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java
index 044cad6..a06f6f7 100644
--- a/library/full-support/test/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java
+++ b/library/full-support/test/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java
@@ -20,6 +20,9 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
@@ -49,17 +52,94 @@ public class DividerItemDecorationTest extends AndroidTestCase {
}
@SmallTest
- public void testShouldDrawDividerBelow() {
- // Set up the canvas to be drawn
- Bitmap bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444);
- Canvas canvas = new Canvas(bitmap);
-
+ public void testShouldDrawDividerBelowWithEitherCondition() {
// Set up the item decoration, with 1px red divider line
final DividerItemDecoration decoration = new DividerItemDecoration();
Drawable divider = new ColorDrawable(Color.RED);
decoration.setDivider(divider);
decoration.setDividerHeight(1);
+ Bitmap bitmap = drawDecoration(decoration, true, true);
+
+ // Draw the expected result on a bitmap
+ Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444);
+ Canvas expectedCanvas = new Canvas(expectedBitmap);
+ Paint paint = new Paint();
+ paint.setColor(Color.RED);
+ expectedCanvas.drawRect(0, 5, 20, 6, paint);
+ expectedCanvas.drawRect(0, 10, 20, 11, paint);
+ expectedCanvas.drawRect(0, 15, 20, 16, paint);
+ // Compare the two bitmaps
+ assertBitmapEquals(expectedBitmap, bitmap);
+
+ bitmap.recycle();
+ bitmap = drawDecoration(decoration, false, true);
+ // should still be the same.
+ assertBitmapEquals(expectedBitmap, bitmap);
+
+ bitmap.recycle();
+ bitmap = drawDecoration(decoration, true, false);
+ // last item should not have a divider below it now
+ paint.setColor(Color.TRANSPARENT);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ expectedCanvas.drawRect(0, 15, 20, 16, paint);
+ assertBitmapEquals(expectedBitmap, bitmap);
+
+ bitmap.recycle();
+ bitmap = drawDecoration(decoration, false, false);
+ // everything should be transparent now
+ expectedCanvas.drawRect(0, 5, 20, 6, paint);
+ expectedCanvas.drawRect(0, 10, 20, 11, paint);
+ assertBitmapEquals(expectedBitmap, bitmap);
+
+ }
+
+ @SmallTest
+ public void testShouldDrawDividerBelowWithBothCondition() {
+ // Set up the item decoration, with 1px green divider line
+ final DividerItemDecoration decoration = new DividerItemDecoration();
+ Drawable divider = new ColorDrawable(Color.GREEN);
+ decoration.setDivider(divider);
+ decoration.setDividerHeight(1);
+ decoration.setDividerCondition(DividerItemDecoration.DIVIDER_CONDITION_BOTH);
+
+ Bitmap bitmap = drawDecoration(decoration, true, true);
+ Paint paint = new Paint();
+ paint.setColor(Color.GREEN);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+ Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444);
+ Canvas expectedCanvas = new Canvas(expectedBitmap);
+ expectedCanvas.drawRect(0, 5, 20, 6, paint);
+ expectedCanvas.drawRect(0, 10, 20, 11, paint);
+ expectedCanvas.drawRect(0, 15, 20, 16, paint);
+ // Should have all the dividers
+ assertBitmapEquals(expectedBitmap, bitmap);
+
+ bitmap.recycle();
+ bitmap = drawDecoration(decoration, false, true);
+ paint.setColor(Color.TRANSPARENT);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ expectedCanvas.drawRect(0, 5, 20, 6, paint);
+ expectedCanvas.drawRect(0, 10, 20, 11, paint);
+ assertBitmapEquals(expectedBitmap, bitmap);
+
+ bitmap.recycle();
+ bitmap = drawDecoration(decoration, true, false);
+ // nothing should be drawn now.
+ expectedCanvas.drawRect(0, 15, 20, 16, paint);
+ assertBitmapEquals(expectedBitmap, bitmap);
+
+ bitmap.recycle();
+ bitmap = drawDecoration(decoration, false, false);
+ assertBitmapEquals(expectedBitmap, bitmap);
+ }
+
+ private Bitmap drawDecoration(DividerItemDecoration decoration, final boolean allowDividerAbove,
+ final boolean allowDividerBelow) {
+ // Set up the canvas to be drawn
+ Bitmap bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444);
+ Canvas canvas = new Canvas(bitmap);
+
// Set up recycler view with vertical linear layout manager
RecyclerView testRecyclerView = new RecyclerView(getContext());
testRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -71,8 +151,7 @@ public class DividerItemDecorationTest extends AndroidTestCase {
final View itemView = new View(getContext());
itemView.setMinimumWidth(20);
itemView.setMinimumHeight(5);
- return new RecyclerView.ViewHolder(itemView) {
- };
+ return ViewHolder.createInstance(itemView, allowDividerAbove, allowDividerBelow);
}
@Override
@@ -87,17 +166,7 @@ public class DividerItemDecorationTest extends AndroidTestCase {
testRecyclerView.layout(0, 0, 20, 20);
decoration.onDraw(canvas, testRecyclerView, null);
-
- // Draw the expected result on a bitmap
- Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444);
- Canvas expectedCanvas = new Canvas(expectedBitmap);
- Paint paint = new Paint();
- paint.setColor(Color.RED);
- expectedCanvas.drawRect(0, 5, 20, 6, paint);
- expectedCanvas.drawRect(0, 10, 20, 11, paint);
-
- // Compare the two bitmaps
- assertBitmapEquals(expectedBitmap, bitmap);
+ return bitmap;
}
private void assertBitmapEquals(Bitmap expected, Bitmap actual) {
@@ -110,4 +179,32 @@ public class DividerItemDecorationTest extends AndroidTestCase {
}
}
}
+
+ private static class ViewHolder extends RecyclerView.ViewHolder
+ implements DividerItemDecoration.DividedViewHolder {
+
+ private boolean mAllowDividerAbove;
+ private boolean mAllowDividerBelow;
+
+ public static ViewHolder createInstance(View itemView, boolean allowDividerAbove,
+ boolean allowDividerBelow) {
+ return new ViewHolder(itemView, allowDividerAbove, allowDividerBelow);
+ }
+
+ private ViewHolder(View itemView, boolean allowDividerAbove, boolean allowDividerBelow) {
+ super(itemView);
+ mAllowDividerAbove = allowDividerAbove;
+ mAllowDividerBelow = allowDividerBelow;
+ }
+
+ @Override
+ public boolean isDividerAllowedAbove() {
+ return mAllowDividerAbove;
+ }
+
+ @Override
+ public boolean isDividerAllowedBelow() {
+ return mAllowDividerBelow;
+ }
+ }
}
diff --git a/library/full-support/test/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java b/library/full-support/test/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java
new file mode 100644
index 0000000..8ec9b0b
--- /dev/null
+++ b/library/full-support/test/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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 android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Build;
+import android.support.v7.widget.RecyclerView;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.setupwizardlib.GlifPreferenceLayout;
+
+public class GlifPreferenceLayoutTest extends InstrumentationTestCase {
+
+ private Context mContext;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = new ContextThemeWrapper(getInstrumentation().getContext(),
+ R.style.SuwThemeGlif_Light);
+ }
+
+ @SmallTest
+ public void testDefaultTemplate() {
+ GlifPreferenceLayout layout = new TestLayout(mContext);
+ assertPreferenceTemplateInflated(layout);
+ }
+
+ @SmallTest
+ public void testGetRecyclerView() {
+ GlifPreferenceLayout layout = new TestLayout(mContext);
+ assertPreferenceTemplateInflated(layout);
+ assertNotNull("getRecyclerView should not be null", layout.getRecyclerView());
+ }
+
+ @SmallTest
+ public void testOnCreateRecyclerView() {
+ GlifPreferenceLayout layout = new TestLayout(mContext);
+ assertPreferenceTemplateInflated(layout);
+ final RecyclerView recyclerView = layout.onCreateRecyclerView(LayoutInflater.from(mContext),
+ layout, null /* savedInstanceState */);
+ assertNotNull("RecyclerView created should not be null", recyclerView);
+ }
+
+ @SmallTest
+ public void testDividerInset() {
+ GlifPreferenceLayout layout = new TestLayout(mContext);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+ }
+ assertPreferenceTemplateInflated(layout);
+
+ layout.addView(layout.onCreateRecyclerView(LayoutInflater.from(mContext), layout,
+ null /* savedInstanceState */));
+
+ layout.setDividerInset(10);
+ assertEquals("Divider inset should be 10", 10, layout.getDividerInset());
+
+ final Drawable divider = layout.getDivider();
+ assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable);
+ }
+
+ private void assertPreferenceTemplateInflated(GlifPreferenceLayout layout) {
+ View contentContainer = layout.findViewById(R.id.suw_layout_content);
+ assertTrue("@id/suw_layout_content should be a ViewGroup",
+ contentContainer instanceof ViewGroup);
+
+ if (layout instanceof TestLayout) {
+ assertNotNull("Header text view should not be null",
+ ((TestLayout) layout).findManagedViewById(R.id.suw_layout_title));
+ assertNotNull("Icon view should not be null",
+ ((TestLayout) layout).findManagedViewById(R.id.suw_layout_icon));
+ }
+ }
+
+ // Make some methods public for testing
+ public static class TestLayout extends GlifPreferenceLayout {
+
+ public TestLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public View findManagedViewById(int id) {
+ return super.findManagedViewById(id);
+ }
+ }
+}
diff --git a/library/full-support/test/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java b/library/full-support/test/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java
index d51a999..8a908ec 100644
--- a/library/full-support/test/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java
+++ b/library/full-support/test/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java
@@ -27,11 +27,8 @@ import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.android.setupwizardlib.GlifRecyclerLayout;
-import com.android.setupwizardlib.view.HeaderRecyclerView;
public class GlifRecyclerLayoutTest extends InstrumentationTestCase {
@@ -115,8 +112,9 @@ public class GlifRecyclerLayoutTest extends InstrumentationTestCase {
if (layout instanceof TestLayout) {
assertNotNull("Header text view should not be null",
- ((TestLayout) layout).getHeaderTextView());
- assertNotNull("Icon view should not be null", ((TestLayout) layout).getIconView());
+ ((TestLayout) layout).findManagedViewById(R.id.suw_layout_title));
+ assertNotNull("Icon view should not be null",
+ ((TestLayout) layout).findManagedViewById(R.id.suw_layout_icon));
}
}
@@ -128,13 +126,8 @@ public class GlifRecyclerLayoutTest extends InstrumentationTestCase {
}
@Override
- public TextView getHeaderTextView() {
- return super.getHeaderTextView();
- }
-
- @Override
- public ImageView getIconView() {
- return super.getIconView();
+ public View findManagedViewById(int id) {
+ return super.findManagedViewById(id);
}
}
}
diff --git a/library/full-support/test/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java b/library/full-support/test/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java
new file mode 100644
index 0000000..f2b55f1
--- /dev/null
+++ b/library/full-support/test/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.test;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Build;
+import android.support.v7.widget.RecyclerView;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.setupwizardlib.SetupWizardPreferenceLayout;
+
+public class SetupWizardPreferenceLayoutTest extends InstrumentationTestCase {
+
+ private Context mContext;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = new ContextThemeWrapper(getInstrumentation().getContext(),
+ R.style.SuwThemeMaterial_Light);
+ }
+
+ @SmallTest
+ public void testDefaultTemplate() {
+ SetupWizardPreferenceLayout layout = new TestLayout(mContext);
+ assertPreferenceTemplateInflated(layout);
+ }
+
+ @SmallTest
+ public void testGetRecyclerView() {
+ SetupWizardPreferenceLayout layout = new TestLayout(mContext);
+ assertPreferenceTemplateInflated(layout);
+ assertNotNull("getRecyclerView should not be null", layout.getRecyclerView());
+ }
+
+ @SmallTest
+ public void testOnCreateRecyclerView() {
+ SetupWizardPreferenceLayout layout = new TestLayout(mContext);
+ assertPreferenceTemplateInflated(layout);
+ final RecyclerView recyclerView = layout.onCreateRecyclerView(LayoutInflater.from(mContext),
+ layout, null /* savedInstanceState */);
+ assertNotNull("RecyclerView created should not be null", recyclerView);
+ }
+
+ @SmallTest
+ public void testDividerInset() {
+ SetupWizardPreferenceLayout layout = new TestLayout(mContext);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+ }
+ assertPreferenceTemplateInflated(layout);
+
+ layout.addView(layout.onCreateRecyclerView(LayoutInflater.from(mContext), layout,
+ null /* savedInstanceState */));
+
+ layout.setDividerInset(10);
+ assertEquals("Divider inset should be 10", 10, layout.getDividerInset());
+
+ final Drawable divider = layout.getDivider();
+ assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable);
+ }
+
+ private void assertPreferenceTemplateInflated(SetupWizardPreferenceLayout layout) {
+ View contentContainer = layout.findViewById(R.id.suw_layout_content);
+ assertTrue("@id/suw_layout_content should be a ViewGroup",
+ contentContainer instanceof ViewGroup);
+
+ if (layout instanceof TestLayout) {
+ assertNotNull("Header text view should not be null",
+ ((TestLayout) layout).findManagedViewById(R.id.suw_layout_title));
+ assertNotNull("Decoration view should not be null",
+ ((TestLayout) layout).findManagedViewById(R.id.suw_layout_decor));
+ }
+ }
+
+ // Make some methods public for testing
+ public static class TestLayout extends SetupWizardPreferenceLayout {
+
+ public TestLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public View findManagedViewById(int id) {
+ return super.findManagedViewById(id);
+ }
+ }
+}
diff --git a/library/full-support/test/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java b/library/full-support/test/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java
new file mode 100644
index 0000000..2dbce11
--- /dev/null
+++ b/library/full-support/test/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.test;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Build;
+import android.support.v7.widget.RecyclerView;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.setupwizardlib.SetupWizardRecyclerLayout;
+
+public class SetupWizardRecyclerLayoutTest extends InstrumentationTestCase {
+
+ private Context mContext;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = new ContextThemeWrapper(getInstrumentation().getContext(),
+ R.style.SuwThemeMaterial_Light);
+ }
+
+ @SmallTest
+ public void testDefaultTemplate() {
+ SetupWizardRecyclerLayout layout = new TestLayout(mContext);
+ assertRecyclerTemplateInflated(layout);
+ }
+
+ @SmallTest
+ public void testInflateFromXml() {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ SetupWizardRecyclerLayout layout = (SetupWizardRecyclerLayout)
+ inflater.inflate(R.layout.test_recycler_layout, null);
+ assertRecyclerTemplateInflated(layout);
+ }
+
+ @SmallTest
+ public void testGetRecyclerView() {
+ SetupWizardRecyclerLayout layout = new TestLayout(mContext);
+ assertRecyclerTemplateInflated(layout);
+ assertNotNull("getRecyclerView should not be null", layout.getRecyclerView());
+ }
+
+ @SmallTest
+ public void testAdapter() {
+ SetupWizardRecyclerLayout layout = new TestLayout(mContext);
+ assertRecyclerTemplateInflated(layout);
+
+ final RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
+ return new RecyclerView.ViewHolder(new View(parent.getContext())) {};
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
+ }
+
+ @Override
+ public int getItemCount() {
+ return 0;
+ }
+ };
+ layout.setAdapter(adapter);
+
+ final RecyclerView.Adapter gotAdapter = layout.getAdapter();
+ // Note: The wrapped adapter should be returned, not the HeaderAdapter.
+ assertSame("Adapter got from SetupWizardLayout should be same as set",
+ adapter, gotAdapter);
+ }
+
+ @SmallTest
+ public void testDividerInset() {
+ SetupWizardRecyclerLayout layout = new TestLayout(mContext);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+ }
+ assertRecyclerTemplateInflated(layout);
+
+ layout.setDividerInset(10);
+ assertEquals("Divider inset should be 10", 10, layout.getDividerInset());
+
+ final Drawable divider = layout.getDivider();
+ assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable);
+ }
+
+ private void assertRecyclerTemplateInflated(SetupWizardRecyclerLayout layout) {
+ View recyclerView = layout.findViewById(R.id.suw_recycler_view);
+ assertTrue("@id/suw_recycler_view should be a RecyclerView",
+ recyclerView instanceof RecyclerView);
+
+ if (layout instanceof TestLayout) {
+ assertNotNull("Header text view should not be null",
+ ((TestLayout) layout).findManagedViewById(R.id.suw_layout_title));
+ assertNotNull("Decoration view should not be null",
+ ((TestLayout) layout).findManagedViewById(R.id.suw_layout_decor));
+ }
+ }
+
+ // Make some methods public for testing
+ public static class TestLayout extends SetupWizardRecyclerLayout {
+
+ public TestLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public View findManagedViewById(int id) {
+ return super.findManagedViewById(id);
+ }
+
+ }
+}
diff --git a/library/main/res/layout/suw_items_button_bar.xml b/library/main/res/layout/suw_items_button_bar.xml
new file mode 100644
index 0000000..48414a2
--- /dev/null
+++ b/library/main/res/layout/suw_items_button_bar.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/SuwItemContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:orientation="horizontal" />
diff --git a/library/main/res/layout/suw_items_description.xml b/library/main/res/layout/suw_items_description.xml
index 36c8088..8712be3 100644
--- a/library/main/res/layout/suw_items_description.xml
+++ b/library/main/res/layout/suw_items_description.xml
@@ -21,7 +21,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:paddingTop="@dimen/suw_description_margin_top">
+ android:paddingTop="@dimen/suw_description_margin_top"
+ android:paddingBottom="@dimen/suw_description_margin_bottom_lists">
<FrameLayout
android:id="@+id/suw_items_icon_container"
diff --git a/library/main/res/layout/suw_list_header.xml b/library/main/res/layout/suw_list_header.xml
index b0717bc..3b21082 100644
--- a/library/main/res/layout/suw_list_header.xml
+++ b/library/main/res/layout/suw_list_header.xml
@@ -21,7 +21,9 @@
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="vertical"
- android:tag="stickyContainer">
+ android:elevation="@dimen/suw_header_elevation_hack"
+ android:tag="stickyContainer"
+ tools:ignore="UnusedAttribute">
<com.android.setupwizardlib.view.Illustration
android:id="@+id/suw_layout_decor"
diff --git a/library/main/res/layout/suw_no_scroll_template_card.xml b/library/main/res/layout/suw_no_scroll_template_card.xml
index e731497..f6c2cab 100644
--- a/library/main/res/layout/suw_no_scroll_template_card.xml
+++ b/library/main/res/layout/suw_no_scroll_template_card.xml
@@ -55,14 +55,7 @@
<FrameLayout
android:id="@+id/suw_layout_content"
android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <!-- Temporary solution to work with PreferenceFragment v14 -->
- <FrameLayout android:id="@+id/list_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- </FrameLayout>
+ android:layout_height="match_parent" />
<include layout="@layout/suw_progress_bar_stub" />
diff --git a/library/main/res/layout/suw_no_scroll_template_card_wide.xml b/library/main/res/layout/suw_no_scroll_template_card_wide.xml
index b010c31..a09b9b6 100644
--- a/library/main/res/layout/suw_no_scroll_template_card_wide.xml
+++ b/library/main/res/layout/suw_no_scroll_template_card_wide.xml
@@ -56,14 +56,7 @@
<FrameLayout
android:id="@+id/suw_layout_content"
android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <!-- Temporary solution to work with PreferenceFragment v14 -->
- <FrameLayout android:id="@+id/list_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- </FrameLayout>
+ android:layout_height="match_parent" />
<include layout="@layout/suw_progress_bar_stub" />
diff --git a/library/main/res/layout/suw_no_scroll_template_header.xml b/library/main/res/layout/suw_no_scroll_template_header.xml
index e17d6b9..413b329 100644
--- a/library/main/res/layout/suw_no_scroll_template_header.xml
+++ b/library/main/res/layout/suw_no_scroll_template_header.xml
@@ -56,14 +56,7 @@
android:id="@+id/suw_layout_content"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1">
-
- <!-- Temporary solution to work with PreferenceFragment v14 -->
- <FrameLayout android:id="@+id/list_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- </FrameLayout>
+ android:layout_weight="1" />
</LinearLayout>
diff --git a/library/main/res/layout/suw_no_scroll_template_header_collapsed.xml b/library/main/res/layout/suw_no_scroll_template_header_collapsed.xml
index 16a93d4..f8af7a7 100644
--- a/library/main/res/layout/suw_no_scroll_template_header_collapsed.xml
+++ b/library/main/res/layout/suw_no_scroll_template_header_collapsed.xml
@@ -45,14 +45,7 @@
<FrameLayout android:id="@+id/suw_layout_content"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1">
-
- <!-- Temporary solution to work with PreferenceFragment v14 -->
- <FrameLayout android:id="@+id/list_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- </FrameLayout>
+ android:layout_weight="1" />
<com.android.setupwizardlib.view.NavigationBar
android:id="@+id/suw_layout_navigation_bar"
diff --git a/library/main/res/values-be-rBY/strings.xml b/library/main/res/values-be-rBY/strings.xml
new file mode 100644
index 0000000..1753d86
--- /dev/null
+++ b/library/main/res/values-be-rBY/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="suw_next_button_label" msgid="7269625133873553978">"Далей"</string>
+ <string name="suw_back_button_label" msgid="1460929053642711025">"Назад"</string>
+ <string name="suw_more_button_label" msgid="7769076059705546563">"Яшчэ"</string>
+</resources>
diff --git a/library/main/res/values-bs-rBA/strings.xml b/library/main/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..2490af5
--- /dev/null
+++ b/library/main/res/values-bs-rBA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="suw_next_button_label" msgid="7269625133873553978">"Naprijed"</string>
+ <string name="suw_back_button_label" msgid="1460929053642711025">"Nazad"</string>
+ <string name="suw_more_button_label" msgid="7769076059705546563">"Još"</string>
+</resources>
diff --git a/library/main/res/values-v21/styles.xml b/library/main/res/values-v21/styles.xml
index 50faf1d..88c39d4 100644
--- a/library/main/res/values-v21/styles.xml
+++ b/library/main/res/values-v21/styles.xml
@@ -30,6 +30,40 @@
<item name="android:textColor">@android:color/white</item>
</style>
+ <!-- GLIF Card layout (for tablets) -->
+
+ <style name="SuwGlifCardBackground">
+ <item name="android:background">?android:attr/colorPrimary</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">?android:attr/listPreferredItemPaddingEnd</item>
+ <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+ <item name="android:paddingTop">@dimen/suw_items_padding_vertical</item>
+ </style>
+
+ <style name="SuwItemTitle">
+ <item name="android:textAppearance">?android:attr/textAppearanceListItem</item>
+ </style>
+
+ <style name="SuwItemSummary">
+ <item name="android:textAppearance">?android:attr/textAppearanceListItemSmall</item>
+ </style>
+
+ <!-- Button styles -->
+
+ <style name="SuwButtonItem" />
+
+ <style name="SuwButtonItem.Colored">
+ <item name="android:buttonStyle">@android:style/Widget.Material.Button</item>
+ <item name="android:colorButtonNormal">?android:attr/colorAccent</item>
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ </style>
+
<!-- Navigation bar styles -->
<style name="SuwNavBarButtonStyle" parent="@android:style/Widget.Material.Button.Borderless">
diff --git a/library/main/res/values/attrs.xml b/library/main/res/values/attrs.xml
index 9a88a1b..8501e94 100644
--- a/library/main/res/values/attrs.xml
+++ b/library/main/res/values/attrs.xml
@@ -22,6 +22,10 @@
<attr name="suwMarginSides" format="dimension|reference" />
<attr name="suwCardBackground" format="color|reference" />
+ <attr name="suwDividerCondition">
+ <enum name="either" value="0" />
+ <enum name="both" value="1" />
+ </attr>
<attr name="suwListItemIconColor" format="color" />
<attr name="suwNavBarBackgroundColor" format="color" />
<attr name="suwNavBarButtonBackground" format="color|reference" />
@@ -77,6 +81,10 @@
<attr name="suwContainer" format="reference" />
</declare-styleable>
+ <declare-styleable name="SuwSetupWizardListLayout">
+ <attr name="suwDividerInset" />
+ </declare-styleable>
+
<declare-styleable name="SuwSetupWizardItemsLayout">
<attr name="android:entries" />
</declare-styleable>
@@ -94,4 +102,17 @@
<attr name="android:visible" />
</declare-styleable>
+ <declare-styleable name="SuwDividerItemDecoration">
+ <attr name="android:listDivider" />
+ <attr name="android:dividerHeight" />
+ <attr name="suwDividerCondition" />
+ </declare-styleable>
+
+ <declare-styleable name="SuwButtonItem">
+ <attr name="android:buttonStyle" />
+ <attr name="android:enabled" />
+ <attr name="android:text" />
+ <attr name="android:theme" />
+ </declare-styleable>
+
</resources>
diff --git a/library/main/res/values/colors.xml b/library/main/res/values/colors.xml
index a4470d1..240d0ac 100644
--- a/library/main/res/values/colors.xml
+++ b/library/main/res/values/colors.xml
@@ -33,4 +33,8 @@
<color name="suw_navbar_bg_dark">#ff21272b</color>
<color name="suw_navbar_bg_light">#ffe4e7e9</color>
+ <!-- GLIF colors -->
+ <color name="suw_color_accent_glif_dark">#ff4285f4</color>
+ <color name="suw_color_accent_glif_light">#ff4285f4</color>
+
</resources>
diff --git a/library/main/res/values/dimens.xml b/library/main/res/values/dimens.xml
index d2302e6..55b9a45 100644
--- a/library/main/res/values/dimens.xml
+++ b/library/main/res/values/dimens.xml
@@ -35,6 +35,7 @@
<dimen name="suw_description_margin_top">24dp</dimen>
<dimen name="suw_description_margin_bottom">12dp</dimen>
+ <dimen name="suw_description_margin_bottom_lists">24dp</dimen>
<dimen name="suw_description_line_spacing_extra">4sp</dimen>
<dimen name="suw_description_text_size">16sp</dimen>
@@ -68,6 +69,8 @@
<!-- Header layout (for phones) -->
<dimen name="suw_title_area_elevation">3dp</dimen>
+ <!-- Hack to force the header (and its shadow) to be drawn on top of the list contents -->
+ <dimen name="suw_header_elevation_hack">1dp</dimen>
<dimen name="suw_header_title_size">24sp</dimen>
<dimen name="suw_header_title_margin_bottom">16dp</dimen>
@@ -89,8 +92,11 @@
<dimen name="suw_items_padding_vertical">15dp</dimen>
<dimen name="suw_items_verbose_padding_vertical">20dp</dimen>
- <dimen name="suw_items_icon_divider_inset">72dp</dimen>
- <dimen name="suw_items_text_divider_inset">24dp</dimen>
+ <dimen name="suw_items_icon_divider_inset">88dp</dimen>
+ <dimen name="suw_items_text_divider_inset">40dp</dimen>
+
+ <dimen name="suw_items_glif_icon_divider_inset">72dp</dimen>
+ <dimen name="suw_items_glif_text_divider_inset">24dp</dimen>
<!-- Extra padding in the bottom to compensate for difference between descent and (top) internal leading -->
<dimen name="suw_items_padding_bottom_extra">1dp</dimen>
diff --git a/library/main/res/values/styles.xml b/library/main/res/values/styles.xml
index acae7c8..7c845a2 100644
--- a/library/main/res/values/styles.xml
+++ b/library/main/res/values/styles.xml
@@ -132,6 +132,11 @@
<item name="android:textAppearance">@style/TextAppearance.SuwGlifBody</item>
</style>
+ <style name="TextAppearance.SuwItemSummary" parent="android:TextAppearance">
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
<!-- GLIF layout -->
<style name="SuwGlifHeaderTitle" parent="SuwBaseHeaderTitle">
diff --git a/library/main/src/com/android/setupwizardlib/GlifLayout.java b/library/main/src/com/android/setupwizardlib/GlifLayout.java
index bf06bf5..c3202ad 100644
--- a/library/main/src/com/android/setupwizardlib/GlifLayout.java
+++ b/library/main/src/com/android/setupwizardlib/GlifLayout.java
@@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
+import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -121,7 +122,14 @@ public class GlifLayout extends TemplateLayout {
if (template == 0) {
template = R.layout.suw_glif_template;
}
- return super.onInflateTemplate(inflater, template);
+ try {
+ return super.onInflateTemplate(inflater, template);
+ } catch (RuntimeException e) {
+ // Versions before M throws RuntimeException for unsuccessful attribute resolution
+ // Versions M+ will throw an InflateException (which extends from RuntimeException)
+ throw new InflateException("Unable to inflate layout. Are you using "
+ + "@style/SuwThemeGlif (or its descendant) as your theme?", e);
+ }
}
@Override
@@ -132,13 +140,22 @@ public class GlifLayout extends TemplateLayout {
return super.findContainer(containerId);
}
+ /**
+ * Same as {@link android.view.View#findViewById(int)}, but may include views that are managed
+ * by this view but not currently added to the view hierarchy. e.g. recycler view or list view
+ * headers that are not currently shown.
+ */
+ protected View findManagedViewById(int id) {
+ return findViewById(id);
+ }
+
public ScrollView getScrollView() {
- final View view = findViewById(R.id.suw_scroll_view);
+ final View view = findManagedViewById(R.id.suw_scroll_view);
return view instanceof ScrollView ? (ScrollView) view : null;
}
- protected TextView getHeaderTextView() {
- return (TextView) findViewById(R.id.suw_layout_title);
+ public TextView getHeaderTextView() {
+ return (TextView) findManagedViewById(R.id.suw_layout_title);
}
public void setHeaderText(int title) {
@@ -182,7 +199,7 @@ public class GlifLayout extends TemplateLayout {
}
protected ImageView getIconView() {
- return (ImageView) findViewById(R.id.suw_layout_icon);
+ return (ImageView) findManagedViewById(R.id.suw_layout_icon);
}
public void setPrimaryColor(ColorStateList color) {
@@ -198,7 +215,7 @@ public class GlifLayout extends TemplateLayout {
private void setGlifPatternColor(ColorStateList color) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
- final View patternBg = findViewById(R.id.suw_pattern_bg);
+ final View patternBg = findManagedViewById(R.id.suw_pattern_bg);
if (patternBg != null) {
final GlifPatternDrawable background =
new GlifPatternDrawable(color.getDefaultColor());
@@ -212,18 +229,18 @@ public class GlifLayout extends TemplateLayout {
}
public boolean isProgressBarShown() {
- final View progressBar = findViewById(R.id.suw_layout_progress);
+ final View progressBar = findManagedViewById(R.id.suw_layout_progress);
return progressBar != null && progressBar.getVisibility() == View.VISIBLE;
}
public void setProgressBarShown(boolean shown) {
- final View progressBar = findViewById(R.id.suw_layout_progress);
+ final View progressBar = findManagedViewById(R.id.suw_layout_progress);
if (shown) {
if (progressBar != null) {
progressBar.setVisibility(View.VISIBLE);
} else {
final ViewStub progressBarStub =
- (ViewStub) findViewById(R.id.suw_layout_progress_stub);
+ (ViewStub) findManagedViewById(R.id.suw_layout_progress_stub);
if (progressBarStub != null) {
progressBarStub.inflate();
}
@@ -238,7 +255,7 @@ public class GlifLayout extends TemplateLayout {
private void setProgressBarColor(ColorStateList color) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
- final ProgressBar bar = (ProgressBar) findViewById(R.id.suw_layout_progress);
+ final ProgressBar bar = (ProgressBar) findManagedViewById(R.id.suw_layout_progress);
if (bar != null) {
bar.setIndeterminateTintList(color);
}
diff --git a/library/main/src/com/android/setupwizardlib/GlifListLayout.java b/library/main/src/com/android/setupwizardlib/GlifListLayout.java
index 77065c8..e5f0d42 100644
--- a/library/main/src/com/android/setupwizardlib/GlifListLayout.java
+++ b/library/main/src/com/android/setupwizardlib/GlifListLayout.java
@@ -88,7 +88,7 @@ public class GlifListLayout extends GlifLayout {
a.getDimensionPixelSize(R.styleable.SuwGlifListLayout_suwDividerInset, 0);
if (dividerInset == 0) {
dividerInset = getResources()
- .getDimensionPixelSize(R.dimen.suw_items_icon_divider_inset);
+ .getDimensionPixelSize(R.dimen.suw_items_glif_icon_divider_inset);
}
setDividerInset(dividerInset);
a.recycle();
@@ -145,8 +145,8 @@ public class GlifListLayout extends GlifLayout {
* theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
*
* @param inset The number of pixels to inset on the "start" side of the list divider. Typically
- * this will be either {@code @dimen/suw_items_icon_divider_inset} or
- * {@code @dimen/suw_items_text_divider_inset}.
+ * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or
+ * {@code @dimen/suw_items_glif_text_divider_inset}.
*/
public void setDividerInset(int inset) {
mDividerInset = inset;
diff --git a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java
index 23db40c..3d0efdf 100644
--- a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java
+++ b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java
@@ -19,6 +19,7 @@ package com.android.setupwizardlib;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -58,6 +59,7 @@ public class GlifPatternDrawable extends Drawable {
private Paint mPaint;
private float[] mTempHsv = new float[3];
private Path mTempPath = new Path();
+ private Bitmap mBitmap;
public GlifPatternDrawable(int color) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -66,6 +68,16 @@ public class GlifPatternDrawable extends Drawable {
@Override
public void draw(Canvas canvas) {
+ if (mBitmap == null) {
+ mBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas bitmapCanvas = new Canvas(mBitmap);
+ renderOnCanvas(bitmapCanvas);
+ }
+ canvas.drawBitmap(mBitmap, 0, 0, null);
+ }
+
+ private void renderOnCanvas(Canvas canvas) {
canvas.save();
canvas.clipRect(getBounds());
@@ -189,6 +201,7 @@ public class GlifPatternDrawable extends Drawable {
public void setColor(int color) {
mColor = color;
mPaint.setColor(color);
+ mBitmap = null;
invalidateSelf();
}
diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
index f7512a9..ce4896f 100644
--- a/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
+++ b/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
@@ -19,6 +19,7 @@ package com.android.setupwizardlib;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
@@ -32,10 +33,12 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
+import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
+import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -48,6 +51,8 @@ public class SetupWizardLayout extends TemplateLayout {
private static final String TAG = "SetupWizardLayout";
+ private ColorStateList mProgressBarColor;
+
public SetupWizardLayout(Context context) {
super(context, 0, 0);
init(null, R.attr.suwLayoutTheme);
@@ -147,6 +152,12 @@ public class SetupWizardLayout extends TemplateLayout {
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ Log.w(TAG, "Ignoring restore instance state " + state);
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
final boolean isProgressBarShown = ss.mIsProgressBarShown;
@@ -162,7 +173,14 @@ public class SetupWizardLayout extends TemplateLayout {
if (template == 0) {
template = R.layout.suw_template;
}
- return super.onInflateTemplate(inflater, template);
+ try {
+ return super.onInflateTemplate(inflater, template);
+ } catch (RuntimeException e) {
+ // Versions before M throws RuntimeException for unsuccessful attribute resolution
+ // Versions M+ will throw an InflateException (which extends from RuntimeException)
+ throw new InflateException("Unable to inflate layout. Are you using "
+ + "@style/SuwThemeMaterial (or its descendant) as your theme?", e);
+ }
}
@Override
@@ -174,12 +192,12 @@ public class SetupWizardLayout extends TemplateLayout {
}
public NavigationBar getNavigationBar() {
- final View view = findViewById(R.id.suw_layout_navigation_bar);
+ final View view = findManagedViewById(R.id.suw_layout_navigation_bar);
return view instanceof NavigationBar ? (NavigationBar) view : null;
}
public ScrollView getScrollView() {
- final View view = findViewById(R.id.suw_bottom_scroll_view);
+ final View view = findManagedViewById(R.id.suw_bottom_scroll_view);
return view instanceof ScrollView ? (ScrollView) view : null;
}
@@ -194,10 +212,6 @@ public class SetupWizardLayout extends TemplateLayout {
}
}
- protected TextView getHeaderTextView() {
- return (TextView) findViewById(R.id.suw_layout_title);
- }
-
public void setHeaderText(int title) {
final TextView titleView = getHeaderTextView();
if (titleView != null) {
@@ -217,8 +231,8 @@ public class SetupWizardLayout extends TemplateLayout {
return titleView != null ? titleView.getText() : null;
}
- protected View getDecorationView() {
- return findViewById(R.id.suw_layout_decor);
+ public TextView getHeaderTextView() {
+ return (TextView) findManagedViewById(R.id.suw_layout_title);
}
/**
@@ -230,7 +244,7 @@ public class SetupWizardLayout extends TemplateLayout {
* @param drawable The drawable specifying the illustration.
*/
public void setIllustration(Drawable drawable) {
- final View view = getDecorationView();
+ final View view = findManagedViewById(R.id.suw_layout_decor);
if (view instanceof Illustration) {
final Illustration illustration = (Illustration) view;
illustration.setIllustration(drawable);
@@ -247,7 +261,7 @@ public class SetupWizardLayout extends TemplateLayout {
* @param horizontalTile Resource ID of the horizontally repeating tile for tablet layout.
*/
public void setIllustration(int asset, int horizontalTile) {
- final View view = getDecorationView();
+ final View view = findManagedViewById(R.id.suw_layout_decor);
if (view instanceof Illustration) {
final Illustration illustration = (Illustration) view;
final Drawable illustrationDrawable = getIllustration(asset, horizontalTile);
@@ -256,7 +270,7 @@ public class SetupWizardLayout extends TemplateLayout {
}
private void setIllustration(Drawable asset, Drawable horizontalTile) {
- final View view = getDecorationView();
+ final View view = findManagedViewById(R.id.suw_layout_decor);
if (view instanceof Illustration) {
final Illustration illustration = (Illustration) view;
final Drawable illustrationDrawable = getIllustration(asset, horizontalTile);
@@ -272,7 +286,7 @@ public class SetupWizardLayout extends TemplateLayout {
* @see com.android.setupwizardlib.view.Illustration#setAspectRatio(float)
*/
public void setIllustrationAspectRatio(float aspectRatio) {
- final View view = getDecorationView();
+ final View view = findManagedViewById(R.id.suw_layout_decor);
if (view instanceof Illustration) {
final Illustration illustration = (Illustration) view;
illustration.setAspectRatio(aspectRatio);
@@ -290,7 +304,7 @@ public class SetupWizardLayout extends TemplateLayout {
* @param paddingTop The top padding in pixels.
*/
public void setDecorPaddingTop(int paddingTop) {
- final View view = getDecorationView();
+ final View view = findManagedViewById(R.id.suw_layout_decor);
if (view != null) {
view.setPadding(view.getPaddingLeft(), paddingTop, view.getPaddingRight(),
view.getPaddingBottom());
@@ -302,7 +316,7 @@ public class SetupWizardLayout extends TemplateLayout {
* a bitmap tile and you want it to repeat, use {@link #setBackgroundTile(int)} instead.
*/
public void setLayoutBackground(Drawable background) {
- final View view = getDecorationView();
+ final View view = findManagedViewById(R.id.suw_layout_decor);
if (view != null) {
//noinspection deprecation
view.setBackgroundDrawable(background);
@@ -361,30 +375,74 @@ public class SetupWizardLayout extends TemplateLayout {
}
}
+ /**
+ * Same as {@link android.view.View#findViewById(int)}, but may include views that are managed
+ * by this view but not currently added to the view hierarchy. e.g. recycler view or list view
+ * headers that are not currently shown.
+ */
+ protected View findManagedViewById(int id) {
+ return findViewById(id);
+ }
+
public boolean isProgressBarShown() {
- final View progressBar = findViewById(R.id.suw_layout_progress);
+ final View progressBar = findManagedViewById(R.id.suw_layout_progress);
return progressBar != null && progressBar.getVisibility() == View.VISIBLE;
}
- public void showProgressBar() {
- final View progressBar = findViewById(R.id.suw_layout_progress);
+ /**
+ * Sets whether the progress bar below the header text is shown or not. The progress bar is
+ * a lazily inflated ViewStub, which means the progress bar will not actually be part of the
+ * view hierarchy until the first time this is set to {@code true}.
+ */
+ public void setProgressBarShown(boolean shown) {
+ final View progressBar = findManagedViewById(R.id.suw_layout_progress);
if (progressBar != null) {
- progressBar.setVisibility(View.VISIBLE);
- } else {
- final ViewStub progressBarStub = (ViewStub) findViewById(R.id.suw_layout_progress_stub);
+ progressBar.setVisibility(shown ? View.VISIBLE : View.GONE);
+ } else if (shown) {
+ final ViewStub progressBarStub =
+ (ViewStub) findManagedViewById(R.id.suw_layout_progress_stub);
if (progressBarStub != null) {
progressBarStub.inflate();
}
+ if (mProgressBarColor != null) {
+ setProgressBarColor(mProgressBarColor);
+ }
}
}
+ /**
+ * @deprecated Use {@link #setProgressBarShown(boolean)}
+ */
+ @Deprecated
+ public void showProgressBar() {
+ setProgressBarShown(true);
+ }
+
+ /**
+ * @deprecated Use {@link #setProgressBarShown(boolean)}
+ */
+ @Deprecated
public void hideProgressBar() {
- final View progressBar = findViewById(R.id.suw_layout_progress);
- if (progressBar != null) {
- progressBar.setVisibility(View.GONE);
+ setProgressBarShown(false);
+ }
+
+ public void setProgressBarColor(ColorStateList color) {
+ mProgressBarColor = color;
+ if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ // Suppress lint error caused by
+ // https://code.google.com/p/android/issues/detail?id=183136
+ // noinspection AndroidLintWrongViewCast
+ final ProgressBar bar = (ProgressBar) findViewById(R.id.suw_layout_progress);
+ if (bar != null) {
+ bar.setIndeterminateTintList(color);
+ }
}
}
+ public ColorStateList getProgressBarColor() {
+ return mProgressBarColor;
+ }
+
/* Misc */
protected static class SavedState extends BaseSavedState {
diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java
index 2ac8600..3410e9b 100644
--- a/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java
+++ b/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java
@@ -18,6 +18,9 @@ package com.android.setupwizardlib;
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.util.Log;
@@ -27,6 +30,7 @@ import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.ListView;
+import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper;
import com.android.setupwizardlib.util.ListViewRequireScrollHelper;
import com.android.setupwizardlib.view.NavigationBar;
@@ -34,6 +38,9 @@ public class SetupWizardListLayout extends SetupWizardLayout {
private static final String TAG = "SetupWizardListLayout";
private ListView mListView;
+ private Drawable mDivider;
+ private Drawable mDefaultDivider;
+ private int mDividerInset;
public SetupWizardListLayout(Context context) {
this(context, 0, 0);
@@ -45,15 +52,27 @@ public class SetupWizardListLayout extends SetupWizardLayout {
public SetupWizardListLayout(Context context, int template, int containerId) {
super(context, template, containerId);
+ init(context, null, 0);
}
public SetupWizardListLayout(Context context, AttributeSet attrs) {
super(context, attrs);
+ init(context, attrs, 0);
}
@TargetApi(VERSION_CODES.HONEYCOMB)
public SetupWizardListLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.SuwSetupWizardListLayout, defStyleAttr, 0);
+ int dividerInset =
+ a.getDimensionPixelSize(R.styleable.SuwSetupWizardListLayout_suwDividerInset, 0);
+ setDividerInset(dividerInset);
+ a.recycle();
}
@Override
@@ -73,6 +92,15 @@ public class SetupWizardListLayout extends SetupWizardLayout {
}
@Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (mDivider == null) {
+ // Update divider in case layout direction has just been resolved
+ updateDivider();
+ }
+ }
+
+ @Override
protected void onTemplateInflated() {
mListView = (ListView) findViewById(android.R.id.list);
}
@@ -85,6 +113,7 @@ public class SetupWizardListLayout extends SetupWizardLayout {
getListView().setAdapter(adapter);
}
+ @Override
public void requireScrollToBottom() {
final NavigationBar navigationBar = getNavigationBar();
final ListView listView = getListView();
@@ -95,4 +124,41 @@ public class SetupWizardListLayout extends SetupWizardLayout {
+ " the template to require scrolling.");
}
}
+
+ /**
+ * Sets the start inset of the divider. This will use the default divider drawable set in the
+ * theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
+ *
+ * @param inset The number of pixels to inset on the "start" side of the list divider. Typically
+ * this will be either {@code @dimen/suw_items_icon_divider_inset} or
+ * {@code @dimen/suw_items_text_divider_inset}.
+ */
+ public void setDividerInset(int inset) {
+ mDividerInset = inset;
+ updateDivider();
+ }
+
+ public int getDividerInset() {
+ return mDividerInset;
+ }
+
+ private void updateDivider() {
+ boolean shouldUpdate = true;
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+ shouldUpdate = isLayoutDirectionResolved();
+ }
+ if (shouldUpdate) {
+ final ListView listView = getListView();
+ if (mDefaultDivider == null) {
+ mDefaultDivider = listView.getDivider();
+ }
+ mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable(mDefaultDivider,
+ mDividerInset /* start */, 0 /* top */, 0 /* end */, 0 /* bottom */, this);
+ listView.setDivider(mDivider);
+ }
+ }
+
+ public Drawable getDivider() {
+ return mDivider;
+ }
}
diff --git a/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java b/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java
new file mode 100644
index 0000000..8325232
--- /dev/null
+++ b/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.setupwizardlib.gesture;
+
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+/**
+ * Helper class to detect the consective-tap gestures on a view.
+ *
+ * <p/>This class is instantiated and used similar to a GestureDetector, where onTouchEvent should
+ * be called when there are MotionEvents this detector should know about.
+ */
+public final class ConsecutiveTapsGestureDetector {
+
+ public interface OnConsecutiveTapsListener {
+ /**
+ * Callback method when the user tapped on the target view X number of times.
+ */
+ void onConsecutiveTaps(int numOfConsecutiveTaps);
+ }
+
+ private final View mView;
+ private final OnConsecutiveTapsListener mListener;
+ private final int mConsecutiveTapTouchSlopSquare;
+
+ private int mConsecutiveTapsCounter = 0;
+ private MotionEvent mPreviousTapEvent;
+
+ /**
+ * @param listener The listener that responds to the gesture.
+ * @param view The target view that associated with consecutive-tap gesture.
+ */
+ public ConsecutiveTapsGestureDetector(
+ OnConsecutiveTapsListener listener,
+ View view) {
+ mListener = listener;
+ mView = view;
+ int doubleTapSlop = ViewConfiguration.get(mView.getContext()).getScaledDoubleTapSlop();
+ mConsecutiveTapTouchSlopSquare = doubleTapSlop * doubleTapSlop;
+ }
+
+ /**
+ * This method should be called from the relevant activity or view, typically in
+ * onTouchEvent, onInterceptTouchEvent or dispatchTouchEvent.
+ *
+ * @param ev The motion event
+ */
+ public void onTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_UP) {
+ Rect viewRect = new Rect();
+ int[] leftTop = new int[2];
+ mView.getLocationOnScreen(leftTop);
+ viewRect.set(
+ leftTop[0],
+ leftTop[1],
+ leftTop[0] + mView.getWidth(),
+ leftTop[1] + mView.getHeight());
+ if (viewRect.contains((int) ev.getX(), (int) ev.getY())) {
+ if (isConsecutiveTap(ev)) {
+ mConsecutiveTapsCounter++;
+ } else {
+ mConsecutiveTapsCounter = 1;
+ }
+ mListener.onConsecutiveTaps(mConsecutiveTapsCounter);
+ } else {
+ // Touch outside the target view. Reset counter.
+ mConsecutiveTapsCounter = 0;
+ }
+
+ if (mPreviousTapEvent != null) {
+ mPreviousTapEvent.recycle();
+ }
+ mPreviousTapEvent = MotionEvent.obtain(ev);
+ }
+ }
+
+ /**
+ * Resets the consecutive-tap counter to zero.
+ */
+ public void resetCounter() {
+ mConsecutiveTapsCounter = 0;
+ }
+
+ /**
+ * Returns true if the distance between consecutive tap is within
+ * {@link #mConsecutiveTapTouchSlopSquare}. False, otherwise.
+ */
+ private boolean isConsecutiveTap(MotionEvent currentTapEvent) {
+ if (mPreviousTapEvent == null) {
+ return false;
+ }
+
+ double deltaX = mPreviousTapEvent.getX() - currentTapEvent.getX();
+ double deltaY = mPreviousTapEvent.getY() - currentTapEvent.getY();
+ return (deltaX * deltaX + deltaY * deltaY <= mConsecutiveTapTouchSlopSquare);
+ }
+}
diff --git a/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java b/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java
new file mode 100644
index 0000000..55bbe75
--- /dev/null
+++ b/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.setupwizardlib.items;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.setupwizardlib.R;
+
+import java.util.ArrayList;
+
+/**
+ * A list item with one or more buttons, declared as
+ * {@link com.android.setupwizardlib.items.ButtonItem}.
+ *
+ * <p>Example usage:
+ * <pre>{@code
+ * &lt;ButtonBarItem&gt;
+ *
+ * &lt;ButtonItem
+ * android:id="@+id/skip_button"
+ * android:text="@string/skip_button_label /&gt;
+ *
+ * &lt;ButtonItem
+ * android:id="@+id/next_button"
+ * android:text="@string/next_button_label
+ * android:theme="@style/SuwButtonItem.Colored" /&gt;
+ *
+ * &lt;/ButtonBarItem&gt;
+ * }</pre>
+ */
+public class ButtonBarItem extends AbstractItem implements ItemInflater.ItemParent {
+
+ private final ArrayList<ButtonItem> mButtons = new ArrayList<>();
+ private boolean mVisible = true;
+
+ public ButtonBarItem() {
+ super();
+ }
+
+ public ButtonBarItem(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public int getCount() {
+ return isVisible() ? 1 : 0;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ // The children buttons are enabled and clickable, but the item itself is not
+ return false;
+ }
+
+ @Override
+ public int getLayoutResource() {
+ return R.layout.suw_items_button_bar;
+ }
+
+ public void setVisible(boolean visible) {
+ mVisible = visible;
+ }
+
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ public int getViewId() {
+ return getId();
+ }
+
+ @Override
+ public void onBindView(View view) {
+ // Note: The efficiency could be improved by trying to recycle the buttons created by
+ // ButtonItem
+ final LinearLayout layout = (LinearLayout) view;
+ layout.removeAllViews();
+
+ for (ButtonItem buttonItem : mButtons) {
+ Button button = buttonItem.createButton(layout);
+ layout.addView(button);
+ }
+
+ view.setId(getViewId());
+ }
+
+ @Override
+ public void addChild(ItemHierarchy child) {
+ if (child instanceof ButtonItem) {
+ mButtons.add((ButtonItem) child);
+ } else {
+ throw new UnsupportedOperationException("Cannot add non-button item to Button Bar");
+ }
+ }
+
+ @Override
+ public ItemHierarchy findItemById(int id) {
+ if (getId() == id) {
+ return this;
+ }
+ for (ButtonItem button : mButtons) {
+ final ItemHierarchy item = button.findItemById(id);
+ if (item != null) {
+ return item;
+ }
+ }
+ return null;
+ }
+}
diff --git a/library/main/src/com/android/setupwizardlib/items/ButtonItem.java b/library/main/src/com/android/setupwizardlib/items/ButtonItem.java
new file mode 100644
index 0000000..4faeff4
--- /dev/null
+++ b/library/main/src/com/android/setupwizardlib/items/ButtonItem.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.setupwizardlib.items;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.setupwizardlib.R;
+
+/**
+ * Description of a button inside {@link com.android.setupwizardlib.items.ButtonBarItem}. This item
+ * will not be bound by the adapter, and must be a child of {@code ButtonBarItem}.
+ */
+public class ButtonItem extends AbstractItem implements View.OnClickListener {
+
+ public interface OnClickListener {
+ void onClick(ButtonItem item);
+ }
+
+ private boolean mEnabled = true;
+ private CharSequence mText;
+ private int mTheme = R.style.SuwButtonItem;
+ private OnClickListener mListener;
+
+ private Button mButton;
+
+ public ButtonItem() {
+ super();
+ }
+
+ public ButtonItem(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwButtonItem);
+ mEnabled = a.getBoolean(R.styleable.SuwButtonItem_android_enabled, true);
+ mText = a.getText(R.styleable.SuwButtonItem_android_text);
+ mTheme = a.getResourceId(R.styleable.SuwButtonItem_android_theme, R.style.SuwButtonItem);
+ a.recycle();
+ }
+
+ public void setOnClickListener(OnClickListener listener) {
+ mListener = listener;
+ }
+
+ public void setText(CharSequence text) {
+ mText = text;
+ }
+
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * The theme to use for this button. This can be used to create button of a particular style
+ * (e.g. a colored or borderless button). Typically {@code android:buttonStyle} will be set in
+ * the theme to change the style applied by the button.
+ *
+ * @param theme Resource ID of the theme
+ */
+ public void setTheme(int theme) {
+ mTheme = theme;
+ mButton = null;
+ }
+
+ /**
+ * @return Resource ID of the theme used by this button.
+ */
+ public int getTheme() {
+ return mTheme;
+ }
+
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ @Override
+ public int getCount() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public int getLayoutResource() {
+ return 0;
+ }
+
+ /**
+ * Do not use this since ButtonItem is not directly part of a list.
+ */
+ @Override
+ public final void onBindView(View view) {
+ throw new UnsupportedOperationException("Cannot bind to ButtonItem's view");
+ }
+
+ protected Button createButton(ViewGroup parent) {
+ if (mButton == null) {
+ Context context = parent.getContext();
+ if (mTheme != 0) {
+ context = new ContextThemeWrapper(context, mTheme);
+ }
+ mButton = new Button(context);
+ mButton.setOnClickListener(this);
+ }
+ mButton.setEnabled(mEnabled);
+ mButton.setText(mText);
+ return mButton;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mListener != null) {
+ mListener.onClick(this);
+ }
+ }
+}
diff --git a/library/main/src/com/android/setupwizardlib/items/Item.java b/library/main/src/com/android/setupwizardlib/items/Item.java
index d03b990..796d533 100644
--- a/library/main/src/com/android/setupwizardlib/items/Item.java
+++ b/library/main/src/com/android/setupwizardlib/items/Item.java
@@ -138,6 +138,9 @@ public class Item extends AbstractItem {
final Drawable icon = getIcon();
if (icon != null) {
final ImageView iconView = (ImageView) view.findViewById(R.id.suw_items_icon);
+ // Set the image drawable to null before setting the state and level to avoid affecting
+ // any recycled drawable in the ImageView
+ iconView.setImageDrawable(null);
iconView.setImageState(icon.getState(), false /* merge */);
iconView.setImageLevel(icon.getLevel());
iconView.setImageDrawable(icon);
diff --git a/library/main/src/com/android/setupwizardlib/items/ItemGroup.java b/library/main/src/com/android/setupwizardlib/items/ItemGroup.java
index 3e500bb..e449a95 100644
--- a/library/main/src/com/android/setupwizardlib/items/ItemGroup.java
+++ b/library/main/src/com/android/setupwizardlib/items/ItemGroup.java
@@ -23,7 +23,8 @@ import android.util.SparseIntArray;
import java.util.ArrayList;
import java.util.List;
-public class ItemGroup extends AbstractItemHierarchy implements ItemHierarchy.Observer {
+public class ItemGroup extends AbstractItemHierarchy implements ItemInflater.ItemParent,
+ ItemHierarchy.Observer {
/* static section */
@@ -101,6 +102,7 @@ public class ItemGroup extends AbstractItemHierarchy implements ItemHierarchy.Ob
/**
* Add a child hierarchy to this item group.
*/
+ @Override
public void addChild(ItemHierarchy child) {
mChildren.add(child);
child.registerObserver(this);
diff --git a/library/main/src/com/android/setupwizardlib/items/ItemInflater.java b/library/main/src/com/android/setupwizardlib/items/ItemInflater.java
index 6bd77ac..cadf1a4 100644
--- a/library/main/src/com/android/setupwizardlib/items/ItemInflater.java
+++ b/library/main/src/com/android/setupwizardlib/items/ItemInflater.java
@@ -27,6 +27,10 @@ public class ItemInflater extends GenericInflater<ItemHierarchy> {
private static final String TAG = "ItemInflater";
+ public interface ItemParent {
+ void addChild(ItemHierarchy child);
+ }
+
private final Context mContext;
public ItemInflater(Context context) {
@@ -44,14 +48,15 @@ public class ItemInflater extends GenericInflater<ItemHierarchy> {
* Return the context we are running in, for access to resources, class
* loader, etc.
*/
+ @Override
public Context getContext() {
return mContext;
}
@Override
protected void onAddChildItem(ItemHierarchy parent, ItemHierarchy child) {
- if (parent instanceof ItemGroup) {
- ((ItemGroup) parent).addChild(child);
+ if (parent instanceof ItemParent) {
+ ((ItemParent) parent).addChild(child);
} else {
throw new IllegalArgumentException("Cannot add child item to " + parent);
}
diff --git a/library/main/src/com/android/setupwizardlib/span/LinkSpan.java b/library/main/src/com/android/setupwizardlib/span/LinkSpan.java
new file mode 100644
index 0000000..e4f9854
--- /dev/null
+++ b/library/main/src/com/android/setupwizardlib/span/LinkSpan.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.setupwizardlib.span;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.text.TextPaint;
+import android.text.style.ClickableSpan;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * A clickable span that will listen for click events and send it back to the context. To use this
+ * class, implement {@link com.android.setupwizardlib.span.LinkSpan.OnClickListener} in your
+ * context (typically your Activity).
+ *
+ * <p />Note on accessibility: For TalkBack to be able to traverse and interact with the links, you
+ * should use {@code LinkAccessibilityHelper} in your {@code TextView} subclass. Optionally you can
+ * also use {@code RichTextView}, which includes link support.
+ */
+public class LinkSpan extends ClickableSpan {
+
+ /*
+ * Implementation note: When the orientation changes, TextView retains a reference to this span
+ * instead of writing it to a parcel (ClickableSpan is not Parcelable). If this class has any
+ * reference to the containing Activity (i.e. the activity context, or any views in the
+ * activity), it will cause memory leak.
+ */
+
+ /* static section */
+
+ private static final String TAG = "LinkSpan";
+
+ private static final Typeface TYPEFACE_MEDIUM =
+ Typeface.create("sans-serif-medium", Typeface.NORMAL);
+
+ public interface OnClickListener {
+ void onClick(LinkSpan span);
+ }
+
+ /* non-static section */
+
+ private final String mId;
+
+ public LinkSpan(String id) {
+ mId = id;
+ }
+
+ @Override
+ public void onClick(View view) {
+ final Context context = view.getContext();
+ if (context instanceof OnClickListener) {
+ ((OnClickListener) context).onClick(this);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ view.cancelPendingInputEvents();
+ }
+ } else {
+ Log.w(TAG, "Dropping click event. No listener attached.");
+ }
+ }
+
+ @Override
+ public void updateDrawState(TextPaint drawState) {
+ super.updateDrawState(drawState);
+ drawState.setUnderlineText(false);
+ drawState.setTypeface(TYPEFACE_MEDIUM);
+ }
+
+ public String getId() {
+ return mId;
+ }
+}
diff --git a/library/main/src/com/android/setupwizardlib/span/SpanHelper.java b/library/main/src/com/android/setupwizardlib/span/SpanHelper.java
new file mode 100644
index 0000000..d75e338
--- /dev/null
+++ b/library/main/src/com/android/setupwizardlib/span/SpanHelper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.setupwizardlib.span;
+
+import android.text.Spannable;
+
+/**
+ * Contains helper methods for dealing with text spans, e.g. the ones in {@code android.text.style}.
+ */
+public class SpanHelper {
+
+ /**
+ * Add {@code newSpan} at the same start and end indices as {@code oldSpan} and remove
+ * {@code oldSpan} from the {@code spannable}.
+ */
+ public static void replaceSpan(Spannable spannable, Object oldSpan, Object newSpan) {
+ final int spanStart = spannable.getSpanStart(oldSpan);
+ final int spanEnd = spannable.getSpanEnd(oldSpan);
+ spannable.removeSpan(oldSpan);
+ spannable.setSpan(newSpan, spanStart, spanEnd, 0);
+ }
+}
diff --git a/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java b/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java
index 9d95db5..44bcefc 100644
--- a/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java
+++ b/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java
@@ -210,7 +210,7 @@ public class SystemBarHelper {
*/
public static void setImeInsetView(final View view) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
- view.setOnApplyWindowInsetsListener(new WindowInsetsListener(view.getContext()));
+ view.setOnApplyWindowInsetsListener(new WindowInsetsListener());
}
}
@@ -305,20 +305,20 @@ public class SystemBarHelper {
@TargetApi(VERSION_CODES.LOLLIPOP)
private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener {
-
- private int mNavigationBarHeight;
-
- public WindowInsetsListener(Context context) {
- mNavigationBarHeight =
- context.getResources().getDimensionPixelSize(R.dimen.suw_navbar_height);
- }
+ private int mBottomOffset;
+ private boolean mHasCalculatedBottomOffset = false;
@Override
public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
+ if (!mHasCalculatedBottomOffset) {
+ mBottomOffset = getBottomDistance(view);
+ mHasCalculatedBottomOffset = true;
+ }
+
int bottomInset = insets.getSystemWindowInsetBottom();
final int bottomMargin = Math.max(
- insets.getSystemWindowInsetBottom() - mNavigationBarHeight, 0);
+ insets.getSystemWindowInsetBottom() - mBottomOffset, 0);
final ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) view.getLayoutParams();
@@ -339,4 +339,10 @@ public class SystemBarHelper {
);
}
}
+
+ private static int getBottomDistance(View view) {
+ int[] coords = new int[2];
+ view.getLocationInWindow(coords);
+ return view.getRootView().getHeight() - coords[1] - view.getHeight();
+ }
}
diff --git a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java
index 7d98a90..10172ce 100644
--- a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java
+++ b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java
@@ -123,6 +123,8 @@ public class WizardManagerHelper {
*/
public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) {
dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE));
+ dstIntent.putExtra(EXTRA_IS_FIRST_RUN,
+ srcIntent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false));
dstIntent.putExtra(EXTRA_SCRIPT_URI, srcIntent.getStringExtra(EXTRA_SCRIPT_URI));
dstIntent.putExtra(EXTRA_ACTION_ID, srcIntent.getStringExtra(EXTRA_ACTION_ID));
}
diff --git a/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java b/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java
index 7fdabec..fe0bc8f 100644
--- a/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java
+++ b/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java
@@ -69,6 +69,16 @@ public class StatusBarBackgroundLayout extends FrameLayout {
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ if (mLastInsets == null) {
+ requestApplyInsets();
+ }
+ }
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
diff --git a/library/platform/res/values-v21/styles.xml b/library/platform/res/values-v21/styles.xml
index 5c51493..8c74d58 100644
--- a/library/platform/res/values-v21/styles.xml
+++ b/library/platform/res/values-v21/styles.xml
@@ -70,8 +70,8 @@
<!-- Placeholder for GLIF dark theme, colors are not updated yet -->
<style name="SuwThemeGlif" parent="android:Theme.Material.NoActionBar">
- <item name="android:colorAccent">@color/suw_color_accent_light</item>
- <item name="android:colorPrimary">@color/suw_color_accent_light</item>
+ <item name="android:colorAccent">@color/suw_color_accent_glif_dark</item>
+ <item name="android:colorPrimary">@color/suw_color_accent_glif_light</item>
<item name="android:indeterminateTint">?android:attr/colorPrimary</item>
<!-- Specify the indeterminateTintMode to work around a bug in Lollipop -->
<item name="android:indeterminateTintMode">src_in</item>
@@ -92,8 +92,8 @@
</style>
<style name="SuwThemeGlif.Light" parent="android:Theme.Material.Light.NoActionBar">
- <item name="android:colorAccent">@color/suw_color_accent_light</item>
- <item name="android:colorPrimary">@color/suw_color_accent_light</item>
+ <item name="android:colorAccent">@color/suw_color_accent_glif_dark</item>
+ <item name="android:colorPrimary">@color/suw_color_accent_glif_light</item>
<item name="android:indeterminateTint">?android:attr/colorPrimary</item>
<!-- Specify the indeterminateTintMode to work around a bug in Lollipop -->
<item name="android:indeterminateTintMode">src_in</item>
@@ -113,28 +113,4 @@
<item name="suwMarginSides">@dimen/suw_glif_margin_sides</item>
</style>
- <!-- GLIF Card layout (for tablets) -->
-
- <style name="SuwGlifCardBackground">
- <item name="android:background">?android:attr/colorPrimary</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">?android:attr/listPreferredItemPaddingEnd</item>
- <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
- <item name="android:paddingTop">@dimen/suw_items_padding_vertical</item>
- </style>
-
- <style name="SuwItemTitle">
- <item name="android:textAppearance">?android:attr/textAppearanceListItem</item>
- </style>
-
- <style name="SuwItemSummary">
- <item name="android:textAppearance">?android:attr/textAppearanceListItemSmall</item>
- </style>
-
</resources>
diff --git a/library/rules.gradle b/library/rules.gradle
index e99c994..8efdd5c 100644
--- a/library/rules.gradle
+++ b/library/rules.gradle
@@ -96,8 +96,12 @@ android {
res.srcDirs = ['test/res']
}
+ androidTestEclairMr1Compat {
+ java.srcDirs = ['eclair-mr1/test/src']
+ }
+
androidTestFullSupport {
- java.srcDirs = ['full-support/test/src']
+ java.srcDirs = ['full-support/test/src', 'eclair-mr1/test/src']
res.srcDirs = ['full-support/test/res']
}
}
diff --git a/library/self.gradle b/library/self.gradle
index 9baa10e..7b3c3df 100644
--- a/library/self.gradle
+++ b/library/self.gradle
@@ -7,7 +7,7 @@ apply plugin: 'dist'
apply from: 'build.gradle'
apply from: '../tools/gradle/docs.gradle'
-task docs(dependsOn: 'javadocPlatformRelease')
+task docs(dependsOn: 'javadocFullSupportRelease')
android.lintOptions {
abortOnError true
diff --git a/library/test/src/com/android/setupwizardlib/test/ButtonBarItemTest.java b/library/test/src/com/android/setupwizardlib/test/ButtonBarItemTest.java
new file mode 100644
index 0000000..d3d87e5
--- /dev/null
+++ b/library/test/src/com/android/setupwizardlib/test/ButtonBarItemTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.test.AndroidTestCase;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.setupwizardlib.items.ButtonBarItem;
+import com.android.setupwizardlib.items.ButtonItem;
+import com.android.setupwizardlib.items.Item;
+import com.android.setupwizardlib.items.ItemHierarchy;
+
+public class ButtonBarItemTest extends AndroidTestCase {
+
+ private ButtonItem mChild1;
+ private ButtonItem mChild2;
+ private ButtonItem mChild3;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mChild1 = new ButtonItem();
+ mChild2 = new ButtonItem();
+ mChild3 = new ButtonItem();
+ }
+
+ public void testFindItemById() {
+ ButtonBarItem item = new ButtonBarItem();
+ item.setId(888);
+
+ mChild1.setId(123);
+ mChild2.setId(456);
+ mChild3.setId(789);
+ item.addChild(mChild1);
+ item.addChild(mChild2);
+ item.addChild(mChild3);
+
+ assertEquals("Finding 123 should return child1", mChild1, item.findItemById(123));
+ assertEquals("Finding 456 should return child2", mChild2, item.findItemById(456));
+ assertEquals("Finding 789 should return child3", mChild3, item.findItemById(789));
+
+ assertEquals("Finding 888 should return ButtonBarItem itself", item,
+ item.findItemById(888));
+
+ assertNull("Finding 999 should return null", item.findItemById(999));
+ }
+
+ public void testBindEmpty() {
+ ButtonBarItem item = new ButtonBarItem();
+ final ViewGroup layout = createLayout();
+ item.onBindView(layout);
+
+ assertEquals("Binding empty ButtonBar should not create any children", 0,
+ layout.getChildCount());
+ }
+
+ public void testBind() {
+ ButtonBarItem item = new ButtonBarItem();
+
+ item.addChild(mChild1);
+ mChild1.setText("child1");
+ item.addChild(mChild2);
+ mChild2.setText("child2");
+ item.addChild(mChild3);
+ mChild3.setText("child3");
+
+ final ViewGroup layout = createLayout();
+ item.onBindView(layout);
+
+ assertEquals("Binding ButtonBar should create 3 children", 3, layout.getChildCount());
+ assertEquals("First button should have text \"child1\"", "child1",
+ ((Button) layout.getChildAt(0)).getText());
+ assertEquals("Second button should have text \"child2\"", "child2",
+ ((Button) layout.getChildAt(1)).getText());
+ assertEquals("Third button should have text \"child3\"", "child3",
+ ((Button) layout.getChildAt(2)).getText());
+ }
+
+ public void testAddInvalidChild() {
+ ButtonBarItem item = new ButtonBarItem();
+
+ ItemHierarchy invalidChild = new Item();
+
+ try {
+ item.addChild(invalidChild);
+ fail("Adding non ButtonItem to ButtonBarItem should throw exception");
+ } catch (UnsupportedOperationException e) {
+ // pass
+ }
+ }
+
+ private ViewGroup createLayout() {
+ return new LinearLayout(mContext);
+ }
+}
diff --git a/library/test/src/com/android/setupwizardlib/test/ButtonItemTest.java b/library/test/src/com/android/setupwizardlib/test/ButtonItemTest.java
new file mode 100644
index 0000000..45342d0
--- /dev/null
+++ b/library/test/src/com/android/setupwizardlib/test/ButtonItemTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.setupwizardlib.R;
+import com.android.setupwizardlib.items.ButtonItem;
+
+public class ButtonItemTest extends AndroidTestCase {
+
+ private ViewGroup mParent;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mParent = new LinearLayout(getContext());
+ }
+
+ public void testDefaultItem() {
+ ButtonItem item = new ButtonItem();
+
+ assertTrue("ButtonItem should be enabled by default", item.isEnabled());
+ assertEquals("ButtonItem should return count = 0", 0, item.getCount());
+ assertEquals("ButtonItem should return layout resource = 0", 0, item.getLayoutResource());
+ assertEquals("Default theme should be @style/SuwButtonItem", R.style.SuwButtonItem,
+ item.getTheme());
+ assertNull("Default text should be null", item.getText());
+ }
+
+ public void testOnBindView() {
+ ButtonItem item = new ButtonItem();
+
+ try {
+ item.onBindView(new View(getContext()));
+ fail("Calling onBindView on ButtonItem should throw UnsupportedOperationException");
+ } catch (UnsupportedOperationException e) {
+ // pass
+ }
+ }
+
+ public void testCreateButton() {
+ TestButtonItem item = new TestButtonItem();
+ final Button button = item.createButton(mParent);
+
+ assertTrue("Default button should be enabled", button.isEnabled());
+ assertTrue("Default button text should be empty", TextUtils.isEmpty(button.getText()));
+ }
+
+ public void testSetEnabledTrue() {
+ TestButtonItem item = new TestButtonItem();
+ item.setEnabled(true);
+
+ final Button button = item.createButton(mParent);
+ assertTrue("ButtonItem should be enabled", item.isEnabled());
+ assertTrue("Button should be enabled", button.isEnabled());
+ }
+
+ public void testSetEnabledFalse() {
+ TestButtonItem item = new TestButtonItem();
+ item.setEnabled(false);
+
+ final Button button = item.createButton(mParent);
+ assertFalse("ButtonItem should be disabled", item.isEnabled());
+ assertFalse("Button should be disabled", button.isEnabled());
+ }
+
+ public void testSetText() {
+ TestButtonItem item = new TestButtonItem();
+ item.setText("lorem ipsum");
+
+ final Button button = item.createButton(mParent);
+ assertEquals("ButtonItem text should be \"lorem ipsum\"", "lorem ipsum", item.getText());
+ assertEquals("Button text should be \"lorem ipsum\"", "lorem ipsum", button.getText());
+ }
+
+ public void testSetTheme() {
+ TestButtonItem item = new TestButtonItem();
+ item.setTheme(12345);
+
+ final Button button = item.createButton(mParent);
+ assertEquals("ButtonItem theme should be 12345", 12345, item.getTheme());
+ button.getContext().getTheme();
+ }
+
+ public void testOnClickListener() {
+ TestButtonItem item = new TestButtonItem();
+ final TestOnClickListener listener = new TestOnClickListener();
+ item.setOnClickListener(listener);
+
+ assertNull("Clicked item should be null before clicking", listener.clickedItem);
+
+ final Button button = item.createButton(mParent);
+ button.performClick();
+
+ assertSame("Clicked item should be set", item, listener.clickedItem);
+ }
+
+ private static class TestOnClickListener implements ButtonItem.OnClickListener {
+
+ public ButtonItem clickedItem = null;
+
+ @Override
+ public void onClick(ButtonItem item) {
+ clickedItem = item;
+ }
+ }
+
+ private static class TestButtonItem extends ButtonItem {
+
+ @Override
+ public Button createButton(ViewGroup parent) {
+ // Make this method public for testing
+ return super.createButton(parent);
+ }
+ }
+}
diff --git a/library/test/src/com/android/setupwizardlib/test/GlifLayoutTest.java b/library/test/src/com/android/setupwizardlib/test/GlifLayoutTest.java
index 310dcbe..1ae620d 100644
--- a/library/test/src/com/android/setupwizardlib/test/GlifLayoutTest.java
+++ b/library/test/src/com/android/setupwizardlib/test/GlifLayoutTest.java
@@ -23,6 +23,7 @@ import android.os.Build;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.ContextThemeWrapper;
+import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
@@ -98,6 +99,21 @@ public class GlifLayoutTest extends InstrumentationTestCase {
}
}
+ @SmallTest
+ public void testWrongTheme() {
+ // Test the error message when using the wrong theme
+ mContext = new ContextThemeWrapper(getInstrumentation().getContext(),
+ android.R.style.Theme);
+ try {
+ new GlifLayout(mContext);
+ fail("Should have thrown InflateException");
+ } catch (InflateException e) {
+ assertEquals("Exception message should mention correct theme to use",
+ "Unable to inflate layout. Are you using @style/SuwThemeGlif "
+ + "(or its descendant) as your theme?", e.getMessage());
+ }
+ }
+
private void assertDefaultTemplateInflated(GlifLayout layout) {
View title = layout.findViewById(R.id.suw_layout_title);
assertNotNull("@id/suw_layout_title should not be null", title);
diff --git a/library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java b/library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java
index d53f49c..44aff73 100644
--- a/library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java
+++ b/library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java
@@ -16,9 +16,11 @@
package com.android.setupwizardlib.test;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
+import android.graphics.Paint;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@@ -27,6 +29,45 @@ import com.android.setupwizardlib.GlifPatternDrawable;
public class GlifPatternDrawableTest extends AndroidTestCase {
@SmallTest
+ public void testDraw() {
+ final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+
+ final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED);
+ drawable.setBounds(0, 0, 1366, 768);
+ drawable.draw(canvas);
+
+ assertEquals("Top left pixel should be #ed0000", 0xffed0000, bitmap.getPixel(0, 0));
+ assertEquals("Center pixel should be #d30000", 0xffd30000, bitmap.getPixel(683, 384));
+ assertEquals("Bottom right pixel should be #c70000", 0xffc70000,
+ bitmap.getPixel(1365, 767));
+ }
+
+ @SmallTest
+ public void testDrawTwice() {
+ // Test that the second time the drawable is drawn is also correct, to make sure caching is
+ // done correctly.
+
+ final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+
+ final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED);
+ drawable.setBounds(0, 0, 1366, 768);
+ drawable.draw(canvas);
+
+ Paint paint = new Paint();
+ paint.setColor(Color.WHITE);
+ canvas.drawRect(0, 0, 1366, 768, paint); // Erase the entire canvas
+
+ drawable.draw(canvas);
+
+ assertEquals("Top left pixel should be #ed0000", 0xffed0000, bitmap.getPixel(0, 0));
+ assertEquals("Center pixel should be #d30000", 0xffd30000, bitmap.getPixel(683, 384));
+ assertEquals("Bottom right pixel should be #c70000", 0xffc70000,
+ bitmap.getPixel(1365, 767));
+ }
+
+ @SmallTest
public void testScaleToCanvasSquare() {
final Canvas canvas = new Canvas();
Matrix expected = new Matrix(canvas.getMatrix());
diff --git a/library/test/src/com/android/setupwizardlib/test/ItemTest.java b/library/test/src/com/android/setupwizardlib/test/ItemTest.java
index 68ea881..d9104ef 100644
--- a/library/test/src/com/android/setupwizardlib/test/ItemTest.java
+++ b/library/test/src/com/android/setupwizardlib/test/ItemTest.java
@@ -42,15 +42,22 @@ public class ItemTest extends AndroidTestCase {
item.setTitle("TestTitle");
item.setSummary("TestSummary");
Drawable icon = new ShapeDrawable();
+ icon.setLevel(4);
item.setIcon(icon);
View view = createLayout();
+ mIconView.setImageLevel(1);
+ Drawable recycledIcon = new ShapeDrawable();
+ mIconView.setImageDrawable(recycledIcon);
+
item.onBindView(view);
assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString());
assertEquals("Summary should be \"TestSummary\"", "TestSummary",
mSummaryView.getText().toString());
assertSame("Icon should be the icon shape drawable", icon, mIconView.getDrawable());
+ assertEquals("Recycled icon level should not change", 1, recycledIcon.getLevel());
+ assertEquals("Icon should be level 4", 4, icon.getLevel());
}
@SmallTest
diff --git a/library/test/src/com/android/setupwizardlib/test/LinkSpanTest.java b/library/test/src/com/android/setupwizardlib/test/LinkSpanTest.java
new file mode 100644
index 0000000..884f8ea
--- /dev/null
+++ b/library/test/src/com/android/setupwizardlib/test/LinkSpanTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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 android.content.Context;
+import android.content.ContextWrapper;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
+
+import com.android.setupwizardlib.span.LinkSpan;
+
+public class LinkSpanTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testOnClick() {
+ final TestContext context = new TestContext(getContext());
+ final TextView textView = new TextView(context);
+ final LinkSpan linkSpan = new LinkSpan("test_id");
+
+ linkSpan.onClick(textView);
+
+ assertSame("Clicked LinkSpan should be passed to setup", linkSpan, context.clickedSpan);
+ }
+
+ @SmallTest
+ public void testNonImplementingContext() {
+ final Context context = getContext();
+ final TextView textView = new TextView(context);
+ final LinkSpan linkSpan = new LinkSpan("test_id");
+
+ linkSpan.onClick(textView);
+
+ // This would be no-op, because the context doesn't implement LinkSpan.OnClickListener.
+ // Just check that no uncaught exception here.
+ }
+
+ private static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener {
+
+ public LinkSpan clickedSpan = null;
+
+ public TestContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public void onClick(LinkSpan span) {
+ clickedSpan = span;
+ }
+ }
+}
diff --git a/library/test/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java b/library/test/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java
index 9e291ac..f71c794 100644
--- a/library/test/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java
+++ b/library/test/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java
@@ -19,9 +19,13 @@ package com.android.setupwizardlib.test;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
+import android.os.Parcelable;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.SparseArray;
+import android.view.AbsSavedState;
import android.view.ContextThemeWrapper;
+import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
@@ -143,6 +147,72 @@ public class SetupWizardLayoutTest extends InstrumentationTestCase {
assertFalse("Progress bar should not be shown", layout.isProgressBarShown());
}
+ @SmallTest
+ public void testWrongTheme() {
+ // Test the error message when using the wrong theme
+ mContext = new ContextThemeWrapper(getInstrumentation().getContext(),
+ android.R.style.Theme);
+ try {
+ new SetupWizardLayout(mContext);
+ fail("Should have thrown InflateException");
+ } catch (InflateException e) {
+ assertEquals("Exception message should mention correct theme to use",
+ "Unable to inflate layout. Are you using @style/SuwThemeMaterial "
+ + "(or its descendant) as your theme?", e.getMessage());
+ }
+ }
+
+ @SmallTest
+ public void testOnRestoreFromInstanceState() {
+ final SetupWizardLayout layout = new SetupWizardLayout(mContext);
+ // noinspection ResourceType
+ layout.setId(1234);
+
+ SparseArray<Parcelable> container = new SparseArray<>();
+ layout.saveHierarchyState(container);
+
+ final SetupWizardLayout layout2 = new SetupWizardLayout(mContext);
+ // noinspection ResourceType
+ layout2.setId(1234);
+ layout2.restoreHierarchyState(container);
+
+ assertFalse("Progress bar should not be shown", layout2.isProgressBarShown());
+ }
+
+ @SmallTest
+ public void testOnRestoreFromInstanceStateProgressBarShown() {
+ final SetupWizardLayout layout = new SetupWizardLayout(mContext);
+ // noinspection ResourceType
+ layout.setId(1234);
+
+ layout.setProgressBarShown(true);
+
+ SparseArray<Parcelable> container = new SparseArray<>();
+ layout.saveHierarchyState(container);
+
+ final SetupWizardLayout layout2 = new SetupWizardLayout(mContext);
+ // noinspection ResourceType
+ layout2.setId(1234);
+ layout2.restoreHierarchyState(container);
+
+ assertTrue("Progress bar should be shown", layout2.isProgressBarShown());
+ }
+
+ @SmallTest
+ public void testOnRestoreFromIncompatibleInstanceState() {
+ final SetupWizardLayout layout = new SetupWizardLayout(mContext);
+ // noinspection ResourceType
+ layout.setId(1234);
+
+ SparseArray<Parcelable> container = new SparseArray<>();
+ container.put(1234, AbsSavedState.EMPTY_STATE);
+ layout.restoreHierarchyState(container);
+
+ // SetupWizardLayout shouldn't crash with incompatible Parcelable
+
+ assertFalse("Progress bar should not be shown", layout.isProgressBarShown());
+ }
+
private void assertDefaultTemplateInflated(SetupWizardLayout layout) {
View decorView = layout.findViewById(R.id.suw_layout_decor);
View navbar = layout.findViewById(R.id.suw_layout_navigation_bar);
diff --git a/library/test/src/com/android/setupwizardlib/test/SpanHelperTest.java b/library/test/src/com/android/setupwizardlib/test/SpanHelperTest.java
new file mode 100644
index 0000000..819e969
--- /dev/null
+++ b/library/test/src/com/android/setupwizardlib/test/SpanHelperTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.Annotation;
+import android.text.SpannableStringBuilder;
+
+import com.android.setupwizardlib.span.SpanHelper;
+
+public class SpanHelperTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testReplaceSpan() {
+ SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world");
+ Annotation oldSpan = new Annotation("key", "value");
+ Annotation newSpan = new Annotation("newkey", "newvalue");
+ ssb.setSpan(oldSpan, 2, 5, 0 /* flags */);
+
+ SpanHelper.replaceSpan(ssb, oldSpan, newSpan);
+
+ final Object[] spans = ssb.getSpans(0, ssb.length(), Object.class);
+ assertEquals("There should be one span in the builder", 1, spans.length);
+ assertSame("The span should be newSpan", newSpan, spans[0]);
+ }
+}
diff --git a/library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java b/library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java
index fe680e4..2cb26ec 100644
--- a/library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java
+++ b/library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java
@@ -16,6 +16,7 @@
package com.android.setupwizardlib.test;
+import android.content.Context;
import android.graphics.drawable.ShapeDrawable;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@@ -32,4 +33,37 @@ public class StatusBarBackgroundLayoutTest extends AndroidTestCase {
assertSame("Status bar background drawable should be same as set",
drawable, layout.getStatusBarBackground());
}
+
+ @SmallTest
+ public void testAttachedToWindow() {
+ // Attaching to window should request apply window inset
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
+ final TestStatusBarBackgroundLayout layout =
+ new TestStatusBarBackgroundLayout(getContext());
+ layout.mRequestApplyInsets = false;
+ layout.onAttachedToWindow();
+
+ assertTrue("Attaching to window should apply window inset", layout.mRequestApplyInsets);
+ }
+ }
+
+ private static class TestStatusBarBackgroundLayout extends StatusBarBackgroundLayout {
+
+ boolean mRequestApplyInsets = false;
+
+ TestStatusBarBackgroundLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ public void requestApplyInsets() {
+ super.requestApplyInsets();
+ mRequestApplyInsets = true;
+ }
+ }
}