From 12b8b856d1a66fd0263558d7c2cb5240f53b4ff1 Mon Sep 17 00:00:00 2001 From: Jeff Gaston Date: Mon, 5 Mar 2018 14:23:11 -0500 Subject: Get Support Library resources from prebuilts Support Library is changing its build process (postprocessing resource files, via Dejetifier), so its resource files won't be able to be used directly for now. Bug: 74196535 Test: m -j checkbuild Change-Id: I30410d817e4b0a70135c5ed9b7ab3a725307fe09 --- library/Android.mk | 6 +++--- library/common-gingerbread.mk | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/Android.mk b/library/Android.mk index 030733e..b0fa9f0 100644 --- a/library/Android.mk +++ b/library/Android.mk @@ -60,9 +60,9 @@ LOCAL_AAPT_FLAGS := --auto-add-overlay \ --extra-packages android.support.v7.recyclerview LOCAL_RESOURCE_DIR += \ - frameworks/support/compat/res \ - frameworks/support/v7/appcompat/res \ - frameworks/support/v7/recyclerview/res + prebuilts/sdk/current/supportcompat/res \ + prebuilts/sdk/current/supportv7/appcompat/res \ + prebuilts/sdk/current/supportv7/recyclerview/res LOCAL_JAVA_LIBRARIES := \ android-support-annotations \ diff --git a/library/common-gingerbread.mk b/library/common-gingerbread.mk index 6ced8b0..02642f2 100644 --- a/library/common-gingerbread.mk +++ b/library/common-gingerbread.mk @@ -41,14 +41,14 @@ endif # Include support-v7-appcompat, if not already included ifeq (,$(findstring android-support-v7-appcompat,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res +LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/appcompat/res LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat endif # Include support-v7-recyclerview, if not already included ifeq (,$(findstring android-support-v7-recyclerview,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res +LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/recyclerview/res LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.recyclerview LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-recyclerview endif -- cgit v1.2.3 From bc1c7a159c14f8b8f532fec60681d34771cd7909 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Mon, 5 Mar 2018 15:59:43 -0800 Subject: Add touch feedback to links - Clear selection after LinkSpan is clicked so that the highlight effect will be cleared when the tap completes - Set focusableInTouchMode to true and revealOnFocusHint to false in RichTextView for N MR1 or above to allow the highlight effect to be visible in touch mode. Test: ./gradlew test connectedAndroidTest Bug: 73350031 Change-Id: Ibb6f67102775802cdfebaa1529c09d936b4096cb --- .../android/setupwizardlib/view/RichTextView.java | 11 ++ .../com/android/setupwizardlib/span/LinkSpan.java | 11 ++ .../setupwizardlib/test/RichTextViewTest.java | 186 -------------------- .../android/setupwizardlib/span/LinkSpanTest.java | 32 +++- .../setupwizardlib/view/RichTextViewTest.java | 194 +++++++++++++++++++++ 5 files changed, 245 insertions(+), 189 deletions(-) delete mode 100644 library/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java create mode 100644 library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java diff --git a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java index e6bc9da..6694cb2 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java +++ b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java @@ -130,6 +130,17 @@ public class RichTextView extends AppCompatTextView implements OnLinkClickListen // as individual TextViews consume touch events and thereby reducing the focus window // shown by Talkback. Disable focus if there are no links setFocusable(hasLinks); + // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is + // focusable in touch mode, we may be focused when the screen is first shown, and starting + // a screen halfway scrolled down is confusing to the user. + if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { + setRevealOnFocusHint(false); + // setRevealOnFocusHint is a new API added in SDK 25. For lower SDK versions, do not + // call setFocusableInTouchMode. We won't get touch effect on those earlier versions, + // but the link will still work, and will prevent the scroll view from starting halfway + // down the page. + setFocusableInTouchMode(hasLinks); + } } private boolean hasLinks(CharSequence text) { diff --git a/library/main/src/com/android/setupwizardlib/span/LinkSpan.java b/library/main/src/com/android/setupwizardlib/span/LinkSpan.java index a5f0424..26a3d16 100644 --- a/library/main/src/com/android/setupwizardlib/span/LinkSpan.java +++ b/library/main/src/com/android/setupwizardlib/span/LinkSpan.java @@ -21,10 +21,13 @@ import android.content.ContextWrapper; import android.graphics.Typeface; import android.os.Build; import android.support.annotation.Nullable; +import android.text.Selection; +import android.text.Spannable; import android.text.TextPaint; import android.text.style.ClickableSpan; import android.util.Log; import android.view.View; +import android.widget.TextView; /** * A clickable span that will listen for click events and send it back to the context. To use this @@ -86,11 +89,19 @@ public class LinkSpan extends ClickableSpan { public void onClick(View view) { if (dispatchClick(view)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // Prevent the touch event from bubbling up to the parent views. view.cancelPendingInputEvents(); } } else { Log.w(TAG, "Dropping click event. No listener attached."); } + if (view instanceof TextView) { + // Remove the highlight effect when the click happens by clearing the selection + CharSequence text = ((TextView) view).getText(); + if (text instanceof Spannable) { + Selection.setSelection((Spannable) text, 0); + } + } } private boolean dispatchClick(View view) { diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java deleted file mode 100644 index 5f3eb9f..0000000 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.setupwizardlib.test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.ContextWrapper; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.text.Annotation; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.TextAppearanceSpan; - -import com.android.setupwizardlib.span.LinkSpan; -import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; -import com.android.setupwizardlib.view.RichTextView; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class RichTextViewTest { - - @Test - public void testLinkAnnotation() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); - textView.setText(ssb); - - final CharSequence text = textView.getText(); - assertTrue("Text should be spanned", text instanceof Spanned); - - Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); - assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); - - spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); - assertTrue("The span should be a LinkSpan", spans[0] instanceof LinkSpan); - assertEquals("The LinkSpan should have id \"foobar\"", - "foobar", ((LinkSpan) spans[0]).getId()); - } - - @Test - public void testOnLinkClickListener() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); - textView.setText(ssb); - - OnLinkClickListener listener = mock(OnLinkClickListener.class); - textView.setOnLinkClickListener(listener); - - assertSame(listener, textView.getOnLinkClickListener()); - - CharSequence text = textView.getText(); - LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - spans[0].onClick(textView); - - verify(listener).onLinkClick(eq(spans[0])); - } - - @Test - public void testLegacyContextOnClickListener() { - // Click listener implemented by context should still be invoked for compatibility. - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - TestContext context = spy(new TestContext(InstrumentationRegistry.getTargetContext())); - RichTextView textView = new RichTextView(context); - textView.setText(ssb); - - CharSequence text = textView.getText(); - LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - spans[0].onClick(textView); - - verify(context).onClick(eq(spans[0])); - } - - @Test - public void testTextStyle() { - Annotation link = new Annotation("textAppearance", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); - textView.setText(ssb); - - final CharSequence text = textView.getText(); - assertTrue("Text should be spanned", text instanceof Spanned); - - Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); - assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); - - spans = ((Spanned) text).getSpans(0, text.length(), TextAppearanceSpan.class); - assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); - assertTrue("The span should be a TextAppearanceSpan", - spans[0] instanceof TextAppearanceSpan); - } - - @Test - public void testTextContainingLinksAreFocusable() { - Annotation testLink = new Annotation("link", "value"); - SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); - spannableStringBuilder.setSpan(testLink, 0, 3, 0); - - RichTextView view = new RichTextView(InstrumentationRegistry.getContext()); - view.setText(spannableStringBuilder); - - assertTrue("TextView should be focusable since it contains spans", view.isFocusable()); - } - - - @SuppressLint("SetTextI18n") // It's OK. This is just a test. - @Test - public void testTextContainingNoLinksAreNotFocusable() { - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); - textView.setText("Thou shall not be focusable!"); - - assertFalse("TextView should not be focusable since it does not contain any span", - textView.isFocusable()); - } - - - // Based on the text contents of the text view, the "focusable" property of the element - // should also be automatically changed. - @SuppressLint("SetTextI18n") // It's OK. This is just a test. - @Test - public void testRichTextViewFocusChangesWithTextChange() { - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); - textView.setText("Thou shall not be focusable!"); - - assertFalse(textView.isFocusable()); - - SpannableStringBuilder spannableStringBuilder = - new SpannableStringBuilder("I am focusable"); - spannableStringBuilder.setSpan(new Annotation("link", "focus:on_me"), 0, 1, 0); - textView.setText(spannableStringBuilder); - assertTrue(textView.isFocusable()); - } - - public static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { - - public TestContext(Context base) { - super(base); - } - - @Override - public void onClick(LinkSpan span) { - // Ignore. Can be verified using Mockito - } - } -} diff --git a/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java b/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java index fe72e03..3aafa7d 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java @@ -16,11 +16,16 @@ package com.android.setupwizardlib.span; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertSame; import static org.robolectric.RuntimeEnvironment.application; import android.content.Context; import android.content.ContextWrapper; +import android.text.Selection; +import android.text.SpannableStringBuilder; +import android.text.method.LinkMovementMethod; import android.widget.TextView; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; @@ -32,7 +37,7 @@ import org.junit.runner.RunWith; public class LinkSpanTest { @Test - public void testOnClick() { + public void onClick_shouldCallListenerOnContext() { final TestContext context = new TestContext(application); final TextView textView = new TextView(context); final LinkSpan linkSpan = new LinkSpan("test_id"); @@ -43,7 +48,7 @@ public class LinkSpanTest { } @Test - public void testNonImplementingContext() { + public void onClick_contextDoesNotImplementOnClickListener_shouldBeNoOp() { final TextView textView = new TextView(application); final LinkSpan linkSpan = new LinkSpan("test_id"); @@ -54,7 +59,7 @@ public class LinkSpanTest { } @Test - public void testWrappedListener() { + public void onClick_contextWrapsOnClickListener_shouldCallWrappedListener() { final TestContext context = new TestContext(application); final Context wrapperContext = new ContextWrapper(context); final TextView textView = new TextView(wrapperContext); @@ -65,6 +70,27 @@ public class LinkSpanTest { assertSame("Clicked LinkSpan should be passed to setup", linkSpan, context.clickedSpan); } + @Test + public void onClick_shouldClearSelection() { + final TestContext context = new TestContext(application); + final TextView textView = new TextView(context); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setFocusable(true); + textView.setFocusableInTouchMode(true); + final LinkSpan linkSpan = new LinkSpan("test_id"); + + SpannableStringBuilder text = new SpannableStringBuilder("Lorem ipsum dolor sit"); + textView.setText(text); + text.setSpan(linkSpan, /* start= */ 0, /* end= */ 5, /* flags= */ 0); + // Simulate the touch effect set by TextView when touched. + Selection.setSelection(text, /* start= */ 0, /* end= */ 5); + + linkSpan.onClick(textView); + + assertThat(Selection.getSelectionStart(textView.getText())).isEqualTo(0); + assertThat(Selection.getSelectionEnd(textView.getText())).isEqualTo(0); + } + @SuppressWarnings("deprecation") private static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java new file mode 100644 index 0000000..2e28b48 --- /dev/null +++ b/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java @@ -0,0 +1,194 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.robolectric.RuntimeEnvironment.application; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.text.Annotation; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.TextAppearanceSpan; + +import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; +import com.android.setupwizardlib.span.LinkSpan; +import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import java.util.Arrays; + +@RunWith(SuwLibRobolectricTestRunner.class) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +public class RichTextViewTest { + + @Test + public void testLinkAnnotation() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + final CharSequence text = textView.getText(); + assertTrue("Text should be spanned", text instanceof Spanned); + + Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); + assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); + + spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); + assertTrue("The span should be a LinkSpan", spans[0] instanceof LinkSpan); + assertEquals("The LinkSpan should have id \"foobar\"", + "foobar", ((LinkSpan) spans[0]).getId()); + } + + @Test + public void testOnLinkClickListener() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + OnLinkClickListener listener = mock(OnLinkClickListener.class); + textView.setOnLinkClickListener(listener); + + assertSame(listener, textView.getOnLinkClickListener()); + + CharSequence text = textView.getText(); + LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + spans[0].onClick(textView); + + verify(listener).onLinkClick(eq(spans[0])); + } + + @Test + public void testLegacyContextOnClickListener() { + // Click listener implemented by context should still be invoked for compatibility. + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + TestContext context = spy(new TestContext(application)); + RichTextView textView = new RichTextView(context); + textView.setText(ssb); + + CharSequence text = textView.getText(); + LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + spans[0].onClick(textView); + + verify(context).onClick(eq(spans[0])); + } + + @Test + public void testTextStyle() { + Annotation link = new Annotation("textAppearance", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + final CharSequence text = textView.getText(); + assertTrue("Text should be spanned", text instanceof Spanned); + + Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); + assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); + + spans = ((Spanned) text).getSpans(0, text.length(), TextAppearanceSpan.class); + assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); + assertTrue("The span should be a TextAppearanceSpan", + spans[0] instanceof TextAppearanceSpan); + } + + @Test + public void testTextContainingLinksAreFocusable() { + Annotation testLink = new Annotation("link", "value"); + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); + spannableStringBuilder.setSpan(testLink, 0, 3, 0); + + RichTextView view = new RichTextView(application); + view.setText(spannableStringBuilder); + + assertTrue("TextView should be focusable since it contains spans", view.isFocusable()); + } + + + @SuppressLint("SetTextI18n") // It's OK. This is just a test. + @Test + public void testTextContainingNoLinksAreNotFocusable() { + RichTextView textView = new RichTextView(application); + textView.setText("Thou shall not be focusable!"); + + assertFalse("TextView should not be focusable since it does not contain any span", + textView.isFocusable()); + } + + + // Based on the text contents of the text view, the "focusable" property of the element + // should also be automatically changed. + @SuppressLint("SetTextI18n") // It's OK. This is just a test. + @Test + public void testRichTextViewFocusChangesWithTextChange() { + RichTextView textView = new RichTextView(application); + textView.setText("Thou shall not be focusable!"); + + assertFalse(textView.isFocusable()); + assertFalse(textView.isFocusableInTouchMode()); + + SpannableStringBuilder spannableStringBuilder = + new SpannableStringBuilder("I am focusable"); + spannableStringBuilder.setSpan(new Annotation("link", "focus:on_me"), 0, 1, 0); + textView.setText(spannableStringBuilder); + assertTrue(textView.isFocusable()); + if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { + assertTrue(textView.isFocusableInTouchMode()); + assertFalse(textView.getRevealOnFocusHint()); + } else { + assertFalse(textView.isFocusableInTouchMode()); + } + } + + public static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { + + public TestContext(Context base) { + super(base); + } + + @Override + public void onClick(LinkSpan span) { + // Ignore. Can be verified using Mockito + } + } +} -- cgit v1.2.3 From fc01f70b288135f0c4654a4c16a023f6fd52ebd0 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Thu, 8 Mar 2018 18:33:42 -0800 Subject: Add touch feedback to tertiary buttons Test: Manual, updated `./gradlew test` Bug: 29574531 Change-Id: I1db6beb3bd4d6c4ab34e7dfeab79ed8976756332 --- library/main/res/values/dimens.xml | 2 ++ library/main/res/values/styles.xml | 4 ++-- .../src/com/android/setupwizardlib/util/GlifStyleTest.java | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/library/main/res/values/dimens.xml b/library/main/res/values/dimens.xml index 69475b2..be6b248 100644 --- a/library/main/res/values/dimens.xml +++ b/library/main/res/values/dimens.xml @@ -26,6 +26,8 @@ 8dp 16dp + + -16dp 8dp 72dp 24dp diff --git a/library/main/res/values/styles.xml b/library/main/res/values/styles.xml index dbd5358..fa2a080 100644 --- a/library/main/res/values/styles.xml +++ b/library/main/res/values/styles.xml @@ -195,10 +195,10 @@ @style/SuwGlifButton.Tertiary @style/SuwGlifButton.Tertiary - @null sans-serif ?attr/suwGlifHeaderGravity - 0dp + @dimen/suw_glif_negative_button_padding + @dimen/suw_glif_negative_button_padding false diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java index 20661ff..46df9d6 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java @@ -16,8 +16,9 @@ package com.android.setupwizardlib.util; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.robolectric.RuntimeEnvironment.application; @@ -59,9 +60,8 @@ public class GlifStyleTest { Robolectric.buildAttributeSet() .setStyleAttribute("@style/SuwGlifButton.Tertiary") .build()); - assertNull("Background of tertiary button should be null", button.getBackground()); - assertNull("Tertiary button should have no transformation method", - button.getTransformationMethod()); + assertThat(button.getBackground()).named("background").isNotNull(); + assertThat(button.getTransformationMethod()).named("transformation method").isNull(); if (VERSION.SDK_INT < VERSION_CODES.M) { // Robolectric resolved the wrong theme attribute on versions >= M // https://github.com/robolectric/robolectric/issues/2940 -- cgit v1.2.3 From 8f5580a2db4f46e03a99551be67e34f96b51a559 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 8 Mar 2018 20:40:52 -0800 Subject: Import translations. DO NOT MERGE Change-Id: I88918ebe98d8085abdc1e55a976cada94b2bf620 Auto-generated-cl: translation import --- library/main/res/values-as/strings.xml | 23 +++++++++++++++++++++++ library/main/res/values-or/strings.xml | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 library/main/res/values-as/strings.xml create mode 100644 library/main/res/values-or/strings.xml diff --git a/library/main/res/values-as/strings.xml b/library/main/res/values-as/strings.xml new file mode 100644 index 0000000..be6e06b --- /dev/null +++ b/library/main/res/values-as/strings.xml @@ -0,0 +1,23 @@ + + + + + "পৰৱৰ্তী" + "উভতি যাওক" + "অধিক" + diff --git a/library/main/res/values-or/strings.xml b/library/main/res/values-or/strings.xml new file mode 100644 index 0000000..c4d12ff --- /dev/null +++ b/library/main/res/values-or/strings.xml @@ -0,0 +1,23 @@ + + + + + "ପରବର୍ତ୍ତୀ" + "ପଛକୁ ଫେରନ୍ତୁ" + "ଅଧିକ" + -- cgit v1.2.3 From 1356bb076365966b36e18781c20db5aa2cc9c156 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 8 Mar 2018 20:50:22 -0800 Subject: Import translations. DO NOT MERGE Change-Id: I88162b22fda2baff60211977694a4f1aeaf666b9 Auto-generated-cl: translation import --- navigationbar/res/values-as/strings.xml | 6 ++++++ navigationbar/res/values-or/strings.xml | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 navigationbar/res/values-as/strings.xml create mode 100644 navigationbar/res/values-or/strings.xml diff --git a/navigationbar/res/values-as/strings.xml b/navigationbar/res/values-as/strings.xml new file mode 100644 index 0000000..398ff1f --- /dev/null +++ b/navigationbar/res/values-as/strings.xml @@ -0,0 +1,6 @@ + + + "পৰৱৰ্তী" + "উভতি যাওক" + diff --git a/navigationbar/res/values-or/strings.xml b/navigationbar/res/values-or/strings.xml new file mode 100644 index 0000000..4c70d06 --- /dev/null +++ b/navigationbar/res/values-or/strings.xml @@ -0,0 +1,6 @@ + + + "ପରବର୍ତ୍ତୀ" + "ପଛକୁ ଫେରନ୍ତୁ" + -- cgit v1.2.3 From a164eae405e822157f07b050982640d5c457f9aa Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Thu, 8 Mar 2018 21:40:05 -0800 Subject: Make links focusableInTouchMode for platform variant ag/3692611, but for platform variant Test: ./gradlew testPlatformDeprecatedDebugUnitTest Bug: 73350031 Change-Id: Ibd27348d97b9884be38eff69b540aedaf6416a2c --- .../platform/src/com/android/setupwizardlib/view/RichTextView.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/platform/src/com/android/setupwizardlib/view/RichTextView.java b/library/platform/src/com/android/setupwizardlib/view/RichTextView.java index 5a78561..aab3238 100644 --- a/library/platform/src/com/android/setupwizardlib/view/RichTextView.java +++ b/library/platform/src/com/android/setupwizardlib/view/RichTextView.java @@ -121,6 +121,11 @@ public class RichTextView extends TextView implements OnLinkClickListener { // as individual TextViews consume touch events and thereby reducing the focus window // shown by Talkback. Disable focus if there are no links setFocusable(hasLinks); + // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is + // focusable in touch mode, we may be focused when the screen is first shown, and starting + // a screen halfway scrolled down is confusing to the user. + setRevealOnFocusHint(false); + setFocusableInTouchMode(hasLinks); } private boolean hasLinks(CharSequence text) { -- cgit v1.2.3 From 83817ea0336e6d9a217b4c4585b11dbaf3eb740a Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Fri, 9 Mar 2018 20:55:24 -0800 Subject: Update fourcolor progress bar Test: Manual. Existing tests pass Bug: 63074068 Change-Id: I52a1704c39a9a54f9a4e79f42d174a20ee96c310 --- library/gingerbread/res/values/styles.xml | 1 + .../drawable-v21/suw_fourcolor_progress_bar.xml | 61 ++++++++++++---------- library/main/res/values-v21/styles.xml | 1 + library/main/res/values/dimens.xml | 1 + 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/library/gingerbread/res/values/styles.xml b/library/gingerbread/res/values/styles.xml index 241f037..130925d 100644 --- a/library/gingerbread/res/values/styles.xml +++ b/library/gingerbread/res/values/styles.xml @@ -277,6 +277,7 @@ diff --git a/library/main/res/drawable-v21/suw_fourcolor_progress_bar.xml b/library/main/res/drawable-v21/suw_fourcolor_progress_bar.xml index 2ac35ee..9d54141 100644 --- a/library/main/res/drawable-v21/suw_fourcolor_progress_bar.xml +++ b/library/main/res/drawable-v21/suw_fourcolor_progress_bar.xml @@ -26,37 +26,44 @@ - - + - - + - - + - - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + + + diff --git a/library/gingerbread/res/values/styles.xml b/library/gingerbread/res/values/styles.xml index 956b6cd..d12f802 100644 --- a/library/gingerbread/res/values/styles.xml +++ b/library/gingerbread/res/values/styles.xml @@ -262,6 +262,13 @@ @color/suw_flat_button_highlight + + + - - - + + + diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java index 7321915..195fc9b 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java @@ -28,6 +28,7 @@ import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; +import android.util.AttributeSet; import android.view.ContextThemeWrapper; import android.widget.Button; import android.widget.ProgressBar; @@ -37,29 +38,21 @@ import androidx.annotation.Nullable; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; -import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; @RunWith(SuwLibRobolectricTestRunner.class) @Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class GlifStyleTest { - private Context mContext; - - @Before - public void setUp() { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - } - - @Ignore("b/78472674") @Test public void testSuwGlifButtonTertiary() { - Button button = new Button( - mContext, + Button button = createButton( + new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light), Robolectric.buildAttributeSet() .setStyleAttribute("@style/SuwGlifButton.Tertiary") .build()); @@ -89,6 +82,22 @@ public class GlifStyleTest { activity.findViewById(R.id.suw_large_progress_bar) instanceof ProgressBar); } + private Button createButton(Context context, AttributeSet attrs) { + Class buttonClass; + try { + // Use AppCompatButton in builds that have them (i.e. gingerbreadCompat) + // noinspection unchecked + buttonClass = (Class) + Class.forName("androidx.appcompat.widget.AppCompatButton"); + } catch (ClassNotFoundException e) { + buttonClass = Button.class; + } + return ReflectionHelpers.callConstructor( + buttonClass, + ClassParameter.from(Context.class, context), + ClassParameter.from(AttributeSet.class, attrs)); + } + private static class GlifThemeActivity extends Activity { @Override -- cgit v1.2.3 From c29e5f172762c06df7e139cf95f8faf9ddff031c Mon Sep 17 00:00:00 2001 From: cnchen Date: Thu, 7 Jun 2018 16:20:59 +0800 Subject: Increase line limit for GLIF header text Bug: 79122597 Test: Manually Change-Id: I4b56d1b3ac631f53367bbae3e395e3932472fb14 --- library/main/res/values/styles.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/main/res/values/styles.xml b/library/main/res/values/styles.xml index fa2a080..17f7932 100644 --- a/library/main/res/values/styles.xml +++ b/library/main/res/values/styles.xml @@ -166,7 +166,7 @@ top end - 2 + 3 @dimen/suw_header_title_size -- cgit v1.2.3 From 1d05333434b4e0f009fbce9a22e8e31521f86cad Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Fri, 8 Jun 2018 18:33:47 -0700 Subject: Fix Partner.getText Test: ./gradlew test Bug: 109781942 Change-Id: I06c3be06def30803e5dda3069686f0ff510b804d --- .../main/src/com/android/setupwizardlib/util/Partner.java | 2 +- .../src/com/android/setupwizardlib/util/PartnerTest.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/library/main/src/com/android/setupwizardlib/util/Partner.java b/library/main/src/com/android/setupwizardlib/util/Partner.java index f3e07aa..9eaedc3 100644 --- a/library/main/src/com/android/setupwizardlib/util/Partner.java +++ b/library/main/src/com/android/setupwizardlib/util/Partner.java @@ -92,7 +92,7 @@ public class Partner { */ public static CharSequence getText(Context context, @StringRes int id) { final ResourceEntry entry = getResourceEntry(context, id); - return entry.resources.getText(id); + return entry.resources.getText(entry.id); } /** diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java index f8e71be..2285cd5 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java @@ -16,6 +16,8 @@ package com.android.setupwizardlib.util; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -145,6 +147,19 @@ public class PartnerTest { expectedPartnerColor, foundColor); } + @Test + public void getText_shouldReturnPartnerValueIfPresent() { + final CharSequence expectedPartnerText = "partner"; + doReturn(12345).when(mPartnerResources) + .getIdentifier(eq("suw_next_button_label"), eq("string"), anyString()); + doReturn(expectedPartnerText).when(mPartnerResources).getText(eq(12345)); + mPackageManager.addResolveInfoForIntent( + new Intent(ACTION_PARTNER_CUSTOMIZATION), + Collections.singletonList(createResolveInfo("test.partner.package", true, true))); + final CharSequence partnerText = Partner.getText(mContext, R.string.suw_next_button_label); + assertThat(partnerText).isEqualTo(expectedPartnerText); + } + @Test public void testLoadDefaultValue() { mPackageManager.addResolveInfoForIntent( -- cgit v1.2.3 From eaebb1e5a9875296c925d926dad6562d87604097 Mon Sep 17 00:00:00 2001 From: Cating Lin Date: Mon, 11 Jun 2018 10:19:02 +0800 Subject: Spinner of progress activity is too big, recommand:226dp. Bug: b/80082592 Test: Manual review screen capture Change-Id: Ic6bd395a5e586c217a4d199d46cd1e6aee9b86d8 --- library/gingerbread/res/values/styles.xml | 4 ++++ library/main/res/values-v21/styles.xml | 2 ++ library/main/res/values/dimens.xml | 1 + 3 files changed, 7 insertions(+) diff --git a/library/gingerbread/res/values/styles.xml b/library/gingerbread/res/values/styles.xml index 2cfe61a..8a736cb 100644 --- a/library/gingerbread/res/values/styles.xml +++ b/library/gingerbread/res/values/styles.xml @@ -283,6 +283,10 @@ diff --git a/library/main/res/values-v21/styles.xml b/library/main/res/values-v21/styles.xml index d2c27f6..a0458a1 100644 --- a/library/main/res/values-v21/styles.xml +++ b/library/main/res/values-v21/styles.xml @@ -50,6 +50,8 @@ @drawable/suw_fourcolor_progress_bar @null @null + @dimen/suw_glif_progress_bar_padding + @dimen/suw_glif_progress_bar_padding diff --git a/library/main/res/values/dimens.xml b/library/main/res/values/dimens.xml index 9aadcd3..63980ab 100644 --- a/library/main/res/values/dimens.xml +++ b/library/main/res/values/dimens.xml @@ -139,6 +139,7 @@ -7dp 7dp + 40dp 56dp -- cgit v1.2.3 From e4d2a1d06a1aac5c71729f51834f5a9952120b3f Mon Sep 17 00:00:00 2001 From: Cyril Lee Date: Wed, 20 Jun 2018 18:52:05 +0800 Subject: Fix video view without updates aspect ratio The IllustrationVideoView should update size of view when new aspectRatio is different from current. Bug: 110098506 Test: "./gradlew test" on folder of ub-setupwizard-master branch Change-Id: I5a4663fa0949a285966be923f183c9f24c4a709c --- .../setupwizardlib/view/IllustrationVideoView.java | 12 ++++++--- .../setupwizardlib/shadow/ShadowMediaPlayer.java | 31 ++++++++++++++++++++++ .../view/IllustrationVideoViewTest.java | 29 ++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java index e5688b3..608c32b 100644 --- a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java +++ b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java @@ -294,9 +294,15 @@ public class IllustrationVideoView extends TextureView implements Animatable, public void onPrepared(MediaPlayer mp) { mPrepared = true; mp.setLooping(shouldLoop()); - float aspectRatio = - (float) mp.getVideoHeight() / mp.getVideoWidth(); - if (Float.compare(mAspectRatio, aspectRatio) == 0) { + + float aspectRatio = 0.0f; + if (mp.getVideoWidth() > 0 && mp.getVideoHeight() > 0) { + aspectRatio = (float) mp.getVideoHeight() / mp.getVideoWidth(); + } else { + Log.w(TAG, "Unexpected video size=" + mp.getVideoWidth() + "x" + + mp.getVideoHeight()); + } + if (Float.compare(mAspectRatio, aspectRatio) != 0) { mAspectRatio = aspectRatio; requestLayout(); } diff --git a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowMediaPlayer.java b/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowMediaPlayer.java index 2ef2b7d..7bcefd0 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowMediaPlayer.java +++ b/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowMediaPlayer.java @@ -34,6 +34,15 @@ import java.util.Map; @Implements(MediaPlayer.class) public class ShadowMediaPlayer extends org.robolectric.shadows.ShadowMediaPlayer { + private int mVideoWidth; + private int mVideoHeight; + + public ShadowMediaPlayer() { + super(); + mVideoWidth = -1; + mVideoHeight = -1; + } + @Implementation public void setDataSource( @NonNull Context context, @@ -48,4 +57,26 @@ public class ShadowMediaPlayer extends org.robolectric.shadows.ShadowMediaPlayer public void seekTo(long msec, int mode) { seekTo((int) msec); } + + public void setVideoSize(int width, int height) { + if (width < 0) { + throw new IllegalArgumentException("Unexpected negative width=" + width); + } + if (height < 0) { + throw new IllegalArgumentException("Unexpected negative height=" + height); + } + + mVideoWidth = width; + mVideoHeight = height; + } + + @Override + public int getVideoWidth() { + return mVideoWidth; + } + + @Override + public int getVideoHeight() { + return mVideoHeight; + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java index c09e22f..a39ffb9 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java @@ -161,6 +161,35 @@ public class IllustrationVideoViewTest { + android.R.color.black); } + @Test + public void prepareVideo_shouldSetAspectRatio() { + createDefaultView(); + + mShadowMediaPlayer.setVideoSize(720, 1280); + + Robolectric.flushForegroundThreadScheduler(); + mView.start(); + + mView.measure(View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY)); + + final float aspectRatio = (float) mView.getMeasuredHeight() / mView.getMeasuredWidth(); + assertThat(aspectRatio).isWithin(0.001f).of(1280f / 720f); + } + + @Test + public void prepareVideo_zeroHeight_shouldSetAspectRatioToZero() { + createDefaultView(); + + mShadowMediaPlayer.setVideoSize(720, 0); + + Robolectric.flushForegroundThreadScheduler(); + mView.start(); + + final float aspectRatio = (float) mView.getHeight() / mView.getWidth(); + assertThat(aspectRatio).isEqualTo(0.0f); + } + private void createDefaultView() { mView = new IllustrationVideoView( application, -- cgit v1.2.3 From e2b863a8f3416e694b7bcccec589dcf46c1d0d84 Mon Sep 17 00:00:00 2001 From: Nicole Huang Date: Thu, 28 Jun 2018 17:40:00 +0800 Subject: [Overlay resource] Support video from overlay package support video from overlay package. bug: 110588815 Test: Manually tested and verified Change-Id: Ia5acc27602309ed7a0a081ef5a9629186f77fb8d --- .../setupwizardlib/view/IllustrationVideoView.java | 34 ++++++++--- .../view/IllustrationVideoViewTest.java | 67 ++++++++++++++++++++++ 2 files changed, 92 insertions(+), 9 deletions(-) diff --git a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java index 608c32b..3c188e8 100644 --- a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java +++ b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java @@ -72,6 +72,8 @@ public class IllustrationVideoView extends TextureView implements Animatable, private @RawRes int mVideoResId = 0; + private String mVideoResPackageName; + @VisibleForTesting Surface mSurface; private boolean mPrepared; @@ -80,8 +82,9 @@ public class IllustrationVideoView extends TextureView implements Animatable, super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwIllustrationVideoView); - mVideoResId = a.getResourceId(R.styleable.SuwIllustrationVideoView_suwVideo, 0); + final int videoResId = a.getResourceId(R.styleable.SuwIllustrationVideoView_suwVideo, 0); a.recycle(); + setVideoResource(videoResId); // By default the video scales without interpolation, resulting in jagged edges in the // video. This works around it by making the view go through scaling, which will apply @@ -110,16 +113,30 @@ public class IllustrationVideoView extends TextureView implements Animatable, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } + + /** + * Set the video and video package name to be played by this view. + * + * @param videoResId Resource ID of the video, typically an MP4 under res/raw. + * @param videoResPackageName The package name of videoResId. + */ + public void setVideoResource(@RawRes int videoResId, String videoResPackageName) { + if (videoResId != mVideoResId + || (videoResPackageName != null && !videoResPackageName.equals( + mVideoResPackageName))) { + mVideoResId = videoResId; + mVideoResPackageName = videoResPackageName; + createMediaPlayer(); + } + } + /** * Set the video to be played by this view. * * @param resId Resource ID of the video, typically an MP4 under res/raw. */ public void setVideoResource(@RawRes int resId) { - if (resId != mVideoResId) { - mVideoResId = resId; - createMediaPlayer(); - } + setVideoResource(resId, getContext().getPackageName()); } @Override @@ -152,12 +169,11 @@ public class IllustrationVideoView extends TextureView implements Animatable, mMediaPlayer.setOnInfoListener(this); mMediaPlayer.setOnErrorListener(this); - setVideoResourceInternal(mVideoResId); + setVideoResourceInternal(mVideoResId, mVideoResPackageName); } - private void setVideoResourceInternal(@RawRes int videoRes) { - Uri uri = - Uri.parse("android.resource://" + getContext().getPackageName() + "/" + videoRes); + private void setVideoResourceInternal(@RawRes int videoRes, String videoResPackageName) { + Uri uri = Uri.parse("android.resource://" + videoResPackageName + "/" + videoRes); try { mMediaPlayer.setDataSource(getContext(), uri); mMediaPlayer.prepareAsync(); diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java index a39ffb9..a6c1808 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java @@ -190,6 +190,64 @@ public class IllustrationVideoViewTest { assertThat(aspectRatio).isEqualTo(0.0f); } + @Test + public void setVideoResId_resetDiffVideoResFromDiffPackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + // reset different videoRes from different package + String newPackageName = "com.android.fakepackage"; + @RawRes int black = android.R.color.black; + addMediaInfo(black, newPackageName); + mView.setVideoResource(black, newPackageName); + + // should be reset to black with the new package + mShadowMediaPlayer = (ShadowMediaPlayer) Shadows.shadowOf(mView.mMediaPlayer); + assertThat(mShadowMediaPlayer.getSourceUri().toString()) + .isEqualTo("android.resource://" + newPackageName + "/" + + android.R.color.black); + } + + @Test + public void setVideoResId_resetDiffVideoResFromSamePackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + // reset different videoRes from the same package(default package) + String defaultPackageName = "com.android.setupwizardlib"; + @RawRes int black = android.R.color.black; + addMediaInfo(black); + mView.setVideoResource(black, defaultPackageName); + + // should be reset to black with the same package(default package) + mShadowMediaPlayer = (ShadowMediaPlayer) Shadows.shadowOf(mView.mMediaPlayer); + assertThat(mShadowMediaPlayer.getSourceUri().toString()) + .isEqualTo("android.resource://" + defaultPackageName + "/" + + android.R.color.black); + } + + @Test + public void setVideoResId_resetSameVideoResFromDifferentPackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + + // reset same videoRes from different package + @RawRes int white = android.R.color.white; + String newPackageName = "com.android.fakepackage"; + addMediaInfo(white, newPackageName); + mView.setVideoResource(white, newPackageName); + + // should be white with the new package + mShadowMediaPlayer = (ShadowMediaPlayer) Shadows.shadowOf(mView.mMediaPlayer); + assertThat(mShadowMediaPlayer.getSourceUri().toString()) + .isEqualTo("android.resource://" + newPackageName + "/" + + android.R.color.white); + } + private void createDefaultView() { mView = new IllustrationVideoView( application, @@ -227,6 +285,15 @@ public class IllustrationVideoViewTest { new MediaInfo(5000, 1)); } + private void addMediaInfo(@RawRes int res, String packageName) { + ShadowMediaPlayer.addMediaInfo( + DataSource.toDataSource( + application, + Uri.parse("android.resource://" + packageName + "/" + res), + null), + new MediaInfo(5000, 1)); + } + @Implements(Surface.class) @TargetApi(VERSION_CODES.HONEYCOMB) public static class ShadowSurface extends org.robolectric.shadows.ShadowSurface { -- cgit v1.2.3 From c97bf5a405f8ca927c369e1338a1095e69b975d5 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Tue, 14 Aug 2018 12:18:37 -0700 Subject: Update support test library Fixes the NoClassDefFoundError on L / M Test: ./gradlew connectedAndroidTest Bug: 79541791 Change-Id: Ie03780cd9f25aabd1456c6b24d9ca62b9d336fff --- library/self.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/self.gradle b/library/self.gradle index a4bff4e..78e5011 100644 --- a/library/self.gradle +++ b/library/self.gradle @@ -25,8 +25,8 @@ android.sourceSets { res.srcDirs = ['test/instrumentation/res'] dependencies { - androidTestImplementation 'com.android.support.test:rules:1.0.0' - androidTestImplementation 'com.android.support.test:runner:1.0.0' + androidTestImplementation 'com.android.support.test:rules:1.0.1' + androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestImplementation 'com.google.truth:truth:0.31' -- cgit v1.2.3 From d901a55fba2f2a80ec62f21a97cc11f98e8022c1 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Thu, 16 Aug 2018 21:47:56 -0700 Subject: Convert frameworks/opt/setupwizard to Android.bp See build/soong/README.md for more information. Test: m checkbuild Change-Id: I91ea161e67e416811d90854a91016c70dafb8ffb --- library/Android.bp | 51 +++++++++++++++++++++++++++++++++++++++++++++ library/Android.mk | 54 ------------------------------------------------ navigationbar/Android.bp | 7 +++++++ navigationbar/Android.mk | 9 -------- 4 files changed, 58 insertions(+), 63 deletions(-) create mode 100644 library/Android.bp delete mode 100644 library/Android.mk create mode 100644 navigationbar/Android.bp delete mode 100644 navigationbar/Android.mk diff --git a/library/Android.bp b/library/Android.bp new file mode 100644 index 0000000..61cd83a --- /dev/null +++ b/library/Android.bp @@ -0,0 +1,51 @@ +// +// Build the platform version of setup wizard library. +// + +android_library { + name: "setup-wizard-lib", + + libs: ["androidx.annotation_annotation"], + manifest: "main/AndroidManifest.xml", + resource_dirs: [ + "main/res", + "platform/res", + ], + sdk_version: "current", + srcs: [ + "main/src/**/*.java", + "platform/src/**/*.java", + ], + min_sdk_version: "23", +} + +// +// Build gingerbread-compat library, which uses AppCompat support library to provide backwards +// compatibility back to SDK v9. +// + +android_library { + name: "setup-wizard-lib-gingerbread-compat", + manifest: "main/AndroidManifest.xml", + resource_dirs: [ + "main/res", + "gingerbread/res", + "recyclerview/res", + ], + sdk_version: "current", + srcs: [ + "main/src/**/*.java", + "gingerbread/src/**/*.java", + "recyclerview/src/**/*.java", + ], + + libs: [ + "androidx.annotation_annotation", + "androidx.core_core", + "androidx.legacy_legacy-support-core-ui", + "androidx.appcompat_appcompat", + "androidx.recyclerview_recyclerview", + ], + + min_sdk_version: "14", +} diff --git a/library/Android.mk b/library/Android.mk deleted file mode 100644 index 4b828b6..0000000 --- a/library/Android.mk +++ /dev/null @@ -1,54 +0,0 @@ -## -# Build the platform version of setup wizard library. -# - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_USE_AAPT2 := true -LOCAL_JAVA_LIBRARIES := \ - androidx.annotation_annotation -LOCAL_MANIFEST_FILE := main/AndroidManifest.xml -LOCAL_MODULE := setup-wizard-lib -LOCAL_RESOURCE_DIR := \ - $(LOCAL_PATH)/main/res \ - $(LOCAL_PATH)/platform/res -LOCAL_SDK_VERSION := current -LOCAL_SRC_FILES := $(call all-java-files-under, main/src platform/src) -LOCAL_MIN_SDK_VERSION := 23 - -include $(BUILD_STATIC_JAVA_LIBRARY) - - -## -# Build gingerbread-compat library, which uses AppCompat support library to provide backwards -# compatibility back to SDK v9. -# - -include $(CLEAR_VARS) - -LOCAL_USE_AAPT2 := true - -LOCAL_AAPT2_ONLY := true - -LOCAL_MANIFEST_FILE := main/AndroidManifest.xml -LOCAL_MODULE := setup-wizard-lib-gingerbread-compat -LOCAL_RESOURCE_DIR := \ - $(LOCAL_PATH)/main/res \ - $(LOCAL_PATH)/gingerbread/res \ - $(LOCAL_PATH)/recyclerview/res -LOCAL_SDK_VERSION := current -LOCAL_SRC_FILES := $(call all-java-files-under, main/src gingerbread/src recyclerview/src) - -LOCAL_JAVA_LIBRARIES := \ - androidx.annotation_annotation - -LOCAL_SHARED_ANDROID_LIBRARIES := \ - androidx.core_core \ - androidx.legacy_legacy-support-core-ui \ - androidx.appcompat_appcompat \ - androidx.recyclerview_recyclerview - -LOCAL_MIN_SDK_VERSION := 14 - -include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/navigationbar/Android.bp b/navigationbar/Android.bp new file mode 100644 index 0000000..3868fe1 --- /dev/null +++ b/navigationbar/Android.bp @@ -0,0 +1,7 @@ +android_library { + name: "setup-wizard-navbar", + + sdk_version: "current", + resource_dirs: ["res"], + srcs: ["src/**/*.java"], +} diff --git a/navigationbar/Android.mk b/navigationbar/Android.mk deleted file mode 100644 index 5036990..0000000 --- a/navigationbar/Android.mk +++ /dev/null @@ -1,9 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SDK_VERSION := current -LOCAL_MODULE := setup-wizard-navbar -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -include $(BUILD_STATIC_JAVA_LIBRARY) -- cgit v1.2.3 From a53b3983bf1477c8b2df96f5608bf449fc113b6e Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Fri, 24 Aug 2018 14:16:17 -0700 Subject: Import updated Android Setup Wizard Library 210155157 PiperOrigin-RevId: 210155157 Change-Id: I707512aab6ac57aaebb93275669ae16b92b0e66d --- library/LICENSE | 202 ++++++ .../setupwizardlib/items/ExpandableSwitchItem.java | 239 ++++--- .../android/setupwizardlib/items/SwitchItem.java | 178 +++-- .../util/LinkAccessibilityHelper.java | 467 +++++++------ .../setupwizardlib/view/NavigationBarButton.java | 233 ++++--- .../android/setupwizardlib/view/RichTextView.java | 318 +++++---- .../items/ButtonItemDrawingTest.java | 129 ++-- .../test/util/DrawingTestActivity.java | 3 +- .../util/LinkAccessibilityHelperTest.java | 598 +++++++++-------- .../items/ExpandableSwitchItemTest.java | 243 +++---- .../setupwizardlib/items/SwitchItemTest.java | 319 +++++---- .../util/DimensionConsistencyTest.java | 47 +- .../src/com/android/setupwizardlib/GlifLayout.java | 448 ++++++------- .../com/android/setupwizardlib/GlifListLayout.java | 236 +++---- .../setupwizardlib/GlifPatternDrawable.java | 489 +++++++------- .../setupwizardlib/SetupWizardItemsLayout.java | 36 +- .../android/setupwizardlib/SetupWizardLayout.java | 741 ++++++++++----------- .../setupwizardlib/SetupWizardListLayout.java | 249 ++++--- .../com/android/setupwizardlib/TemplateLayout.java | 411 ++++++------ .../gesture/ConsecutiveTapsGestureDetector.java | 147 ++-- .../android/setupwizardlib/items/AbstractItem.java | 64 +- .../items/AbstractItemHierarchy.java | 195 +++--- .../setupwizardlib/items/ButtonBarItem.java | 147 ++-- .../android/setupwizardlib/items/ButtonItem.java | 244 ++++--- .../com/android/setupwizardlib/items/IItem.java | 42 +- .../src/com/android/setupwizardlib/items/Item.java | 284 ++++---- .../android/setupwizardlib/items/ItemAdapter.java | 246 +++---- .../android/setupwizardlib/items/ItemGroup.java | 533 ++++++++------- .../setupwizardlib/items/ItemHierarchy.java | 113 ++-- .../android/setupwizardlib/items/ItemInflater.java | 32 +- .../setupwizardlib/items/ReflectionInflater.java | 201 +++--- .../setupwizardlib/items/SimpleInflater.java | 283 ++++---- .../com/android/setupwizardlib/span/LinkSpan.java | 197 +++--- .../android/setupwizardlib/span/SpanHelper.java | 20 +- .../setupwizardlib/template/ButtonFooterMixin.java | 259 ++++--- .../template/ColoredHeaderMixin.java | 69 +- .../setupwizardlib/template/HeaderMixin.java | 110 ++- .../android/setupwizardlib/template/IconMixin.java | 148 ++-- .../android/setupwizardlib/template/ListMixin.java | 328 +++++---- .../template/ListViewScrollHandlingDelegate.java | 90 ++- .../com/android/setupwizardlib/template/Mixin.java | 3 +- .../template/NavigationBarMixin.java | 93 ++- .../setupwizardlib/template/ProgressBarMixin.java | 182 +++-- .../template/RequireScrollMixin.java | 418 ++++++------ .../template/ScrollViewScrollHandlingDelegate.java | 75 +-- .../util/DrawableLayoutDirectionHelper.java | 114 ++-- .../setupwizardlib/util/FallbackThemeWrapper.java | 48 +- .../com/android/setupwizardlib/util/Partner.java | 300 +++++---- .../android/setupwizardlib/util/ResultCodes.java | 8 +- .../setupwizardlib/util/SystemBarHelper.java | 599 +++++++++-------- .../setupwizardlib/util/WizardManagerHelper.java | 731 ++++++++++---------- .../setupwizardlib/view/BottomScrollView.java | 131 ++-- .../setupwizardlib/view/ButtonBarLayout.java | 142 ++-- .../setupwizardlib/view/CheckableLinearLayout.java | 97 ++- .../setupwizardlib/view/FillContentLayout.java | 145 ++-- .../android/setupwizardlib/view/Illustration.java | 341 +++++----- .../setupwizardlib/view/IllustrationVideoView.java | 485 +++++++------- .../view/IntrinsicSizeFrameLayout.java | 104 +-- .../android/setupwizardlib/view/NavigationBar.java | 199 +++--- .../view/StatusBarBackgroundLayout.java | 116 ++-- .../setupwizardlib/view/StickyHeaderListView.java | 215 +++--- .../view/StickyHeaderScrollView.java | 125 ++-- .../view/TouchableMovementMethod.java | 86 ++- .../setupwizardlib/view/NavigationBarButton.java | 12 +- .../android/setupwizardlib/view/RichTextView.java | 245 ++++--- .../test/util/DrawingTestActivity.java | 3 +- .../setupwizardlib/DividerItemDecoration.java | 356 +++++----- .../setupwizardlib/GlifPreferenceLayout.java | 103 ++- .../android/setupwizardlib/GlifRecyclerLayout.java | 294 ++++---- .../SetupWizardPreferenceLayout.java | 98 ++- .../setupwizardlib/SetupWizardRecyclerLayout.java | 298 ++++----- .../setupwizardlib/items/ItemViewHolder.java | 70 +- .../setupwizardlib/items/RecyclerItemAdapter.java | 401 ++++++----- .../setupwizardlib/template/RecyclerMixin.java | 393 ++++++----- .../RecyclerViewScrollHandlingDelegate.java | 84 ++- .../setupwizardlib/view/HeaderRecyclerView.java | 408 ++++++------ .../view/StickyHeaderRecyclerView.java | 174 ++--- .../items/RecyclerItemAdapterTest.java | 218 +++--- .../setupwizardlib/template/RecyclerMixinTest.java | 176 +++-- .../test/DividerItemDecorationTest.java | 334 +++++----- .../test/GlifPreferenceLayoutTest.java | 116 ++-- .../test/GlifRecyclerLayoutTest.java | 250 ++++--- .../test/HeaderRecyclerViewTest.java | 259 ++++--- .../test/SetupWizardPreferenceLayoutTest.java | 118 ++-- .../test/SetupWizardRecyclerLayoutTest.java | 257 ++++--- .../RecyclerViewScrollHandlingDelegateTest.java | 91 ++- .../android/setupwizardlib/TemplateLayoutTest.java | 111 ++- .../template/ButtonFooterMixinTest.java | 256 +++---- .../template/ColoredHeaderMixinTest.java | 89 ++- .../setupwizardlib/template/HeaderMixinTest.java | 119 ++-- .../setupwizardlib/template/IconMixinTest.java | 189 +++--- .../setupwizardlib/template/ListMixinTest.java | 204 +++--- .../template/NavigationBarMixinTest.java | 107 ++- .../template/ProgressBarMixinTest.java | 212 +++--- .../template/TemplateLayoutMixinTest.java | 51 +- .../setupwizardlib/test/BottomScrollViewTest.java | 146 ++-- .../setupwizardlib/test/ButtonBarItemTest.java | 175 ++--- .../test/DrawableLayoutDirectionHelperTest.java | 189 +++--- .../setupwizardlib/test/GlifLayoutTest.java | 177 ++--- .../setupwizardlib/test/GlifListLayoutTest.java | 180 +++-- .../test/GlifPatternDrawableTest.java | 212 +++--- .../setupwizardlib/test/IllustrationTest.java | 53 +- .../setupwizardlib/test/ItemAdapterTest.java | 116 ++-- .../setupwizardlib/test/ItemInflaterTest.java | 45 +- .../setupwizardlib/test/ItemLayoutTest.java | 86 ++- .../com/android/setupwizardlib/test/ItemTest.java | 298 ++++----- .../test/ReflectionInflaterTest.java | 79 +-- .../setupwizardlib/test/SetupWizardLayoutTest.java | 390 +++++------ .../test/SetupWizardListLayoutTest.java | 171 ++--- .../setupwizardlib/test/SimpleInflaterTest.java | 47 +- .../setupwizardlib/test/SpanHelperTest.java | 28 +- .../test/StatusBarBackgroundLayoutTest.java | 70 +- .../setupwizardlib/test/SystemBarHelperTest.java | 432 ++++++------ .../test/util/DrawingTestHelper.java | 106 +-- .../setupwizardlib/test/util/MockWindow.java | 491 +++++++------- .../util/FallbackThemeWrapperTest.java | 60 +- .../com/android/setupwizardlib/GlifLayoutTest.java | 616 ++++++++--------- .../ConsecutiveTapsGestureDetectorTest.java | 117 ++-- .../setupwizardlib/items/ButtonItemTest.java | 256 +++---- .../setupwizardlib/items/ItemGroupTest.java | 513 +++++++------- .../robolectric/ExternalResources.java | 160 +++++ .../robolectric/SuwLibRobolectricTestRunner.java | 16 +- .../android/setupwizardlib/shadow/ShadowLog.java | 51 -- .../setupwizardlib/shadow/ShadowMediaPlayer.java | 82 --- .../android/setupwizardlib/span/LinkSpanTest.java | 112 ++-- .../ListViewScrollHandlingDelegateTest.java | 127 ++-- .../template/RequireScrollMixinTest.java | 233 ++++--- .../ScrollViewScrollHandlingDelegateTest.java | 77 +-- .../setupwizardlib/util/GlifDimensionTest.java | 148 ++-- .../android/setupwizardlib/util/GlifStyleTest.java | 104 ++- .../setupwizardlib/util/GlifV3StyleTest.java | 69 +- .../android/setupwizardlib/util/PartnerTest.java | 315 ++++----- .../util/WizardManagerHelperTest.java | 601 ++++++++--------- .../setupwizardlib/view/FillContentLayoutTest.java | 97 +-- .../view/IllustrationVideoViewTest.java | 459 ++++++------- .../setupwizardlib/view/RichTextViewTest.java | 340 +++++----- 136 files changed, 13925 insertions(+), 14310 deletions(-) create mode 100644 library/LICENSE create mode 100644 library/test/robotest/src/com/android/setupwizardlib/robolectric/ExternalResources.java delete mode 100644 library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java delete mode 100644 library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowMediaPlayer.java diff --git a/library/LICENSE b/library/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/library/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java b/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java index 71d1bb6..4b9347f 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java +++ b/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java @@ -28,7 +28,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.view.CheckableLinearLayout; @@ -37,140 +36,130 @@ import com.android.setupwizardlib.view.CheckableLinearLayout; * summary, and when that is clicked, will expand to show a longer summary. The end (right for LTR) * side is a switch which can be toggled by the user. * - * Note: It is highly recommended to use this item with recycler view rather than list view, because - * list view draws the touch ripple effect on top of the item, rather than letting the item handle - * it. Therefore you might see a double-ripple, one for the expandable area and one for the entire - * list item, when using this in list view. + *

Note: It is highly recommended to use this item with recycler view rather than list view, + * because list view draws the touch ripple effect on top of the item, rather than letting the item + * handle it. Therefore you might see a double-ripple, one for the expandable area and one for the + * entire list item, when using this in list view. */ public class ExpandableSwitchItem extends SwitchItem - implements OnCheckedChangeListener, OnClickListener { - - private CharSequence mCollapsedSummary; - private CharSequence mExpandedSummary; - private boolean mIsExpanded = false; - - public ExpandableSwitchItem() { - super(); - } - - public ExpandableSwitchItem(Context context, AttributeSet attrs) { - super(context, attrs); - final TypedArray a = - context.obtainStyledAttributes(attrs, R.styleable.SuwExpandableSwitchItem); - mCollapsedSummary = a.getText(R.styleable.SuwExpandableSwitchItem_suwCollapsedSummary); - mExpandedSummary = a.getText(R.styleable.SuwExpandableSwitchItem_suwExpandedSummary); - a.recycle(); - } - - @Override - protected int getDefaultLayoutResource() { - return R.layout.suw_items_expandable_switch; + implements OnCheckedChangeListener, OnClickListener { + + private CharSequence collapsedSummary; + private CharSequence expandedSummary; + private boolean isExpanded = false; + + public ExpandableSwitchItem() { + super(); + } + + public ExpandableSwitchItem(Context context, AttributeSet attrs) { + super(context, attrs); + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwExpandableSwitchItem); + collapsedSummary = a.getText(R.styleable.SuwExpandableSwitchItem_suwCollapsedSummary); + expandedSummary = a.getText(R.styleable.SuwExpandableSwitchItem_suwExpandedSummary); + a.recycle(); + } + + @Override + protected int getDefaultLayoutResource() { + return R.layout.suw_items_expandable_switch; + } + + @Override + public CharSequence getSummary() { + return isExpanded ? getExpandedSummary() : getCollapsedSummary(); + } + + /** @return True if the item is currently expanded. */ + public boolean isExpanded() { + return isExpanded; + } + + /** Sets whether the item should be expanded. */ + public void setExpanded(boolean expanded) { + if (isExpanded == expanded) { + return; } - - @Override - public CharSequence getSummary() { - return mIsExpanded ? getExpandedSummary() : getCollapsedSummary(); + isExpanded = expanded; + notifyItemChanged(); + } + + /** @return The summary shown when in collapsed state. */ + public CharSequence getCollapsedSummary() { + return collapsedSummary; + } + + /** + * Sets the summary text shown when the item is collapsed. Corresponds to the {@code + * app:suwCollapsedSummary} XML attribute. + */ + public void setCollapsedSummary(CharSequence collapsedSummary) { + this.collapsedSummary = collapsedSummary; + if (!isExpanded()) { + notifyChanged(); } - - /** - * @return True if the item is currently expanded. - */ - public boolean isExpanded() { - return mIsExpanded; + } + + /** @return The summary shown when in expanded state. */ + public CharSequence getExpandedSummary() { + return expandedSummary; + } + + /** + * Sets the summary text shown when the item is expanded. Corresponds to the {@code + * app:suwExpandedSummary} XML attribute. + */ + public void setExpandedSummary(CharSequence expandedSummary) { + this.expandedSummary = expandedSummary; + if (isExpanded()) { + notifyChanged(); } + } - /** - * Sets whether the item should be expanded. - */ - public void setExpanded(boolean expanded) { - if (mIsExpanded == expanded) { - return; - } - mIsExpanded = expanded; - notifyItemChanged(); - } + @Override + public void onBindView(View view) { + // TODO: If it is possible to detect, log a warning if this is being used with ListView. + super.onBindView(view); + View content = view.findViewById(R.id.suw_items_expandable_switch_content); + content.setOnClickListener(this); - /** - * @return The summary shown when in collapsed state. - */ - public CharSequence getCollapsedSummary() { - return mCollapsedSummary; + if (content instanceof CheckableLinearLayout) { + ((CheckableLinearLayout) content).setChecked(isExpanded()); } - /** - * Sets the summary text shown when the item is collapsed. Corresponds to the - * {@code app:suwCollapsedSummary} XML attribute. - */ - public void setCollapsedSummary(CharSequence collapsedSummary) { - mCollapsedSummary = collapsedSummary; - if (!isExpanded()) { - notifyChanged(); + tintCompoundDrawables(view); + + // Expandable switch item has focusability on the expandable layout on the left, and the + // switch on the right, but not the item itself. + view.setFocusable(false); + } + + @Override + public void onClick(View v) { + setExpanded(!isExpanded()); + } + + // Tint the expand arrow with the text color + private void tintCompoundDrawables(View view) { + final TypedArray a = + view.getContext().obtainStyledAttributes(new int[] {android.R.attr.textColorPrimary}); + final ColorStateList tintColor = a.getColorStateList(0); + a.recycle(); + + if (tintColor != null) { + TextView titleView = (TextView) view.findViewById(R.id.suw_items_title); + for (Drawable drawable : titleView.getCompoundDrawables()) { + if (drawable != null) { + drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); } - } - - /** - * @return The summary shown when in expanded state. - */ - public CharSequence getExpandedSummary() { - return mExpandedSummary; - } - - /** - * Sets the summary text shown when the item is expanded. Corresponds to the - * {@code app:suwExpandedSummary} XML attribute. - */ - public void setExpandedSummary(CharSequence expandedSummary) { - mExpandedSummary = expandedSummary; - if (isExpanded()) { - notifyChanged(); - } - } - - @Override - public void onBindView(View view) { - // TODO: If it is possible to detect, log a warning if this is being used with ListView. - super.onBindView(view); - View content = view.findViewById(R.id.suw_items_expandable_switch_content); - content.setOnClickListener(this); - - if (content instanceof CheckableLinearLayout) { - ((CheckableLinearLayout) content).setChecked(isExpanded()); - } - - tintCompoundDrawables(view); - - // Expandable switch item has focusability on the expandable layout on the left, and the - // switch on the right, but not the item itself. - view.setFocusable(false); - } - - @Override - public void onClick(View v) { - setExpanded(!isExpanded()); - } - - // Tint the expand arrow with the text color - private void tintCompoundDrawables(View view) { - final TypedArray a = view.getContext() - .obtainStyledAttributes(new int[] {android.R.attr.textColorPrimary}); - final ColorStateList tintColor = a.getColorStateList(0); - a.recycle(); - - if (tintColor != null) { - TextView titleView = (TextView) view.findViewById(R.id.suw_items_title); - for (Drawable drawable : titleView.getCompoundDrawables()) { - if (drawable != null) { - drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); - } - } - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - for (Drawable drawable : titleView.getCompoundDrawablesRelative()) { - if (drawable != null) { - drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); - } - } - } - + } + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + for (Drawable drawable : titleView.getCompoundDrawablesRelative()) { + if (drawable != null) { + drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); + } } + } } + } } diff --git a/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java b/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java index 8d828ac..56fa742 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java +++ b/library/gingerbread/src/com/android/setupwizardlib/items/SwitchItem.java @@ -18,12 +18,10 @@ package com.android.setupwizardlib.items; import android.content.Context; import android.content.res.TypedArray; +import androidx.appcompat.widget.SwitchCompat; import android.util.AttributeSet; import android.view.View; import android.widget.CompoundButton; - -import androidx.appcompat.widget.SwitchCompat; - import com.android.setupwizardlib.R; /** @@ -34,101 +32,93 @@ import com.android.setupwizardlib.R; */ public class SwitchItem extends Item implements CompoundButton.OnCheckedChangeListener { - /** - * Listener for check state changes of this switch item. - */ - public interface OnCheckedChangeListener { - - /** - * Callback when checked state of a {@link SwitchItem} is changed. - * - * @see #setOnCheckedChangeListener(OnCheckedChangeListener) - */ - void onCheckedChange(SwitchItem item, boolean isChecked); - } - - private boolean mChecked = false; - private OnCheckedChangeListener mListener; + /** Listener for check state changes of this switch item. */ + public interface OnCheckedChangeListener { /** - * Creates a default switch item. - */ - public SwitchItem() { - super(); - } - - /** - * Creates a switch item. This constructor is used for inflation from XML. + * Callback when checked state of a {@link SwitchItem} is changed. * - * @param context The context which this item is inflated in. - * @param attrs The XML attributes defined on the item. + * @see #setOnCheckedChangeListener(OnCheckedChangeListener) */ - public SwitchItem(Context context, AttributeSet attrs) { - super(context, attrs); - final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwSwitchItem); - mChecked = a.getBoolean(R.styleable.SuwSwitchItem_android_checked, false); - a.recycle(); + void onCheckedChange(SwitchItem item, boolean isChecked); + } + + private boolean checked = false; + private OnCheckedChangeListener listener; + + /** Creates a default switch item. */ + public SwitchItem() { + super(); + } + + /** + * Creates a switch item. This constructor is used for inflation from XML. + * + * @param context The context which this item is inflated in. + * @param attrs The XML attributes defined on the item. + */ + public SwitchItem(Context context, AttributeSet attrs) { + super(context, attrs); + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwSwitchItem); + checked = a.getBoolean(R.styleable.SuwSwitchItem_android_checked, false); + a.recycle(); + } + + /** Sets whether this item should be checked. */ + public void setChecked(boolean checked) { + if (this.checked != checked) { + this.checked = checked; + notifyItemChanged(); + if (listener != null) { + listener.onCheckedChange(this, checked); + } } - - /** - * Sets whether this item should be checked. - */ - public void setChecked(boolean checked) { - if (mChecked != checked) { - mChecked = checked; - notifyItemChanged(); - if (mListener != null) { - mListener.onCheckedChange(this, checked); - } - } - } - - /** - * @return True if this switch item is currently checked. - */ - public boolean isChecked() { - return mChecked; - } - - @Override - protected int getDefaultLayoutResource() { - return R.layout.suw_items_switch; - } - - /** - * Toggle the checked state of the switch, without invalidating the entire item. - * - * @param view The root view of this item, typically from the argument of onItemClick. - */ - public void toggle(View view) { - mChecked = !mChecked; - final SwitchCompat switchView = (SwitchCompat) view.findViewById(R.id.suw_items_switch); - switchView.setChecked(mChecked); - } - - @Override - public void onBindView(View view) { - super.onBindView(view); - final SwitchCompat switchView = (SwitchCompat) view.findViewById(R.id.suw_items_switch); - switchView.setOnCheckedChangeListener(null); - switchView.setChecked(mChecked); - switchView.setOnCheckedChangeListener(this); - switchView.setEnabled(isEnabled()); - } - - /** - * Sets a listener to listen for changes in checked state. This listener is invoked in both - * user toggling the switch and calls to {@link #setChecked(boolean)}. - */ - public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { - mListener = listener; - } - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mChecked = isChecked; - if (mListener != null) { - mListener.onCheckedChange(this, isChecked); - } + } + + /** @return True if this switch item is currently checked. */ + public boolean isChecked() { + return checked; + } + + @Override + protected int getDefaultLayoutResource() { + return R.layout.suw_items_switch; + } + + /** + * Toggle the checked state of the switch, without invalidating the entire item. + * + * @param view The root view of this item, typically from the argument of onItemClick. + */ + public void toggle(View view) { + checked = !checked; + final SwitchCompat switchView = (SwitchCompat) view.findViewById(R.id.suw_items_switch); + switchView.setChecked(checked); + } + + @Override + public void onBindView(View view) { + super.onBindView(view); + final SwitchCompat switchView = (SwitchCompat) view.findViewById(R.id.suw_items_switch); + switchView.setOnCheckedChangeListener(null); + switchView.setChecked(checked); + switchView.setOnCheckedChangeListener(this); + switchView.setEnabled(isEnabled()); + } + + /** + * Sets a listener to listen for changes in checked state. This listener is invoked in both user + * toggling the switch and calls to {@link #setChecked(boolean)}. + */ + public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { + this.listener = listener; + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + checked = isChecked; + if (listener != null) { + listener.onCheckedChange(this, isChecked); } + } } diff --git a/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java index a0ea379..c416a9e 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java +++ b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java @@ -19,6 +19,12 @@ package com.android.setupwizardlib.util; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.core.view.accessibility.AccessibilityNodeProviderCompat; +import androidx.customview.widget.ExploreByTouchHelper; import android.text.Layout; import android.text.Spanned; import android.text.style.ClickableSpan; @@ -28,14 +34,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.core.view.AccessibilityDelegateCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.view.accessibility.AccessibilityNodeProviderCompat; -import androidx.customview.widget.ExploreByTouchHelper; - import java.util.List; /** @@ -46,6 +44,7 @@ import java.util.List; * support for ClickableSpan accessibility. * *

Sample usage: + * *

  * LinkAccessibilityHelper mAccessibilityHelper;
  *
@@ -68,262 +67,260 @@ import java.util.List;
  */
 public class LinkAccessibilityHelper extends AccessibilityDelegateCompat {
 
-    private static final String TAG = "LinkAccessibilityHelper";
-
-    private final AccessibilityDelegateCompat mDelegate;
-
-    public LinkAccessibilityHelper(TextView view) {
-        this(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
-                // Platform support was added in O. This helper will be no-op
-                ? new AccessibilityDelegateCompat()
-                // Pre-O, we extend ExploreByTouchHelper to expose a virtual view hierarchy
-                : new PreOLinkAccessibilityHelper(view));
-    }
-
-    @VisibleForTesting
-    LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) {
-        mDelegate = delegate;
-    }
-
-    @Override
-    public void sendAccessibilityEvent(View host, int eventType) {
-        mDelegate.sendAccessibilityEvent(host, eventType);
+  private static final String TAG = "LinkAccessibilityHelper";
+
+  private final AccessibilityDelegateCompat delegate;
+
+  public LinkAccessibilityHelper(TextView view) {
+    this(
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+            // Platform support was added in O. This helper will be no-op
+            ? new AccessibilityDelegateCompat()
+            // Pre-O, we extend ExploreByTouchHelper to expose a virtual view hierarchy
+            : new PreOLinkAccessibilityHelper(view));
+  }
+
+  @VisibleForTesting
+  LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public void sendAccessibilityEvent(View host, int eventType) {
+    delegate.sendAccessibilityEvent(host, eventType);
+  }
+
+  @Override
+  public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
+    delegate.sendAccessibilityEventUnchecked(host, event);
+  }
+
+  @Override
+  public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+    return delegate.dispatchPopulateAccessibilityEvent(host, event);
+  }
+
+  @Override
+  public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+    delegate.onPopulateAccessibilityEvent(host, event);
+  }
+
+  @Override
+  public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+    delegate.onInitializeAccessibilityEvent(host, event);
+  }
+
+  @Override
+  public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+    delegate.onInitializeAccessibilityNodeInfo(host, info);
+  }
+
+  @Override
+  public boolean onRequestSendAccessibilityEvent(
+      ViewGroup host, View child, AccessibilityEvent event) {
+    return delegate.onRequestSendAccessibilityEvent(host, child, event);
+  }
+
+  @Override
+  public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
+    return delegate.getAccessibilityNodeProvider(host);
+  }
+
+  @Override
+  public boolean performAccessibilityAction(View host, int action, Bundle args) {
+    return delegate.performAccessibilityAction(host, action, args);
+  }
+
+  /**
+   * Dispatches hover event to the virtual view hierarchy. This method should be called in {@link
+   * View#dispatchHoverEvent(MotionEvent)}.
+   *
+   * @see ExploreByTouchHelper#dispatchHoverEvent(MotionEvent)
+   */
+  public boolean dispatchHoverEvent(MotionEvent event) {
+    return delegate instanceof ExploreByTouchHelper
+        && ((ExploreByTouchHelper) delegate).dispatchHoverEvent(event);
+  }
+
+  @VisibleForTesting
+  static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper {
+
+    private final Rect tempRect = new Rect();
+    private final TextView view;
+
+    PreOLinkAccessibilityHelper(TextView view) {
+      super(view);
+      this.view = view;
     }
 
     @Override
-    public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
-        mDelegate.sendAccessibilityEventUnchecked(host, event);
-    }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
-        return mDelegate.dispatchPopulateAccessibilityEvent(host, event);
-    }
-
-    @Override
-    public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
-        mDelegate.onPopulateAccessibilityEvent(host, event);
+    protected int getVirtualViewAt(float x, float y) {
+      final CharSequence text = view.getText();
+      if (text instanceof Spanned) {
+        final Spanned spannedText = (Spanned) text;
+        final int offset = getOffsetForPosition(view, x, y);
+        ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+        if (linkSpans.length == 1) {
+          ClickableSpan linkSpan = linkSpans[0];
+          return spannedText.getSpanStart(linkSpan);
+        }
+      }
+      return ExploreByTouchHelper.INVALID_ID;
     }
 
     @Override
-    public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
-        mDelegate.onInitializeAccessibilityEvent(host, event);
+    protected void getVisibleVirtualViews(List virtualViewIds) {
+      final CharSequence text = view.getText();
+      if (text instanceof Spanned) {
+        final Spanned spannedText = (Spanned) text;
+        ClickableSpan[] linkSpans =
+            spannedText.getSpans(0, spannedText.length(), ClickableSpan.class);
+        for (ClickableSpan span : linkSpans) {
+          virtualViewIds.add(spannedText.getSpanStart(span));
+        }
+      }
     }
 
     @Override
-    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
-        mDelegate.onInitializeAccessibilityNodeInfo(host, info);
+    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+      final ClickableSpan span = getSpanForOffset(virtualViewId);
+      if (span != null) {
+        event.setContentDescription(getTextForSpan(span));
+      } else {
+        Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+        event.setContentDescription(view.getText());
+      }
     }
 
     @Override
-    public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
-            AccessibilityEvent event) {
-        return mDelegate.onRequestSendAccessibilityEvent(host, child, event);
+    protected void onPopulateNodeForVirtualView(
+        int virtualViewId, AccessibilityNodeInfoCompat info) {
+      final ClickableSpan span = getSpanForOffset(virtualViewId);
+      if (span != null) {
+        info.setContentDescription(getTextForSpan(span));
+      } else {
+        Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+        info.setContentDescription(view.getText());
+      }
+      info.setFocusable(true);
+      info.setClickable(true);
+      getBoundsForSpan(span, tempRect);
+      if (tempRect.isEmpty()) {
+        Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
+        tempRect.set(0, 0, 1, 1);
+      }
+      info.setBoundsInParent(tempRect);
+      info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
     }
 
     @Override
-    public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
-        return mDelegate.getAccessibilityNodeProvider(host);
+    protected boolean onPerformActionForVirtualView(
+        int virtualViewId, int action, Bundle arguments) {
+      if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+        ClickableSpan span = getSpanForOffset(virtualViewId);
+        if (span != null) {
+          span.onClick(view);
+          return true;
+        } else {
+          Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+        }
+      }
+      return false;
     }
 
-    @Override
-    public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        return mDelegate.performAccessibilityAction(host, action, args);
+    private ClickableSpan getSpanForOffset(int offset) {
+      CharSequence text = view.getText();
+      if (text instanceof Spanned) {
+        Spanned spannedText = (Spanned) text;
+        ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+        if (spans.length == 1) {
+          return spans[0];
+        }
+      }
+      return null;
     }
 
-    /**
-     * Dispatches hover event to the virtual view hierarchy. This method should be called in
-     * {@link View#dispatchHoverEvent(MotionEvent)}.
-     *
-     * @see ExploreByTouchHelper#dispatchHoverEvent(MotionEvent)
-     */
-    public boolean dispatchHoverEvent(MotionEvent event) {
-        return mDelegate instanceof ExploreByTouchHelper
-                && ((ExploreByTouchHelper) mDelegate).dispatchHoverEvent(event);
+    private CharSequence getTextForSpan(ClickableSpan span) {
+      CharSequence text = view.getText();
+      if (text instanceof Spanned) {
+        Spanned spannedText = (Spanned) text;
+        return spannedText.subSequence(
+            spannedText.getSpanStart(span), spannedText.getSpanEnd(span));
+      }
+      return text;
     }
 
-    @VisibleForTesting
-    static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper {
-
-        private final Rect mTempRect = new Rect();
-        private final TextView mView;
-
-        PreOLinkAccessibilityHelper(TextView view) {
-            super(view);
-            mView = view;
-        }
-
-        @Override
-        protected int getVirtualViewAt(float x, float y) {
-            final CharSequence text = mView.getText();
-            if (text instanceof Spanned) {
-                final Spanned spannedText = (Spanned) text;
-                final int offset = getOffsetForPosition(mView, x, y);
-                ClickableSpan[] linkSpans =
-                        spannedText.getSpans(offset, offset, ClickableSpan.class);
-                if (linkSpans.length == 1) {
-                    ClickableSpan linkSpan = linkSpans[0];
-                    return spannedText.getSpanStart(linkSpan);
-                }
-            }
-            return ExploreByTouchHelper.INVALID_ID;
-        }
-
-        @Override
-        protected void getVisibleVirtualViews(List virtualViewIds) {
-            final CharSequence text = mView.getText();
-            if (text instanceof Spanned) {
-                final Spanned spannedText = (Spanned) text;
-                ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
-                        ClickableSpan.class);
-                for (ClickableSpan span : linkSpans) {
-                    virtualViewIds.add(spannedText.getSpanStart(span));
-                }
-            }
-        }
-
-        @Override
-        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
-            final ClickableSpan span = getSpanForOffset(virtualViewId);
-            if (span != null) {
-                event.setContentDescription(getTextForSpan(span));
+    // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for
+    // the section on the first line.
+    private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
+      CharSequence text = view.getText();
+      outRect.setEmpty();
+      if (text instanceof Spanned) {
+        final Layout layout = view.getLayout();
+        if (layout != null) {
+          Spanned spannedText = (Spanned) text;
+          final int spanStart = spannedText.getSpanStart(span);
+          final int spanEnd = spannedText.getSpanEnd(span);
+          final float xStart = layout.getPrimaryHorizontal(spanStart);
+          final float xEnd = layout.getPrimaryHorizontal(spanEnd);
+          final int lineStart = layout.getLineForOffset(spanStart);
+          final int lineEnd = layout.getLineForOffset(spanEnd);
+          layout.getLineBounds(lineStart, outRect);
+          if (lineEnd == lineStart) {
+            // If the span is on a single line, adjust both the left and right bounds
+            // so outrect is exactly bounding the span.
+            outRect.left = (int) Math.min(xStart, xEnd);
+            outRect.right = (int) Math.max(xStart, xEnd);
+          } else {
+            // If the span wraps across multiple lines, only use the first line (as
+            // returned by layout.getLineBounds above), and adjust the "start" of
+            // outrect to where the span starts, leaving the "end" of outrect at the end
+            // of the line. ("start" being left for LTR, and right for RTL)
+            if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
+              outRect.right = (int) xStart;
             } else {
-                Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
-                event.setContentDescription(mView.getText());
+              outRect.left = (int) xStart;
             }
-        }
-
-        @Override
-        protected void onPopulateNodeForVirtualView(
-                int virtualViewId,
-                AccessibilityNodeInfoCompat info) {
-            final ClickableSpan span = getSpanForOffset(virtualViewId);
-            if (span != null) {
-                info.setContentDescription(getTextForSpan(span));
-            } else {
-                Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
-                info.setContentDescription(mView.getText());
-            }
-            info.setFocusable(true);
-            info.setClickable(true);
-            getBoundsForSpan(span, mTempRect);
-            if (mTempRect.isEmpty()) {
-                Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
-                mTempRect.set(0, 0, 1, 1);
-            }
-            info.setBoundsInParent(mTempRect);
-            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
-        }
+          }
 
-        @Override
-        protected boolean onPerformActionForVirtualView(
-                int virtualViewId,
-                int action,
-                Bundle arguments) {
-            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
-                ClickableSpan span = getSpanForOffset(virtualViewId);
-                if (span != null) {
-                    span.onClick(mView);
-                    return true;
-                } else {
-                    Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
-                }
-            }
-            return false;
-        }
-
-        private ClickableSpan getSpanForOffset(int offset) {
-            CharSequence text = mView.getText();
-            if (text instanceof Spanned) {
-                Spanned spannedText = (Spanned) text;
-                ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
-                if (spans.length == 1) {
-                    return spans[0];
-                }
-            }
-            return null;
-        }
-
-        private CharSequence getTextForSpan(ClickableSpan span) {
-            CharSequence text = mView.getText();
-            if (text instanceof Spanned) {
-                Spanned spannedText = (Spanned) text;
-                return spannedText.subSequence(
-                        spannedText.getSpanStart(span),
-                        spannedText.getSpanEnd(span));
-            }
-            return text;
-        }
-
-        // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for
-        // the section on the first line.
-        private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
-            CharSequence text = mView.getText();
-            outRect.setEmpty();
-            if (text instanceof Spanned) {
-                final Layout layout = mView.getLayout();
-                if (layout != null) {
-                    Spanned spannedText = (Spanned) text;
-                    final int spanStart = spannedText.getSpanStart(span);
-                    final int spanEnd = spannedText.getSpanEnd(span);
-                    final float xStart = layout.getPrimaryHorizontal(spanStart);
-                    final float xEnd = layout.getPrimaryHorizontal(spanEnd);
-                    final int lineStart = layout.getLineForOffset(spanStart);
-                    final int lineEnd = layout.getLineForOffset(spanEnd);
-                    layout.getLineBounds(lineStart, outRect);
-                    if (lineEnd == lineStart) {
-                        // If the span is on a single line, adjust both the left and right bounds
-                        // so outrect is exactly bounding the span.
-                        outRect.left = (int) Math.min(xStart, xEnd);
-                        outRect.right = (int) Math.max(xStart, xEnd);
-                    } else {
-                        // If the span wraps across multiple lines, only use the first line (as
-                        // returned by layout.getLineBounds above), and adjust the "start" of
-                        // outrect to where the span starts, leaving the "end" of outrect at the end
-                        // of the line. ("start" being left for LTR, and right for RTL)
-                        if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
-                            outRect.right = (int) xStart;
-                        } else {
-                            outRect.left = (int) xStart;
-                        }
-                    }
-
-                    // Offset for padding
-                    outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
-                }
-            }
-            return outRect;
+          // Offset for padding
+          outRect.offset(view.getTotalPaddingLeft(), view.getTotalPaddingTop());
         }
+      }
+      return outRect;
+    }
 
-        // Compat implementation of TextView#getOffsetForPosition().
+    // Compat implementation of TextView#getOffsetForPosition().
 
-        private static int getOffsetForPosition(TextView view, float x, float y) {
-            if (view.getLayout() == null) return -1;
-            final int line = getLineAtCoordinate(view, y);
-            return getOffsetAtCoordinate(view, line, x);
-        }
+    private static int getOffsetForPosition(TextView view, float x, float y) {
+      if (view.getLayout() == null) {
+        return -1;
+      }
+      final int line = getLineAtCoordinate(view, y);
+      return getOffsetAtCoordinate(view, line, x);
+    }
 
-        private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
-            x -= view.getTotalPaddingLeft();
-            // Clamp the position to inside of the view.
-            x = Math.max(0.0f, x);
-            x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
-            x += view.getScrollX();
-            return x;
-        }
+    private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
+      x -= view.getTotalPaddingLeft();
+      // Clamp the position to inside of the view.
+      x = Math.max(0.0f, x);
+      x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
+      x += view.getScrollX();
+      return x;
+    }
 
-        private static int getLineAtCoordinate(TextView view, float y) {
-            y -= view.getTotalPaddingTop();
-            // Clamp the position to inside of the view.
-            y = Math.max(0.0f, y);
-            y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
-            y += view.getScrollY();
-            return view.getLayout().getLineForVertical((int) y);
-        }
+    private static int getLineAtCoordinate(TextView view, float y) {
+      y -= view.getTotalPaddingTop();
+      // Clamp the position to inside of the view.
+      y = Math.max(0.0f, y);
+      y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
+      y += view.getScrollY();
+      return view.getLayout().getLineForVertical((int) y);
+    }
 
-        private static int getOffsetAtCoordinate(TextView view, int line, float x) {
-            x = convertToLocalHorizontalCoordinate(view, x);
-            return view.getLayout().getOffsetForHorizontal(line, x);
-        }
+    private static int getOffsetAtCoordinate(TextView view, int line, float x) {
+      x = convertToLocalHorizontalCoordinate(view, x);
+      return view.getLayout().getOffsetForHorizontal(line, x);
     }
+  }
 }
diff --git a/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java b/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java
index 872cc9f..b2d9d79 100644
--- a/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java
+++ b/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java
@@ -23,11 +23,10 @@ import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.os.Build;
+import androidx.annotation.NonNull;
 import android.util.AttributeSet;
 import android.widget.Button;
 
-import androidx.annotation.NonNull;
-
 /**
  * Button for navigation bar, which includes tinting of its compound drawables to be used for dark
  * and light themes.
@@ -35,128 +34,144 @@ import androidx.annotation.NonNull;
 @SuppressLint("AppCompatCustomView")
 public class NavigationBarButton extends Button {
 
-    public NavigationBarButton(Context context) {
-        super(context);
-        init();
+  public NavigationBarButton(Context context) {
+    super(context);
+    init();
+  }
+
+  public NavigationBarButton(Context context, AttributeSet attrs) {
+    super(context, attrs);
+    init();
+  }
+
+  private void init() {
+    // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter,
+    // so manually getting it and wrapping it in the compat drawable.
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+      Drawable[] drawables = getCompoundDrawablesRelative();
+      for (int i = 0; i < drawables.length; i++) {
+        if (drawables[i] != null) {
+          drawables[i] = TintedDrawable.wrap(drawables[i]);
+        }
+      }
+      setCompoundDrawablesRelativeWithIntrinsicBounds(
+          drawables[0], drawables[1], drawables[2], drawables[3]);
     }
+  }
 
-    public NavigationBarButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init();
+  @Override
+  public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
+    if (left != null) {
+      left = TintedDrawable.wrap(left);
     }
-
-    private void init() {
-        // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter,
-        // so manually getting it and wrapping it in the compat drawable.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            Drawable[] drawables = getCompoundDrawablesRelative();
-            for (int i = 0; i < drawables.length; i++) {
-                if (drawables[i] != null) {
-                    drawables[i] = TintedDrawable.wrap(drawables[i]);
-                }
-            }
-            setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1],
-                    drawables[2], drawables[3]);
+    if (top != null) {
+      top = TintedDrawable.wrap(top);
+    }
+    if (right != null) {
+      right = TintedDrawable.wrap(right);
+    }
+    if (bottom != null) {
+      bottom = TintedDrawable.wrap(bottom);
+    }
+    super.setCompoundDrawables(left, top, right, bottom);
+    tintDrawables();
+  }
+
+  @Override
+  public void setCompoundDrawablesRelative(
+      Drawable start, Drawable top, Drawable end, Drawable bottom) {
+    if (start != null) {
+      start = TintedDrawable.wrap(start);
+    }
+    if (top != null) {
+      top = TintedDrawable.wrap(top);
+    }
+    if (end != null) {
+      end = TintedDrawable.wrap(end);
+    }
+    if (bottom != null) {
+      bottom = TintedDrawable.wrap(bottom);
+    }
+    super.setCompoundDrawablesRelative(start, top, end, bottom);
+    tintDrawables();
+  }
+
+  @Override
+  public void setTextColor(ColorStateList colors) {
+    super.setTextColor(colors);
+    tintDrawables();
+  }
+
+  private void tintDrawables() {
+    final ColorStateList textColors = getTextColors();
+    if (textColors != null) {
+      for (Drawable drawable : getAllCompoundDrawables()) {
+        if (drawable instanceof TintedDrawable) {
+          ((TintedDrawable) drawable).setTintListCompat(textColors);
         }
+      }
+      invalidate();
+    }
+  }
+
+  private Drawable[] getAllCompoundDrawables() {
+    Drawable[] drawables = new Drawable[6];
+    Drawable[] compoundDrawables = getCompoundDrawables();
+    drawables[0] = compoundDrawables[0]; // left
+    drawables[1] = compoundDrawables[1]; // top
+    drawables[2] = compoundDrawables[2]; // right
+    drawables[3] = compoundDrawables[3]; // bottom
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+      Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative();
+      drawables[4] = compoundDrawablesRelative[0]; // start
+      drawables[5] = compoundDrawablesRelative[2]; // end
+    }
+    return drawables;
+  }
+
+  // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0
+  // or above
+  private static class TintedDrawable extends LayerDrawable {
+
+    public static TintedDrawable wrap(Drawable drawable) {
+      if (drawable instanceof TintedDrawable) {
+        return (TintedDrawable) drawable;
+      }
+      return new TintedDrawable(drawable.mutate());
     }
 
-    @Override
-    public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
-        if (left != null) left = TintedDrawable.wrap(left);
-        if (top != null) top = TintedDrawable.wrap(top);
-        if (right != null) right = TintedDrawable.wrap(right);
-        if (bottom != null) bottom = TintedDrawable.wrap(bottom);
-        super.setCompoundDrawables(left, top, right, bottom);
-        tintDrawables();
+    private ColorStateList tintList = null;
+
+    TintedDrawable(Drawable wrapped) {
+      super(new Drawable[] {wrapped});
     }
 
     @Override
-    public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end,
-            Drawable bottom) {
-        if (start != null) start = TintedDrawable.wrap(start);
-        if (top != null) top = TintedDrawable.wrap(top);
-        if (end != null) end = TintedDrawable.wrap(end);
-        if (bottom != null) bottom = TintedDrawable.wrap(bottom);
-        super.setCompoundDrawablesRelative(start, top, end, bottom);
-        tintDrawables();
+    public boolean isStateful() {
+      return true;
     }
 
     @Override
-    public void setTextColor(ColorStateList colors) {
-        super.setTextColor(colors);
-        tintDrawables();
-    }
-
-    private void tintDrawables() {
-        final ColorStateList textColors = getTextColors();
-        if (textColors != null) {
-            for (Drawable drawable : getAllCompoundDrawables()) {
-                if (drawable instanceof TintedDrawable) {
-                    ((TintedDrawable) drawable).setTintListCompat(textColors);
-                }
-            }
-            invalidate();
-        }
+    public boolean setState(@NonNull int[] stateSet) {
+      boolean needsInvalidate = super.setState(stateSet);
+      boolean needsInvalidateForState = updateState();
+      return needsInvalidate || needsInvalidateForState;
     }
 
-    private Drawable[] getAllCompoundDrawables() {
-        Drawable[] drawables = new Drawable[6];
-        Drawable[] compoundDrawables = getCompoundDrawables();
-        drawables[0] = compoundDrawables[0];  // left
-        drawables[1] = compoundDrawables[1];  // top
-        drawables[2] = compoundDrawables[2];  // right
-        drawables[3] = compoundDrawables[3];  // bottom
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative();
-            drawables[4] = compoundDrawablesRelative[0];  // start
-            drawables[5] = compoundDrawablesRelative[2];  // end
-        }
-        return drawables;
+    public void setTintListCompat(ColorStateList colors) {
+      tintList = colors;
+      if (updateState()) {
+        invalidateSelf();
+      }
     }
 
-    // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0
-    // or above
-    private static class TintedDrawable extends LayerDrawable {
-
-        public static TintedDrawable wrap(Drawable drawable) {
-            if (drawable instanceof TintedDrawable) {
-                return (TintedDrawable) drawable;
-            }
-            return new TintedDrawable(drawable.mutate());
-        }
-
-        private ColorStateList mTintList = null;
-
-        TintedDrawable(Drawable wrapped) {
-            super(new Drawable[] { wrapped });
-        }
-
-        @Override
-        public boolean isStateful() {
-            return true;
-        }
-
-        @Override
-        public boolean setState(@NonNull int[] stateSet) {
-            boolean needsInvalidate = super.setState(stateSet);
-            boolean needsInvalidateForState = updateState();
-            return needsInvalidate || needsInvalidateForState;
-        }
-
-        public void setTintListCompat(ColorStateList colors) {
-            mTintList = colors;
-            if (updateState()) {
-                invalidateSelf();
-            }
-        }
-
-        private boolean updateState() {
-            if (mTintList != null) {
-                final int color = mTintList.getColorForState(getState(), 0);
-                setColorFilter(color, PorterDuff.Mode.SRC_IN);
-                return true;  // Needs invalidate
-            }
-            return false;
-        }
+    private boolean updateState() {
+      if (tintList != null) {
+        final int color = tintList.getColorForState(getState(), 0);
+        setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        return true; // Needs invalidate
+      }
+      return false;
     }
+  }
 }
diff --git a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java
index 1ee3219..5e50e7a 100644
--- a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java
+++ b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java
@@ -20,6 +20,8 @@ import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
+import androidx.core.view.ViewCompat;
+import androidx.appcompat.widget.AppCompatTextView;
 import android.text.Annotation;
 import android.text.SpannableString;
 import android.text.Spanned;
@@ -29,10 +31,6 @@ import android.text.style.TextAppearanceSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
-
-import androidx.appcompat.widget.AppCompatTextView;
-import androidx.core.view.ViewCompat;
-
 import com.android.setupwizardlib.span.LinkSpan;
 import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener;
 import com.android.setupwizardlib.span.SpanHelper;
@@ -40,178 +38,178 @@ import com.android.setupwizardlib.util.LinkAccessibilityHelper;
 import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod;
 
 /**
- * An extension of TextView that automatically replaces the annotation tags as specified in
- * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)}
+ * An extension of TextView that automatically replaces the annotation tags as specified in {@link
+ * SpanHelper#replaceSpan(android.text.Spannable, Object, Object)}
  */
 public class RichTextView extends AppCompatTextView implements OnLinkClickListener {
 
-    /* static section */
-
-    private static final String TAG = "RichTextView";
-
-    private static final String ANNOTATION_LINK = "link";
-    private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance";
-
-    /**
-     * Replace <annotation> tags in strings to become their respective types. Currently 2
-     * types are supported:
-     * 
    - *
  1. <annotation link="foobar"> will create a - * {@link com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key - * "foobar"
  2. - *
  3. <annotation textAppearance="TextAppearance.FooBar"> will create a - * {@link android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar
  4. - *
- */ - public static CharSequence getRichText(Context context, CharSequence text) { - if (text instanceof Spanned) { - final SpannableString spannable = new SpannableString(text); - final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class); - for (Annotation span : spans) { - final String key = span.getKey(); - if (ANNOTATION_TEXT_APPEARANCE.equals(key)) { - String textAppearance = span.getValue(); - final int style = context.getResources() - .getIdentifier(textAppearance, "style", context.getPackageName()); - if (style == 0) { - Log.w(TAG, "Cannot find resource: " + style); - } - final TextAppearanceSpan textAppearanceSpan = - new TextAppearanceSpan(context, style); - SpanHelper.replaceSpan(spannable, span, textAppearanceSpan); - } else if (ANNOTATION_LINK.equals(key)) { - LinkSpan link = new LinkSpan(span.getValue()); - SpanHelper.replaceSpan(spannable, span, link); - } - } - return spannable; + /* static section */ + + private static final String TAG = "RichTextView"; + + private static final String ANNOTATION_LINK = "link"; + private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance"; + + /** + * Replace <annotation> tags in strings to become their respective types. Currently 2 types + * are supported: + * + *
    + *
  1. <annotation link="foobar"> will create a {@link + * com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key "foobar" + *
  2. <annotation textAppearance="TextAppearance.FooBar"> will create a {@link + * android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar + *
+ */ + public static CharSequence getRichText(Context context, CharSequence text) { + if (text instanceof Spanned) { + final SpannableString spannable = new SpannableString(text); + final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class); + for (Annotation span : spans) { + final String key = span.getKey(); + if (ANNOTATION_TEXT_APPEARANCE.equals(key)) { + String textAppearance = span.getValue(); + final int style = + context + .getResources() + .getIdentifier(textAppearance, "style", context.getPackageName()); + if (style == 0) { + Log.w(TAG, "Cannot find resource: " + style); + } + final TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(context, style); + SpanHelper.replaceSpan(spannable, span, textAppearanceSpan); + } else if (ANNOTATION_LINK.equals(key)) { + LinkSpan link = new LinkSpan(span.getValue()); + SpanHelper.replaceSpan(spannable, span, link); } - return text; - } - - /* non-static section */ - - private LinkAccessibilityHelper mAccessibilityHelper; - private OnLinkClickListener mOnLinkClickListener; - - public RichTextView(Context context) { - super(context); - init(); - } - - public RichTextView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + } + return spannable; } - - private void init() { - mAccessibilityHelper = new LinkAccessibilityHelper(this); - ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper); + return text; + } + + /* non-static section */ + + private LinkAccessibilityHelper accessibilityHelper; + private OnLinkClickListener onLinkClickListener; + + public RichTextView(Context context) { + super(context); + init(); + } + + public RichTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + accessibilityHelper = new LinkAccessibilityHelper(this); + ViewCompat.setAccessibilityDelegate(this, accessibilityHelper); + } + + @Override + public void setText(CharSequence text, BufferType type) { + text = getRichText(getContext(), text); + // Set text first before doing anything else because setMovementMethod internally calls + // setText. This in turn ends up calling this method with mText as the first parameter + super.setText(text, type); + boolean hasLinks = hasLinks(text); + + if (hasLinks) { + // When a TextView has a movement method, it will set the view to clickable. This makes + // View.onTouchEvent always return true and consumes the touch event, essentially + // nullifying any return values of MovementMethod.onTouchEvent. + // To still allow propagating touch events to the parent when this view doesn't have + // links, we only set the movement method here if the text contains links. + setMovementMethod(TouchableLinkMovementMethod.getInstance()); + } else { + setMovementMethod(null); } - - @Override - public void setText(CharSequence text, BufferType type) { - text = getRichText(getContext(), text); - // Set text first before doing anything else because setMovementMethod internally calls - // setText. This in turn ends up calling this method with mText as the first parameter - super.setText(text, type); - boolean hasLinks = hasLinks(text); - - if (hasLinks) { - // When a TextView has a movement method, it will set the view to clickable. This makes - // View.onTouchEvent always return true and consumes the touch event, essentially - // nullifying any return values of MovementMethod.onTouchEvent. - // To still allow propagating touch events to the parent when this view doesn't have - // links, we only set the movement method here if the text contains links. - setMovementMethod(TouchableLinkMovementMethod.getInstance()); - } else { - setMovementMethod(null); - } - // ExploreByTouchHelper automatically enables focus for RichTextView - // even though it may not have any links. Causes problems during talkback - // as individual TextViews consume touch events and thereby reducing the focus window - // shown by Talkback. Disable focus if there are no links - setFocusable(hasLinks); - // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is - // focusable in touch mode, we may be focused when the screen is first shown, and starting - // a screen halfway scrolled down is confusing to the user. - if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { - setRevealOnFocusHint(false); - // setRevealOnFocusHint is a new API added in SDK 25. For lower SDK versions, do not - // call setFocusableInTouchMode. We won't get touch effect on those earlier versions, - // but the link will still work, and will prevent the scroll view from starting halfway - // down the page. - setFocusableInTouchMode(hasLinks); - } + // ExploreByTouchHelper automatically enables focus for RichTextView + // even though it may not have any links. Causes problems during talkback + // as individual TextViews consume touch events and thereby reducing the focus window + // shown by Talkback. Disable focus if there are no links + setFocusable(hasLinks); + // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is + // focusable in touch mode, we may be focused when the screen is first shown, and starting + // a screen halfway scrolled down is confusing to the user. + if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { + setRevealOnFocusHint(false); + // setRevealOnFocusHint is a new API added in SDK 25. For lower SDK versions, do not + // call setFocusableInTouchMode. We won't get touch effect on those earlier versions, + // but the link will still work, and will prevent the scroll view from starting halfway + // down the page. + setFocusableInTouchMode(hasLinks); } + } - private boolean hasLinks(CharSequence text) { - if (text instanceof Spanned) { - final ClickableSpan[] spans = - ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); - return spans.length > 0; - } - return false; + private boolean hasLinks(CharSequence text) { + if (text instanceof Spanned) { + final ClickableSpan[] spans = + ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); + return spans.length > 0; } - - @Override - @SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called - public boolean onTouchEvent(MotionEvent event) { - // Since View#onTouchEvent always return true if the view is clickable (which is the case - // when a TextView has a movement method), override the implementation to allow the movement - // method, if it implements TouchableMovementMethod, to say that the touch is not handled, - // allowing the event to bubble up to the parent view. - boolean superResult = super.onTouchEvent(event); - MovementMethod movementMethod = getMovementMethod(); - if (movementMethod instanceof TouchableMovementMethod) { - TouchableMovementMethod touchableMovementMethod = - (TouchableMovementMethod) movementMethod; - if (touchableMovementMethod.getLastTouchEvent() == event) { - return touchableMovementMethod.isLastTouchEventHandled(); - } - } - return superResult; + return false; + } + + @Override + @SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called + public boolean onTouchEvent(MotionEvent event) { + // Since View#onTouchEvent always return true if the view is clickable (which is the case + // when a TextView has a movement method), override the implementation to allow the movement + // method, if it implements TouchableMovementMethod, to say that the touch is not handled, + // allowing the event to bubble up to the parent view. + boolean superResult = super.onTouchEvent(event); + MovementMethod movementMethod = getMovementMethod(); + if (movementMethod instanceof TouchableMovementMethod) { + TouchableMovementMethod touchableMovementMethod = (TouchableMovementMethod) movementMethod; + if (touchableMovementMethod.getLastTouchEvent() == event) { + return touchableMovementMethod.isLastTouchEventHandled(); + } } + return superResult; + } - @Override - protected boolean dispatchHoverEvent(MotionEvent event) { - if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) { - return true; - } - return super.dispatchHoverEvent(event); + @Override + protected boolean dispatchHoverEvent(MotionEvent event) { + if (accessibilityHelper != null && accessibilityHelper.dispatchHoverEvent(event)) { + return true; } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a - // workaround, set the state on those drawables directly. - final int[] state = getDrawableState(); - for (Drawable drawable : getCompoundDrawablesRelative()) { - if (drawable != null) { - if (drawable.setState(state)) { - invalidateDrawable(drawable); - } - } - } + return super.dispatchHoverEvent(event); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a + // workaround, set the state on those drawables directly. + final int[] state = getDrawableState(); + for (Drawable drawable : getCompoundDrawablesRelative()) { + if (drawable != null) { + if (drawable.setState(state)) { + invalidateDrawable(drawable); + } } + } } + } - public void setOnLinkClickListener(OnLinkClickListener listener) { - mOnLinkClickListener = listener; - } + public void setOnLinkClickListener(OnLinkClickListener listener) { + onLinkClickListener = listener; + } - public OnLinkClickListener getOnLinkClickListener() { - return mOnLinkClickListener; - } + public OnLinkClickListener getOnLinkClickListener() { + return onLinkClickListener; + } - @Override - public boolean onLinkClick(LinkSpan span) { - if (mOnLinkClickListener != null) { - return mOnLinkClickListener.onLinkClick(span); - } - return false; + @Override + public boolean onLinkClick(LinkSpan span) { + if (onLinkClickListener != null) { + return onLinkClickListener.onLinkClick(span); } + return false; + } } diff --git a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java index f5b8253..7f51fa7 100644 --- a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/items/ButtonItemDrawingTest.java @@ -18,19 +18,16 @@ package com.android.setupwizardlib.items; import static org.junit.Assert.assertTrue; +import androidx.annotation.StyleRes; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.LinearLayout; - -import androidx.annotation.StyleRes; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.test.util.DrawingTestHelper; - import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,69 +36,65 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ButtonItemDrawingTest { - private static final int GLIF_ACCENT_COLOR = 0xff4285f4; - private static final int GLIF_V3_ACCENT_COLOR = 0xff1a73e8; - - // These tests need to be run on UI thread because button uses ValueAnimator - @Rule - public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); - - @Test - @UiThreadTest - public void drawButton_glif_shouldHaveAccentColoredButton() - throws InstantiationException, IllegalAccessException { - Button button = createButton(R.style.SuwThemeGlif_Light); - - DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); - drawingTestHelper.drawView(button); - - int accentPixelCount = - countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_ACCENT_COLOR); - assertTrue("> 10 pixels should be #4285f4. Found " + accentPixelCount, - accentPixelCount > 10); - } - - @Test - @UiThreadTest - public void drawButton_glifV3_shouldHaveAccentColoredButton() - throws InstantiationException, IllegalAccessException { - Button button = createButton(R.style.SuwThemeGlifV3_Light); - - DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); - drawingTestHelper.drawView(button); - - int accentPixelCount = - countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_V3_ACCENT_COLOR); - assertTrue("> 10 pixels should be #1a73e8. Found " + accentPixelCount, - accentPixelCount > 10); - } - - private Button createButton(@StyleRes int theme) - throws InstantiationException, IllegalAccessException { - final ViewGroup parent = new LinearLayout(DrawingTestHelper.createCanvasActivity(theme)); - TestButtonItem item = new TestButtonItem(); - item.setTheme(R.style.SuwButtonItem_Colored); - item.setText("foobar"); - - return item.createButton(parent); - } - - private int countPixelsWithColor(int[] pixels, int color) { - int count = 0; - for (int pixel : pixels) { - if (pixel == color) { - count++; - } - } - return count; + private static final int GLIF_ACCENT_COLOR = 0xff4285f4; + private static final int GLIF_V3_ACCENT_COLOR = 0xff1a73e8; + + // These tests need to be run on UI thread because button uses ValueAnimator + @Rule public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); + + @Test + @UiThreadTest + public void drawButton_glif_shouldHaveAccentColoredButton() + throws InstantiationException, IllegalAccessException { + Button button = createButton(R.style.SuwThemeGlif_Light); + + DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); + drawingTestHelper.drawView(button); + + int accentPixelCount = countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_ACCENT_COLOR); + assertTrue("> 10 pixels should be #4285f4. Found " + accentPixelCount, accentPixelCount > 10); + } + + @Test + @UiThreadTest + public void drawButton_glifV3_shouldHaveAccentColoredButton() + throws InstantiationException, IllegalAccessException { + Button button = createButton(R.style.SuwThemeGlifV3_Light); + + DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); + drawingTestHelper.drawView(button); + + int accentPixelCount = + countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_V3_ACCENT_COLOR); + assertTrue("> 10 pixels should be #1a73e8. Found " + accentPixelCount, accentPixelCount > 10); + } + + private Button createButton(@StyleRes int theme) + throws InstantiationException, IllegalAccessException { + final ViewGroup parent = new LinearLayout(DrawingTestHelper.createCanvasActivity(theme)); + TestButtonItem item = new TestButtonItem(); + item.setTheme(R.style.SuwButtonItem_Colored); + item.setText("foobar"); + + return item.createButton(parent); + } + + private int countPixelsWithColor(int[] pixels, int color) { + int count = 0; + for (int pixel : pixels) { + if (pixel == color) { + count++; + } } + return count; + } - private static class TestButtonItem extends ButtonItem { + private static class TestButtonItem extends ButtonItem { - @Override - public Button createButton(ViewGroup parent) { - // Make this method public for testing - return super.createButton(parent); - } + @Override + public Button createButton(ViewGroup parent) { + // Make this method public for testing + return super.createButton(parent); } + } } diff --git a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java index d3518f4..80e23e9 100644 --- a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java @@ -26,5 +26,4 @@ import androidx.appcompat.app.AppCompatActivity; * * @see DrawingTestHelper */ -public class DrawingTestActivity extends AppCompatActivity { -} +public class DrawingTestActivity extends AppCompatActivity {} diff --git a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java index da07b40..da5e16c 100644 --- a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java @@ -25,9 +25,10 @@ import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.os.Bundle; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; +import androidx.customview.widget.ExploreByTouchHelper; import android.text.SpannableStringBuilder; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -35,328 +36,323 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.TextView; - import androidx.core.text.BidiFormatter; -import androidx.core.view.AccessibilityDelegateCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; -import androidx.customview.widget.ExploreByTouchHelper; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.util.LinkAccessibilityHelper.PreOLinkAccessibilityHelper; - -import org.junit.Test; -import org.junit.runner.RunWith; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class LinkAccessibilityHelperTest { - private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); - - private TextView mTextView; - private TestPreOLinkAccessibilityHelper mHelper; - - private DisplayMetrics mDisplayMetrics; - - @Test - public void testGetVirtualViewAt() { - initTextView(); - final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10)); - assertEquals("Virtual view ID should be 1", 1, virtualViewId); - } - - @Test - public void testGetVirtualViewAtHost() { - initTextView(); - final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100)); - assertEquals("Virtual view ID should be INVALID_ID", - ExploreByTouchHelper.INVALID_ID, virtualViewId); - } - - @Test - public void testGetVisibleVirtualViews() { - initTextView(); - List virtualViewIds = new ArrayList<>(); - mHelper.getVisibleVirtualViews(virtualViewIds); - - assertEquals("VisibleVirtualViews should be [1]", - Collections.singletonList(1), virtualViewIds); - } - - @Test - public void testOnPopulateEventForVirtualView() { - initTextView(); - AccessibilityEvent event = AccessibilityEvent.obtain(); - mHelper.onPopulateEventForVirtualView(1, event); - - // LinkSpan is set on substring(1, 2) of "Hello world" --> "e" - assertEquals("LinkSpan description should be \"e\"", - "e", event.getContentDescription().toString()); - - event.recycle(); - } - - @Test - public void testOnPopulateEventForVirtualViewHost() { - initTextView(); - AccessibilityEvent event = AccessibilityEvent.obtain(); - mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event); - - assertEquals("Host view description should be \"Hello world\"", "Hello world", - event.getContentDescription().toString()); - - event.recycle(); - } - - @Test - public void testOnPopulateNodeForVirtualView() { - initTextView(); - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(1, info); - - assertEquals("LinkSpan description should be \"e\"", - "e", info.getContentDescription().toString()); - assertTrue("LinkSpan should be focusable", info.isFocusable()); - assertTrue("LinkSpan should be clickable", info.isClickable()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should be (10.5dp, 0dp, 18.5dp, 20.5dp)", - new Rect(dp2Px(10.5f), dp2Px(0f), dp2Px(18.5f), dp2Px(20.5f)), bounds); - - info.recycle(); - } - - @Test - public void testNullLayout() { - initTextView(); - // Setting the padding will cause the layout to be null-ed out. - mTextView.setPadding(1, 1, 1, 1); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(0, info); - - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should be (0, 0, 1, 1)", - new Rect(0, 0, 1, 1), bounds); - - info.recycle(); - } - - @Test - public void testRtlLayout() { - SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום"); - ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); - initTextView(ssb); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(1, info); - - assertEquals("LinkSpan description should be \"כ\"", - "כ", info.getContentDescription().toString()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should be (481.5dp, 0dp, 489.5dp, 20.5dp)", - new Rect(dp2Px(481.5f), dp2Px(0f), dp2Px(489.5f), dp2Px(20.5f)), bounds); - - info.recycle(); - } - - @Test - public void testMultilineLink() { - SpannableStringBuilder ssb = new SpannableStringBuilder( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); + + private TextView mTextView; + private TestPreOLinkAccessibilityHelper mHelper; + + private DisplayMetrics mDisplayMetrics; + + @Test + public void testGetVirtualViewAt() { + initTextView(); + final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10)); + assertEquals("Virtual view ID should be 1", 1, virtualViewId); + } + + @Test + public void testGetVirtualViewAtHost() { + initTextView(); + final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100)); + assertEquals( + "Virtual view ID should be INVALID_ID", ExploreByTouchHelper.INVALID_ID, virtualViewId); + } + + @Test + public void testGetVisibleVirtualViews() { + initTextView(); + List virtualViewIds = new ArrayList<>(); + mHelper.getVisibleVirtualViews(virtualViewIds); + + assertEquals("VisibleVirtualViews should be [1]", Collections.singletonList(1), virtualViewIds); + } + + @Test + public void testOnPopulateEventForVirtualView() { + initTextView(); + AccessibilityEvent event = AccessibilityEvent.obtain(); + mHelper.onPopulateEventForVirtualView(1, event); + + // LinkSpan is set on substring(1, 2) of "Hello world" --> "e" + assertEquals( + "LinkSpan description should be \"e\"", "e", event.getContentDescription().toString()); + + event.recycle(); + } + + @Test + public void testOnPopulateEventForVirtualViewHost() { + initTextView(); + AccessibilityEvent event = AccessibilityEvent.obtain(); + mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event); + + assertEquals( + "Host view description should be \"Hello world\"", + "Hello world", + event.getContentDescription().toString()); + + event.recycle(); + } + + @Test + public void testOnPopulateNodeForVirtualView() { + initTextView(); + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(1, info); + + assertEquals( + "LinkSpan description should be \"e\"", "e", info.getContentDescription().toString()); + assertTrue("LinkSpan should be focusable", info.isFocusable()); + assertTrue("LinkSpan should be clickable", info.isClickable()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should be (10.5dp, 0dp, 18.5dp, 20.5dp)", + new Rect(dp2Px(10.5f), dp2Px(0f), dp2Px(18.5f), dp2Px(20.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testNullLayout() { + initTextView(); + // Setting the padding will cause the layout to be null-ed out. + mTextView.setPadding(1, 1, 1, 1); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(0, info); + + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals("LinkSpan bounds should be (0, 0, 1, 1)", new Rect(0, 0, 1, 1), bounds); + + info.recycle(); + } + + @Test + public void testRtlLayout() { + SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום"); + ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); + initTextView(ssb); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(1, info); + + assertEquals( + "LinkSpan description should be \"כ\"", "כ", info.getContentDescription().toString()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should be (481.5dp, 0dp, 489.5dp, 20.5dp)", + new Rect(dp2Px(481.5f), dp2Px(0f), dp2Px(489.5f), dp2Px(20.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testMultilineLink() { + SpannableStringBuilder ssb = + new SpannableStringBuilder( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Praesent accumsan efficitur eros eu porttitor."); - ssb.setSpan(LINK_SPAN, 51, 74, 0 /* flags */); - initTextView(ssb); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(51, info); - - assertEquals("LinkSpan description should match the span", - "elit. Praesent accumsan", info.getContentDescription().toString()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should match first line of the span", - new Rect(dp2Px(343f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), bounds); - - info.recycle(); + ssb.setSpan(LINK_SPAN, 51, 74, 0 /* flags */); + initTextView(ssb); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(51, info); + + assertEquals( + "LinkSpan description should match the span", + "elit. Praesent accumsan", + info.getContentDescription().toString()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should match first line of the span", + new Rect(dp2Px(343f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testRtlMultilineLink() { + String iwLoremIpsum = + "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " + + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + + "דפים המחשב מיזמים ב."; + SpannableStringBuilder ssb = new SpannableStringBuilder(iwLoremIpsum); + ssb.setSpan(LINK_SPAN, 50, 100, 0 /* flags */); + initTextView(ssb); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView(50, info); + + assertEquals( + "LinkSpan description should match the span", + iwLoremIpsum.substring(50, 100), + info.getContentDescription().toString()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should match the first line of the span", + new Rect(dp2Px(0f), dp2Px(0f), dp2Px(150f), dp2Px(19.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testBidiMultilineLink() { + String iwLoremIpsum = + "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " + + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + + "דפים המחשב מיזמים ב."; + BidiFormatter formatter = BidiFormatter.getInstance(false /* rtlContext */); + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append("hello ").append(formatter.unicodeWrap(iwLoremIpsum)).append(" world"); + ssb.setSpan( + LINK_SPAN, + "hello ".length() + 2, // Add two for the characters added by BidiFormatter + "hello ".length() + 2 + iwLoremIpsum.length(), + 0 /* flags */); + initTextView(ssb); + + AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); + mHelper.onPopulateNodeForVirtualView("hello ".length() + 2, info); + + assertEquals( + "LinkSpan description should match the span", + iwLoremIpsum, + info.getContentDescription().toString()); + Rect bounds = new Rect(); + info.getBoundsInParent(bounds); + assertEquals( + "LinkSpan bounds should match the first line of the span", + new Rect(dp2Px(491.5f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), + bounds); + + info.recycle(); + } + + @Test + public void testMethodDelegation() { + initTextView(); + AccessibilityDelegateCompat delegate = mock(AccessibilityDelegateCompat.class); + LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate); + + AccessibilityEvent accessibilityEvent = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED); + + helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED); + verify(delegate) + .sendAccessibilityEvent(same(mTextView), eq(AccessibilityEvent.TYPE_VIEW_CLICKED)); + + helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent); + verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent)); + + helper.performAccessibilityAction( + mTextView, AccessibilityActionCompat.ACTION_CLICK.getId(), Bundle.EMPTY); + verify(delegate) + .performAccessibilityAction( + same(mTextView), eq(AccessibilityActionCompat.ACTION_CLICK.getId()), eq(Bundle.EMPTY)); + + helper.dispatchPopulateAccessibilityEvent(mTextView, accessibilityEvent); + verify(delegate).dispatchPopulateAccessibilityEvent(same(mTextView), same(accessibilityEvent)); + + helper.getAccessibilityNodeProvider(mTextView); + verify(delegate).getAccessibilityNodeProvider(same(mTextView)); + + helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent); + verify(delegate).onInitializeAccessibilityEvent(same(mTextView), eq(accessibilityEvent)); + + AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain(); + helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo); + verify(delegate) + .onInitializeAccessibilityNodeInfo(same(mTextView), same(accessibilityNodeInfo)); + + helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent); + verify(delegate).onPopulateAccessibilityEvent(same(mTextView), same(accessibilityEvent)); + + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext()); + helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent); + verify(delegate) + .onRequestSendAccessibilityEvent(same(parent), same(mTextView), same(accessibilityEvent)); + } + + private void initTextView() { + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); + initTextView(ssb); + } + + private void initTextView(CharSequence text) { + mTextView = new TextView(InstrumentationRegistry.getContext()); + mTextView.setSingleLine(false); + mTextView.setText(text); + mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + mHelper = new TestPreOLinkAccessibilityHelper(mTextView); + + int measureExactly500dp = + View.MeasureSpec.makeMeasureSpec(dp2Px(500), View.MeasureSpec.EXACTLY); + mTextView.measure(measureExactly500dp, measureExactly500dp); + mTextView.layout(dp2Px(0), dp2Px(0), dp2Px(500), dp2Px(500)); + } + + private int dp2Px(float dp) { + if (mDisplayMetrics == null) { + mDisplayMetrics = InstrumentationRegistry.getContext().getResources().getDisplayMetrics(); } + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics); + } - @Test - public void testRtlMultilineLink() { - String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " - + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " - + "דפים המחשב מיזמים ב."; - SpannableStringBuilder ssb = new SpannableStringBuilder(iwLoremIpsum); - ssb.setSpan(LINK_SPAN, 50, 100, 0 /* flags */); - initTextView(ssb); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView(50, info); - - assertEquals("LinkSpan description should match the span", - iwLoremIpsum.substring(50, 100), - info.getContentDescription().toString()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should match the first line of the span", - new Rect(dp2Px(0f), dp2Px(0f), dp2Px(150f), dp2Px(19.5f)), bounds); - - info.recycle(); - } + public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper { - @Test - public void testBidiMultilineLink() { - String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " - + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " - + "דפים המחשב מיזמים ב."; - BidiFormatter formatter = BidiFormatter.getInstance(false /* rtlContext */); - SpannableStringBuilder ssb = new SpannableStringBuilder(); - ssb.append("hello ").append(formatter.unicodeWrap(iwLoremIpsum)).append(" world"); - ssb.setSpan(LINK_SPAN, - "hello ".length() + 2, // Add two for the characters added by BidiFormatter - "hello ".length() + 2 + iwLoremIpsum.length(), - 0 /* flags */); - initTextView(ssb); - - AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - mHelper.onPopulateNodeForVirtualView("hello ".length() + 2, info); - - assertEquals("LinkSpan description should match the span", - iwLoremIpsum, - info.getContentDescription().toString()); - Rect bounds = new Rect(); - info.getBoundsInParent(bounds); - assertEquals("LinkSpan bounds should match the first line of the span", - new Rect(dp2Px(491.5f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), bounds); - - info.recycle(); + TestPreOLinkAccessibilityHelper(TextView view) { + super(view); } - @Test - public void testMethodDelegation() { - initTextView(); - AccessibilityDelegateCompat delegate = mock(AccessibilityDelegateCompat.class); - LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate); - - AccessibilityEvent accessibilityEvent = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED); - - helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED); - verify(delegate).sendAccessibilityEvent( - same(mTextView), - eq(AccessibilityEvent.TYPE_VIEW_CLICKED)); - - helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent); - verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent)); - - helper.performAccessibilityAction( - mTextView, - AccessibilityActionCompat.ACTION_CLICK.getId(), - Bundle.EMPTY); - verify(delegate).performAccessibilityAction( - same(mTextView), - eq(AccessibilityActionCompat.ACTION_CLICK.getId()), - eq(Bundle.EMPTY)); - - helper.dispatchPopulateAccessibilityEvent( - mTextView, - accessibilityEvent); - verify(delegate).dispatchPopulateAccessibilityEvent( - same(mTextView), - same(accessibilityEvent)); - - helper.getAccessibilityNodeProvider(mTextView); - verify(delegate).getAccessibilityNodeProvider(same(mTextView)); - - helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent); - verify(delegate).onInitializeAccessibilityEvent( - same(mTextView), - eq(accessibilityEvent)); - - AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain(); - helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo); - verify(delegate).onInitializeAccessibilityNodeInfo( - same(mTextView), - same(accessibilityNodeInfo)); - - helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent); - verify(delegate).onPopulateAccessibilityEvent( - same(mTextView), - same(accessibilityEvent)); - - FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext()); - helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent); - verify(delegate).onRequestSendAccessibilityEvent( - same(parent), - same(mTextView), - same(accessibilityEvent)); + @Override + public int getVirtualViewAt(float x, float y) { + return super.getVirtualViewAt(x, y); } - private void initTextView() { - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); - initTextView(ssb); + @Override + public void getVisibleVirtualViews(List virtualViewIds) { + super.getVisibleVirtualViews(virtualViewIds); } - private void initTextView(CharSequence text) { - mTextView = new TextView(InstrumentationRegistry.getContext()); - mTextView.setSingleLine(false); - mTextView.setText(text); - mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - mHelper = new TestPreOLinkAccessibilityHelper(mTextView); - - int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500), - View.MeasureSpec.EXACTLY); - mTextView.measure(measureExactly500dp, measureExactly500dp); - mTextView.layout(dp2Px(0), dp2Px(0), dp2Px(500), dp2Px(500)); + @Override + public void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { + super.onPopulateEventForVirtualView(virtualViewId, event); } - private int dp2Px(float dp) { - if (mDisplayMetrics == null) { - mDisplayMetrics = - InstrumentationRegistry.getContext().getResources().getDisplayMetrics(); - } - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics); + @Override + public void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat info) { + super.onPopulateNodeForVirtualView(virtualViewId, info); } - public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper { - - TestPreOLinkAccessibilityHelper(TextView view) { - super(view); - } - - @Override - public int getVirtualViewAt(float x, float y) { - return super.getVirtualViewAt(x, y); - } - - @Override - public void getVisibleVirtualViews(List virtualViewIds) { - super.getVisibleVirtualViews(virtualViewIds); - } - - @Override - public void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { - super.onPopulateEventForVirtualView(virtualViewId, event); - } - - @Override - public void onPopulateNodeForVirtualView(int virtualViewId, - AccessibilityNodeInfoCompat info) { - super.onPopulateNodeForVirtualView(virtualViewId, info); - } - - @Override - public boolean onPerformActionForVirtualView(int virtualViewId, int action, - Bundle arguments) { - return super.onPerformActionForVirtualView(virtualViewId, action, arguments); - } + @Override + public boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) { + return super.onPerformActionForVirtualView(virtualViewId, action, arguments); } + } } diff --git a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java index 6192061..f988563 100644 --- a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java +++ b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/ExpandableSwitchItemTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.items; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -27,133 +26,139 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.view.CheckableLinearLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class ExpandableSwitchItemTest { - private TextView mSummaryView; - private ExpandableSwitchItem mItem; - - @Before - public void setUp() { - mItem = new ExpandableSwitchItem(); - mItem.setTitle("TestTitle"); - mItem.setCollapsedSummary("TestSummary"); - mItem.setExpandedSummary("TestSummaryExpanded"); - } - - @Test - public void testInitialState() { - View view = createLayout(); - mItem.onBindView(view); - - assertEquals("Collapsed summary should be TestSummary", - "TestSummary", mItem.getCollapsedSummary()); - assertEquals("Expanded summary should be TestSummaryExpanded", - "TestSummaryExpanded", mItem.getExpandedSummary()); - - assertEquals("Should be collapsed initially", "TestSummary", mItem.getSummary()); - assertEquals("Summary view should display collapsed summary", - "TestSummary", mSummaryView.getText()); - - assertFalse("Expandable switch item itself should not be focusable", view.isFocusable()); - - View switchContent = view.findViewById(R.id.suw_items_expandable_switch_content); - assertThat(switchContent).isInstanceOf(CheckableLinearLayout.class); - assertThat(switchContent.isFocusable()) - .named("expandable content focusable") - .isTrue(); - } - - @Test - public void testExpanded() { - View view = createLayout(); - mItem.onBindView(view); - - mItem.setExpanded(true); - - assertEquals("Collapsed summary should be TestSummary", - "TestSummary", mItem.getCollapsedSummary()); - assertEquals("Expanded summary should be TestSummaryExpanded", - "TestSummaryExpanded", mItem.getExpandedSummary()); - - assertTrue("Should be expanded", mItem.isExpanded()); - assertEquals("getSummary should be expanded summary", - "TestSummaryExpanded", mItem.getSummary()); - } - - @Test - public void testCollapsed() { - View view = createLayout(); - mItem.onBindView(view); - - mItem.setExpanded(true); - assertTrue("Should be expanded", mItem.isExpanded()); - - mItem.setExpanded(false); - - assertEquals("Collapsed summary should be TestSummary", - "TestSummary", mItem.getCollapsedSummary()); - assertEquals("Expanded summary should be TestSummaryExpanded", - "TestSummaryExpanded", mItem.getExpandedSummary()); - - assertFalse("Should be expanded", mItem.isExpanded()); - assertEquals("getSummary should be collapsed summary", - "TestSummary", mItem.getSummary()); - } - - @Test - public void testClick() { - View view = createLayout(); - mItem.onBindView(view); - - assertFalse("Should not be expanded initially", mItem.isExpanded()); - - final View content = view.findViewById(R.id.suw_items_expandable_switch_content); - content.performClick(); - assertTrue("Should be expanded after clicking", mItem.isExpanded()); - - content.performClick(); - assertFalse("Should be collapsed again after clicking", mItem.isExpanded()); - } - - @Test - public void testDrawableState() { - final View view = - LayoutInflater.from(application).inflate(mItem.getLayoutResource(), null); - mItem.onBindView(view); - - final View titleView = view.findViewById(R.id.suw_items_title); - assertThat(titleView.getDrawableState()).asList().named("Drawable state") - .doesNotContain(android.R.attr.state_checked); - - mItem.setExpanded(true); - mItem.onBindView(view); - assertThat(titleView.getDrawableState()).asList().named("Drawable state") - .contains(android.R.attr.state_checked); - - mItem.setExpanded(false); - mItem.onBindView(view); - assertThat(titleView.getDrawableState()).asList().named("Drawable state") - .doesNotContain(android.R.attr.state_checked); - } - - private ViewGroup createLayout() { - ViewGroup root = - (ViewGroup) LayoutInflater.from(application) - .inflate(R.layout.suw_items_expandable_switch, null); - mSummaryView = root.findViewById(R.id.suw_items_summary); - - return root; - } + private TextView mSummaryView; + private ExpandableSwitchItem mItem; + + @Before + public void setUp() { + mItem = new ExpandableSwitchItem(); + mItem.setTitle("TestTitle"); + mItem.setCollapsedSummary("TestSummary"); + mItem.setExpandedSummary("TestSummaryExpanded"); + } + + @Test + public void testInitialState() { + View view = createLayout(); + mItem.onBindView(view); + + assertEquals( + "Collapsed summary should be TestSummary", "TestSummary", mItem.getCollapsedSummary()); + assertEquals( + "Expanded summary should be TestSummaryExpanded", + "TestSummaryExpanded", + mItem.getExpandedSummary()); + + assertEquals("Should be collapsed initially", "TestSummary", mItem.getSummary()); + assertEquals( + "Summary view should display collapsed summary", "TestSummary", mSummaryView.getText()); + + assertFalse("Expandable switch item itself should not be focusable", view.isFocusable()); + + View switchContent = view.findViewById(R.id.suw_items_expandable_switch_content); + assertThat(switchContent).isInstanceOf(CheckableLinearLayout.class); + assertThat(switchContent.isFocusable()).named("expandable content focusable").isTrue(); + } + + @Test + public void testExpanded() { + View view = createLayout(); + mItem.onBindView(view); + + mItem.setExpanded(true); + + assertEquals( + "Collapsed summary should be TestSummary", "TestSummary", mItem.getCollapsedSummary()); + assertEquals( + "Expanded summary should be TestSummaryExpanded", + "TestSummaryExpanded", + mItem.getExpandedSummary()); + + assertTrue("Should be expanded", mItem.isExpanded()); + assertEquals( + "getSummary should be expanded summary", "TestSummaryExpanded", mItem.getSummary()); + } + + @Test + public void testCollapsed() { + View view = createLayout(); + mItem.onBindView(view); + + mItem.setExpanded(true); + assertTrue("Should be expanded", mItem.isExpanded()); + + mItem.setExpanded(false); + + assertEquals( + "Collapsed summary should be TestSummary", "TestSummary", mItem.getCollapsedSummary()); + assertEquals( + "Expanded summary should be TestSummaryExpanded", + "TestSummaryExpanded", + mItem.getExpandedSummary()); + + assertFalse("Should be expanded", mItem.isExpanded()); + assertEquals("getSummary should be collapsed summary", "TestSummary", mItem.getSummary()); + } + + @Test + public void testClick() { + View view = createLayout(); + mItem.onBindView(view); + + assertFalse("Should not be expanded initially", mItem.isExpanded()); + + final View content = view.findViewById(R.id.suw_items_expandable_switch_content); + content.performClick(); + assertTrue("Should be expanded after clicking", mItem.isExpanded()); + + content.performClick(); + assertFalse("Should be collapsed again after clicking", mItem.isExpanded()); + } + + @Test + public void testDrawableState() { + final View view = LayoutInflater.from(application).inflate(mItem.getLayoutResource(), null); + mItem.onBindView(view); + + final View titleView = view.findViewById(R.id.suw_items_title); + assertThat(titleView.getDrawableState()) + .asList() + .named("Drawable state") + .doesNotContain(android.R.attr.state_checked); + + mItem.setExpanded(true); + mItem.onBindView(view); + assertThat(titleView.getDrawableState()) + .asList() + .named("Drawable state") + .contains(android.R.attr.state_checked); + + mItem.setExpanded(false); + mItem.onBindView(view); + assertThat(titleView.getDrawableState()) + .asList() + .named("Drawable state") + .doesNotContain(android.R.attr.state_checked); + } + + private ViewGroup createLayout() { + ViewGroup root = + (ViewGroup) + LayoutInflater.from(application).inflate(R.layout.suw_items_expandable_switch, null); + mSummaryView = root.findViewById(R.id.suw_items_summary); + + return root; + } } diff --git a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java index 4716c4d..0acc5fc 100644 --- a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java +++ b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/items/SwitchItemTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.items; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -26,6 +25,7 @@ import static org.robolectric.RuntimeEnvironment.application; import android.annotation.TargetApi; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import androidx.appcompat.widget.SwitchCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,12 +33,8 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; - -import androidx.appcompat.widget.SwitchCompat; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,214 +42,215 @@ import org.robolectric.Robolectric; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class SwitchItemTest { - private SwitchCompat mSwitch; + private SwitchCompat mSwitch; - @Test - public void defaultLayout_baselineAligned_shouldBeFalse() { - Assume.assumeTrue(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP); - LayoutInflater inflater = LayoutInflater.from(application); - SwitchItem item = new SwitchItem(); - LinearLayout layout = (LinearLayout) inflater.inflate(item.getDefaultLayoutResource(), - null); - assertThat(layout.isBaselineAligned()).isFalse(); - } + @Test + public void defaultLayout_baselineAligned_shouldBeFalse() { + Assume.assumeTrue(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP); + LayoutInflater inflater = LayoutInflater.from(application); + SwitchItem item = new SwitchItem(); + LinearLayout layout = (LinearLayout) inflater.inflate(item.getDefaultLayoutResource(), null); + assertThat(layout.isBaselineAligned()).isFalse(); + } + + @Test + public void verboseLayout_clipPadding_shouldBeFalse() { + Assume.assumeTrue(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP); + LayoutInflater inflater = LayoutInflater.from(application); + SwitchItem item = + new SwitchItem( + application, + Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.layout, "@layout/suw_items_switch_verbose") + .build()); + ViewGroup layout = (ViewGroup) inflater.inflate(item.getLayoutResource(), null); + assertThat(layout.getClipToPadding()).isFalse(); + } - @Test - public void verboseLayout_clipPadding_shouldBeFalse() { - Assume.assumeTrue(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP); - LayoutInflater inflater = LayoutInflater.from(application); - SwitchItem item = new SwitchItem(application, - Robolectric.buildAttributeSet() - .addAttribute(android.R.attr.layout, "@layout/suw_items_switch_verbose") - .build()); - ViewGroup layout = (ViewGroup) inflater.inflate(item.getLayoutResource(), null); - assertThat(layout.getClipToPadding()).isFalse(); - } + @Test + public void testChecked() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - @Test - public void testChecked() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + item.setChecked(true); - item.setChecked(true); + item.onBindView(view); - item.onBindView(view); + assertTrue("Switch should be checked", mSwitch.isChecked()); + } - assertTrue("Switch should be checked", mSwitch.isChecked()); - } + @Test + public void testNotChecked() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - @Test - public void testNotChecked() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + item.setChecked(false); - item.setChecked(false); + item.onBindView(view); - item.onBindView(view); + assertFalse("Switch should be unchecked", mSwitch.isChecked()); + } - assertFalse("Switch should be unchecked", mSwitch.isChecked()); - } - - @Test - public void testListener() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + @Test + public void testListener() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - item.setChecked(true); + item.setChecked(true); - final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); - item.setOnCheckedChangeListener(listener); + final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); + item.setOnCheckedChangeListener(listener); - item.onBindView(view); + item.onBindView(view); - assertTrue("Switch should be checked", mSwitch.isChecked()); - mSwitch.setChecked(false); + assertTrue("Switch should be checked", mSwitch.isChecked()); + mSwitch.setChecked(false); - assertTrue("Listener should be called", listener.mCalled); - assertFalse("Listener should not be checked", listener.mChecked); + assertTrue("Listener should be called", listener.mCalled); + assertFalse("Listener should not be checked", listener.mChecked); - mSwitch.setChecked(true); + mSwitch.setChecked(true); - assertTrue("Listener should be called", listener.mCalled); - assertTrue("Listener should be checked", listener.mChecked); - } + assertTrue("Listener should be called", listener.mCalled); + assertTrue("Listener should be checked", listener.mChecked); + } - @Test - public void testRebind() { - SwitchItem item1 = new SwitchItem(); - item1.setTitle("TestTitle1"); - item1.setSummary("TestSummary1"); - item1.setChecked(false); + @Test + public void testRebind() { + SwitchItem item1 = new SwitchItem(); + item1.setTitle("TestTitle1"); + item1.setSummary("TestSummary1"); + item1.setChecked(false); - SwitchItem item2 = new SwitchItem(); - item2.setTitle("TestTitle2"); - item2.setSummary("TestSummary2"); - item2.setChecked(true); + SwitchItem item2 = new SwitchItem(); + item2.setTitle("TestTitle2"); + item2.setSummary("TestSummary2"); + item2.setChecked(true); - View view = createLayout(); + View view = createLayout(); - item1.onBindView(view); - item2.onBindView(view); + item1.onBindView(view); + item2.onBindView(view); - // Switch should be bound to item2, and therefore checked - assertTrue("Switch should be checked", mSwitch.isChecked()); + // Switch should be bound to item2, and therefore checked + assertTrue("Switch should be checked", mSwitch.isChecked()); - // Switching the switch to false should change the checked state of item 2 only - mSwitch.setChecked(false); - assertFalse("Item1 should still be unchecked", item1.isChecked()); - assertFalse("Item2 should not be checked", item2.isChecked()); + // Switching the switch to false should change the checked state of item 2 only + mSwitch.setChecked(false); + assertFalse("Item1 should still be unchecked", item1.isChecked()); + assertFalse("Item2 should not be checked", item2.isChecked()); - // Switching the switch to true should change the checked state of item 2 only - mSwitch.setChecked(true); - assertFalse("Item1 should still be unchecked", item1.isChecked()); - assertTrue("Item2 should be checked", item2.isChecked()); - } + // Switching the switch to true should change the checked state of item 2 only + mSwitch.setChecked(true); + assertFalse("Item1 should still be unchecked", item1.isChecked()); + assertTrue("Item2 should be checked", item2.isChecked()); + } - @Test - public void testListenerSetChecked() { - // Check that calling setChecked on the item will also call the listener. + @Test + public void testListenerSetChecked() { + // Check that calling setChecked on the item will also call the listener. - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - item.setChecked(true); + item.setChecked(true); - final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); - item.setOnCheckedChangeListener(listener); + final TestOnCheckedChangeListener listener = new TestOnCheckedChangeListener(); + item.setOnCheckedChangeListener(listener); - item.onBindView(view); + item.onBindView(view); - assertTrue("Switch should be checked", mSwitch.isChecked()); - item.setChecked(false); + assertTrue("Switch should be checked", mSwitch.isChecked()); + item.setChecked(false); - assertTrue("Listener should be called", listener.mCalled); - assertFalse("Listener should not be checked", listener.mChecked); + assertTrue("Listener should be called", listener.mCalled); + assertFalse("Listener should not be checked", listener.mChecked); - item.setChecked(true); + item.setChecked(true); - assertTrue("Listener should be called", listener.mCalled); - assertTrue("Listener should be checked", listener.mChecked); - } + assertTrue("Listener should be called", listener.mCalled); + assertTrue("Listener should be checked", listener.mChecked); + } - @Test - public void testToggle() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - View view = createLayout(); + @Test + public void testToggle() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + View view = createLayout(); - item.setChecked(true); - item.onBindView(view); + item.setChecked(true); + item.onBindView(view); - assertTrue("Switch should be checked", mSwitch.isChecked()); + assertTrue("Switch should be checked", mSwitch.isChecked()); - item.toggle(view); + item.toggle(view); - assertFalse("Switch should be unchecked", mSwitch.isChecked()); - } + assertFalse("Switch should be unchecked", mSwitch.isChecked()); + } - @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) - @Config(minSdk = VERSION_CODES.JELLY_BEAN_MR1) - @Test - public void testAccessibility() { - SwitchItem item = new SwitchItem(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + @Config(minSdk = VERSION_CODES.JELLY_BEAN_MR1) + @Test + public void testAccessibility() { + SwitchItem item = new SwitchItem(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); - View view = LayoutInflater.from(application).inflate(R.layout.suw_items_switch, null); - item.onBindView(view); + View view = LayoutInflater.from(application).inflate(R.layout.suw_items_switch, null); + item.onBindView(view); - final View titleView = view.findViewById(R.id.suw_items_title); - assertEquals("Title view should label for switch", - R.id.suw_items_switch, titleView.getLabelFor()); - } + final View titleView = view.findViewById(R.id.suw_items_title); + assertEquals( + "Title view should label for switch", R.id.suw_items_switch, titleView.getLabelFor()); + } - private ViewGroup createLayout() { - ViewGroup root = new FrameLayout(application); + private ViewGroup createLayout() { + ViewGroup root = new FrameLayout(application); - TextView titleView = new TextView(application); - titleView.setId(R.id.suw_items_title); - root.addView(titleView); + TextView titleView = new TextView(application); + titleView.setId(R.id.suw_items_title); + root.addView(titleView); - TextView summaryView = new TextView(application); - summaryView.setId(R.id.suw_items_summary); - root.addView(summaryView); + TextView summaryView = new TextView(application); + summaryView.setId(R.id.suw_items_summary); + root.addView(summaryView); - FrameLayout iconContainer = new FrameLayout(application); - iconContainer.setId(R.id.suw_items_icon_container); - root.addView(iconContainer); + FrameLayout iconContainer = new FrameLayout(application); + iconContainer.setId(R.id.suw_items_icon_container); + root.addView(iconContainer); - ImageView iconView = new ImageView(application); - iconView.setId(R.id.suw_items_icon); - iconContainer.addView(iconView); + ImageView iconView = new ImageView(application); + iconView.setId(R.id.suw_items_icon); + iconContainer.addView(iconView); - mSwitch = new SwitchCompat(application); - mSwitch.setId(R.id.suw_items_switch); - root.addView(mSwitch); + mSwitch = new SwitchCompat(application); + mSwitch.setId(R.id.suw_items_switch); + root.addView(mSwitch); - return root; - } + return root; + } - private static class TestOnCheckedChangeListener implements SwitchItem.OnCheckedChangeListener { + private static class TestOnCheckedChangeListener implements SwitchItem.OnCheckedChangeListener { - boolean mCalled = false; - boolean mChecked = false; + boolean mCalled = false; + boolean mChecked = false; - @Override - public void onCheckedChange(SwitchItem item, boolean isChecked) { - mCalled = true; - mChecked = isChecked; - } + @Override + public void onCheckedChange(SwitchItem item, boolean isChecked) { + mCalled = true; + mChecked = isChecked; } + } } diff --git a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/util/DimensionConsistencyTest.java b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/util/DimensionConsistencyTest.java index 7a08235..af19905 100644 --- a/library/gingerbread/test/robotest/src/com/android/setupwizardlib/util/DimensionConsistencyTest.java +++ b/library/gingerbread/test/robotest/src/com/android/setupwizardlib/util/DimensionConsistencyTest.java @@ -24,10 +24,8 @@ import android.content.res.Resources; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.ContextThemeWrapper; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,32 +35,31 @@ import org.robolectric.annotation.Config; @Config(sdk = Config.ALL_SDKS) public class DimensionConsistencyTest { - // Visual height of the framework switch widget - private static final int SWTICH_HEIGHT_DP = 26; + // Visual height of the framework switch widget + private static final int SWTICH_HEIGHT_DP = 26; - private Context mContext; + private Context mContext; - @Before - public void setUp() { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - } + @Before + public void setUp() { + mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + } - @Test - public void testSwitchPaddingTop() { - final Resources res = mContext.getResources(); + @Test + public void testSwitchPaddingTop() { + final Resources res = mContext.getResources(); - assertEquals( - "Switch and divider should be aligned at center vertically: " - + "suw_switch_padding_top + SWITCH_HEIGHT / 2 = " - + "suw_switch_divider_padding_top + suw_switch_divider_height / 2", - res.getDimensionPixelSize(R.dimen.suw_switch_divider_padding_top) - + (res.getDimensionPixelSize(R.dimen.suw_switch_divider_height) / 2), - res.getDimensionPixelSize(R.dimen.suw_switch_padding_top) - + (dp2Px(SWTICH_HEIGHT_DP) / 2)); - } + assertEquals( + "Switch and divider should be aligned at center vertically: " + + "suw_switch_padding_top + SWITCH_HEIGHT / 2 = " + + "suw_switch_divider_padding_top + suw_switch_divider_height / 2", + res.getDimensionPixelSize(R.dimen.suw_switch_divider_padding_top) + + (res.getDimensionPixelSize(R.dimen.suw_switch_divider_height) / 2), + res.getDimensionPixelSize(R.dimen.suw_switch_padding_top) + (dp2Px(SWTICH_HEIGHT_DP) / 2)); + } - private int dp2Px(float dp) { - DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); - } + private int dp2Px(float dp) { + DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); + } } diff --git a/library/main/src/com/android/setupwizardlib/GlifLayout.java b/library/main/src/com/android/setupwizardlib/GlifLayout.java index 9b30c2f..20fb6b6 100644 --- a/library/main/src/com/android/setupwizardlib/GlifLayout.java +++ b/library/main/src/com/android/setupwizardlib/GlifLayout.java @@ -24,6 +24,9 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION_CODES; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -32,11 +35,6 @@ import android.view.ViewStub; import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; - -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.setupwizardlib.template.ButtonFooterMixin; import com.android.setupwizardlib.template.ColoredHeaderMixin; import com.android.setupwizardlib.template.HeaderMixin; @@ -50,6 +48,7 @@ import com.android.setupwizardlib.view.StatusBarBackgroundLayout; * Layout for the GLIF theme used in Setup Wizard for N. * *

Example usage: + * *

{@code
  * <com.android.setupwizardlib.GlifLayout
  *     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -66,259 +65,254 @@ import com.android.setupwizardlib.view.StatusBarBackgroundLayout;
  */
 public class GlifLayout extends TemplateLayout {
 
-    private static final String TAG = "GlifLayout";
-
-    private ColorStateList mPrimaryColor;
-
-    private boolean mBackgroundPatterned = true;
-
-    /**
-     * The color of the background. If null, the color will inherit from mPrimaryColor.
-     */
-    @Nullable
-    private ColorStateList mBackgroundBaseColor;
-
-    private boolean mLayoutFullscreen = true;
-
-    public GlifLayout(Context context) {
-        this(context, 0, 0);
-    }
-
-    public GlifLayout(Context context, int template) {
-        this(context, template, 0);
-    }
-
-    public GlifLayout(Context context, int template, int containerId) {
-        super(context, template, containerId);
-        init(null, R.attr.suwLayoutTheme);
-    }
+  private static final String TAG = "GlifLayout";
 
-    public GlifLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(attrs, R.attr.suwLayoutTheme);
-    }
+  private ColorStateList primaryColor;
 
-    @TargetApi(VERSION_CODES.HONEYCOMB)
-    public GlifLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        init(attrs, defStyleAttr);
-    }
+  private boolean backgroundPatterned = true;
 
-    // All the constructors delegate to this init method. The 3-argument constructor is not
-    // available in LinearLayout before v11, so call super with the exact same arguments.
-    private void init(AttributeSet attrs, int defStyleAttr) {
-        registerMixin(HeaderMixin.class, new ColoredHeaderMixin(this, attrs, defStyleAttr));
-        registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr));
-        registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this));
-        registerMixin(ButtonFooterMixin.class, new ButtonFooterMixin(this));
-        final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
-        registerMixin(RequireScrollMixin.class, requireScrollMixin);
-
-        final ScrollView scrollView = getScrollView();
-        if (scrollView != null) {
-            requireScrollMixin.setScrollHandlingDelegate(
-                    new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView));
-        }
-
-        TypedArray a = getContext().obtainStyledAttributes(attrs,
-                R.styleable.SuwGlifLayout, defStyleAttr, 0);
-
-        ColorStateList primaryColor =
-                a.getColorStateList(R.styleable.SuwGlifLayout_suwColorPrimary);
-        if (primaryColor != null) {
-            setPrimaryColor(primaryColor);
-        }
-
-        ColorStateList backgroundColor =
-                a.getColorStateList(R.styleable.SuwGlifLayout_suwBackgroundBaseColor);
-        setBackgroundBaseColor(backgroundColor);
-
-        boolean backgroundPatterned =
-                a.getBoolean(R.styleable.SuwGlifLayout_suwBackgroundPatterned, true);
-        setBackgroundPatterned(backgroundPatterned);
-
-        final int footer = a.getResourceId(R.styleable.SuwGlifLayout_suwFooter, 0);
-        if (footer != 0) {
-            inflateFooter(footer);
-        }
-
-        final int stickyHeader = a.getResourceId(R.styleable.SuwGlifLayout_suwStickyHeader, 0);
-        if (stickyHeader != 0) {
-            inflateStickyHeader(stickyHeader);
-        }
-
-        mLayoutFullscreen = a.getBoolean(R.styleable.SuwGlifLayout_suwLayoutFullscreen, true);
-
-        a.recycle();
-
-        if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && mLayoutFullscreen) {
-            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-        }
-    }
+  /** The color of the background. If null, the color will inherit from primaryColor. */
+  @Nullable private ColorStateList backgroundBaseColor;
 
-    @Override
-    protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) {
-        if (template == 0) {
-            template = R.layout.suw_glif_template;
-        }
-        return inflateTemplate(inflater, R.style.SuwThemeGlif_Light, template);
-    }
+  private boolean layoutFullscreen = true;
 
-    @Override
-    protected ViewGroup findContainer(int containerId) {
-        if (containerId == 0) {
-            containerId = R.id.suw_layout_content;
-        }
-        return super.findContainer(containerId);
-    }
+  public GlifLayout(Context context) {
+    this(context, 0, 0);
+  }
 
-    /**
-     * Sets the footer of the layout, which is at the bottom of the content area outside the
-     * scrolling container. The footer can only be inflated once per instance of this layout.
-     *
-     * @param footer The layout to be inflated as footer.
-     * @return The root of the inflated footer view.
-     */
-    public View inflateFooter(@LayoutRes int footer) {
-        ViewStub footerStub = findManagedViewById(R.id.suw_layout_footer);
-        footerStub.setLayoutResource(footer);
-        return footerStub.inflate();
-    }
+  public GlifLayout(Context context, int template) {
+    this(context, template, 0);
+  }
 
-    /**
-     * Sets the sticky header (i.e. header that doesn't scroll) of the layout, which is at the top
-     * of the content area outside of the scrolling container. The header can only be inflated once
-     * per instance of this layout.
-     *
-     * @param header The layout to be inflated as the header.
-     * @return The root of the inflated header view.
-     */
-    public View inflateStickyHeader(@LayoutRes int header) {
-        ViewStub stickyHeaderStub = findManagedViewById(R.id.suw_layout_sticky_header);
-        stickyHeaderStub.setLayoutResource(header);
-        return stickyHeaderStub.inflate();
-    }
+  public GlifLayout(Context context, int template, int containerId) {
+    super(context, template, containerId);
+    init(null, R.attr.suwLayoutTheme);
+  }
 
-    public ScrollView getScrollView() {
-        final View view = findManagedViewById(R.id.suw_scroll_view);
-        return view instanceof ScrollView ? (ScrollView) view : null;
-    }
+  public GlifLayout(Context context, AttributeSet attrs) {
+    super(context, attrs);
+    init(attrs, R.attr.suwLayoutTheme);
+  }
 
-    public TextView getHeaderTextView() {
-        return getMixin(HeaderMixin.class).getTextView();
-    }
+  @TargetApi(VERSION_CODES.HONEYCOMB)
+  public GlifLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+    super(context, attrs, defStyleAttr);
+    init(attrs, defStyleAttr);
+  }
 
-    public void setHeaderText(int title) {
-        getMixin(HeaderMixin.class).setText(title);
-    }
+  // All the constructors delegate to this init method. The 3-argument constructor is not
+  // available in LinearLayout before v11, so call super with the exact same arguments.
+  private void init(AttributeSet attrs, int defStyleAttr) {
+    registerMixin(HeaderMixin.class, new ColoredHeaderMixin(this, attrs, defStyleAttr));
+    registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr));
+    registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this));
+    registerMixin(ButtonFooterMixin.class, new ButtonFooterMixin(this));
+    final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
+    registerMixin(RequireScrollMixin.class, requireScrollMixin);
 
-    public void setHeaderText(CharSequence title) {
-        getMixin(HeaderMixin.class).setText(title);
+    final ScrollView scrollView = getScrollView();
+    if (scrollView != null) {
+      requireScrollMixin.setScrollHandlingDelegate(
+          new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView));
     }
 
-    public CharSequence getHeaderText() {
-        return getMixin(HeaderMixin.class).getText();
-    }
+    TypedArray a =
+        getContext().obtainStyledAttributes(attrs, R.styleable.SuwGlifLayout, defStyleAttr, 0);
 
-    public void setHeaderColor(ColorStateList color) {
-        final ColoredHeaderMixin mixin = (ColoredHeaderMixin) getMixin(HeaderMixin.class);
-        mixin.setColor(color);
+    ColorStateList primaryColor = a.getColorStateList(R.styleable.SuwGlifLayout_suwColorPrimary);
+    if (primaryColor != null) {
+      setPrimaryColor(primaryColor);
     }
 
-    public ColorStateList getHeaderColor() {
-        final ColoredHeaderMixin mixin = (ColoredHeaderMixin) getMixin(HeaderMixin.class);
-        return mixin.getColor();
-    }
+    ColorStateList backgroundColor =
+        a.getColorStateList(R.styleable.SuwGlifLayout_suwBackgroundBaseColor);
+    setBackgroundBaseColor(backgroundColor);
 
-    public void setIcon(Drawable icon) {
-        getMixin(IconMixin.class).setIcon(icon);
-    }
+    boolean backgroundPatterned =
+        a.getBoolean(R.styleable.SuwGlifLayout_suwBackgroundPatterned, true);
+    setBackgroundPatterned(backgroundPatterned);
 
-    public Drawable getIcon() {
-        return getMixin(IconMixin.class).getIcon();
+    final int footer = a.getResourceId(R.styleable.SuwGlifLayout_suwFooter, 0);
+    if (footer != 0) {
+      inflateFooter(footer);
     }
 
-    /**
-     * Sets the primary color of this layout, which will be used to determine the color of the
-     * progress bar and the background pattern.
-     */
-    public void setPrimaryColor(@NonNull ColorStateList color) {
-        mPrimaryColor = color;
-        updateBackground();
-        getMixin(ProgressBarMixin.class).setColor(color);
+    final int stickyHeader = a.getResourceId(R.styleable.SuwGlifLayout_suwStickyHeader, 0);
+    if (stickyHeader != 0) {
+      inflateStickyHeader(stickyHeader);
     }
 
-    public ColorStateList getPrimaryColor() {
-        return mPrimaryColor;
-    }
+    layoutFullscreen = a.getBoolean(R.styleable.SuwGlifLayout_suwLayoutFullscreen, true);
 
-    /**
-     * Sets the base color of the background view, which is the status bar for phones and the full-
-     * screen background for tablets. If {@link #isBackgroundPatterned()} is true, the pattern will
-     * be drawn with this color.
-     *
-     * @param color The color to use as the base color of the background. If {@code null},
-     *              {@link #getPrimaryColor()} will be used.
-     */
-    public void setBackgroundBaseColor(@Nullable ColorStateList color) {
-        mBackgroundBaseColor = color;
-        updateBackground();
-    }
+    a.recycle();
 
-    /**
-     * @return The base color of the background. {@code null} indicates the background will be drawn
-     *         with {@link #getPrimaryColor()}.
-     */
-    @Nullable
-    public ColorStateList getBackgroundBaseColor() {
-        return mBackgroundBaseColor;
+    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) {
+      setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
     }
+  }
 
-    /**
-     * Sets whether the background should be {@link GlifPatternDrawable}. If {@code false}, the
-     * background will be a solid color.
-     */
-    public void setBackgroundPatterned(boolean patterned) {
-        mBackgroundPatterned = patterned;
-        updateBackground();
+  @Override
+  protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) {
+    if (template == 0) {
+      template = R.layout.suw_glif_template;
     }
+    return inflateTemplate(inflater, R.style.SuwThemeGlif_Light, template);
+  }
 
-    /**
-     * @return True if this view uses {@link GlifPatternDrawable} as background.
-     */
-    public boolean isBackgroundPatterned() {
-        return mBackgroundPatterned;
+  @Override
+  protected ViewGroup findContainer(int containerId) {
+    if (containerId == 0) {
+      containerId = R.id.suw_layout_content;
     }
-
-    private void updateBackground() {
-        final View patternBg = findManagedViewById(R.id.suw_pattern_bg);
-        if (patternBg != null) {
-            int backgroundColor = 0;
-            if (mBackgroundBaseColor != null) {
-                backgroundColor = mBackgroundBaseColor.getDefaultColor();
-            } else if (mPrimaryColor != null) {
-                backgroundColor = mPrimaryColor.getDefaultColor();
-            }
-            Drawable background = mBackgroundPatterned
-                    ? new GlifPatternDrawable(backgroundColor)
-                    : new ColorDrawable(backgroundColor);
-            if (patternBg instanceof StatusBarBackgroundLayout) {
-                ((StatusBarBackgroundLayout) patternBg).setStatusBarBackground(background);
-            } else {
-                patternBg.setBackgroundDrawable(background);
-            }
-        }
+    return super.findContainer(containerId);
+  }
+
+  /**
+   * Sets the footer of the layout, which is at the bottom of the content area outside the scrolling
+   * container. The footer can only be inflated once per instance of this layout.
+   *
+   * @param footer The layout to be inflated as footer.
+   * @return The root of the inflated footer view.
+   */
+  public View inflateFooter(@LayoutRes int footer) {
+    ViewStub footerStub = findManagedViewById(R.id.suw_layout_footer);
+    footerStub.setLayoutResource(footer);
+    return footerStub.inflate();
+  }
+
+  /**
+   * Sets the sticky header (i.e. header that doesn't scroll) of the layout, which is at the top of
+   * the content area outside of the scrolling container. The header can only be inflated once per
+   * instance of this layout.
+   *
+   * @param header The layout to be inflated as the header.
+   * @return The root of the inflated header view.
+   */
+  public View inflateStickyHeader(@LayoutRes int header) {
+    ViewStub stickyHeaderStub = findManagedViewById(R.id.suw_layout_sticky_header);
+    stickyHeaderStub.setLayoutResource(header);
+    return stickyHeaderStub.inflate();
+  }
+
+  public ScrollView getScrollView() {
+    final View view = findManagedViewById(R.id.suw_scroll_view);
+    return view instanceof ScrollView ? (ScrollView) view : null;
+  }
+
+  public TextView getHeaderTextView() {
+    return getMixin(HeaderMixin.class).getTextView();
+  }
+
+  public void setHeaderText(int title) {
+    getMixin(HeaderMixin.class).setText(title);
+  }
+
+  public void setHeaderText(CharSequence title) {
+    getMixin(HeaderMixin.class).setText(title);
+  }
+
+  public CharSequence getHeaderText() {
+    return getMixin(HeaderMixin.class).getText();
+  }
+
+  public void setHeaderColor(ColorStateList color) {
+    final ColoredHeaderMixin mixin = (ColoredHeaderMixin) getMixin(HeaderMixin.class);
+    mixin.setColor(color);
+  }
+
+  public ColorStateList getHeaderColor() {
+    final ColoredHeaderMixin mixin = (ColoredHeaderMixin) getMixin(HeaderMixin.class);
+    return mixin.getColor();
+  }
+
+  public void setIcon(Drawable icon) {
+    getMixin(IconMixin.class).setIcon(icon);
+  }
+
+  public Drawable getIcon() {
+    return getMixin(IconMixin.class).getIcon();
+  }
+
+  /**
+   * Sets the primary color of this layout, which will be used to determine the color of the
+   * progress bar and the background pattern.
+   */
+  public void setPrimaryColor(@NonNull ColorStateList color) {
+    primaryColor = color;
+    updateBackground();
+    getMixin(ProgressBarMixin.class).setColor(color);
+  }
+
+  public ColorStateList getPrimaryColor() {
+    return primaryColor;
+  }
+
+  /**
+   * Sets the base color of the background view, which is the status bar for phones and the full-
+   * screen background for tablets. If {@link #isBackgroundPatterned()} is true, the pattern will be
+   * drawn with this color.
+   *
+   * @param color The color to use as the base color of the background. If {@code null}, {@link
+   *     #getPrimaryColor()} will be used.
+   */
+  public void setBackgroundBaseColor(@Nullable ColorStateList color) {
+    backgroundBaseColor = color;
+    updateBackground();
+  }
+
+  /**
+   * @return The base color of the background. {@code null} indicates the background will be drawn
+   *     with {@link #getPrimaryColor()}.
+   */
+  @Nullable
+  public ColorStateList getBackgroundBaseColor() {
+    return backgroundBaseColor;
+  }
+
+  /**
+   * Sets whether the background should be {@link GlifPatternDrawable}. If {@code false}, the
+   * background will be a solid color.
+   */
+  public void setBackgroundPatterned(boolean patterned) {
+    backgroundPatterned = patterned;
+    updateBackground();
+  }
+
+  /** @return True if this view uses {@link GlifPatternDrawable} as background. */
+  public boolean isBackgroundPatterned() {
+    return backgroundPatterned;
+  }
+
+  private void updateBackground() {
+    final View patternBg = findManagedViewById(R.id.suw_pattern_bg);
+    if (patternBg != null) {
+      int backgroundColor = 0;
+      if (backgroundBaseColor != null) {
+        backgroundColor = backgroundBaseColor.getDefaultColor();
+      } else if (primaryColor != null) {
+        backgroundColor = primaryColor.getDefaultColor();
+      }
+      Drawable background =
+          backgroundPatterned
+              ? new GlifPatternDrawable(backgroundColor)
+              : new ColorDrawable(backgroundColor);
+      if (patternBg instanceof StatusBarBackgroundLayout) {
+        ((StatusBarBackgroundLayout) patternBg).setStatusBarBackground(background);
+      } else {
+        patternBg.setBackgroundDrawable(background);
+      }
     }
+  }
 
-    public boolean isProgressBarShown() {
-        return getMixin(ProgressBarMixin.class).isShown();
-    }
+  public boolean isProgressBarShown() {
+    return getMixin(ProgressBarMixin.class).isShown();
+  }
 
-    public void setProgressBarShown(boolean shown) {
-        getMixin(ProgressBarMixin.class).setShown(shown);
-    }
+  public void setProgressBarShown(boolean shown) {
+    getMixin(ProgressBarMixin.class).setShown(shown);
+  }
 
-    public ProgressBar peekProgressBar() {
-        return getMixin(ProgressBarMixin.class).peekProgressBar();
-    }
+  public ProgressBar peekProgressBar() {
+    return getMixin(ProgressBarMixin.class).peekProgressBar();
+  }
 }
diff --git a/library/main/src/com/android/setupwizardlib/GlifListLayout.java b/library/main/src/com/android/setupwizardlib/GlifListLayout.java
index 8266e5f..c4f66fa 100644
--- a/library/main/src/com/android/setupwizardlib/GlifListLayout.java
+++ b/library/main/src/com/android/setupwizardlib/GlifListLayout.java
@@ -26,7 +26,6 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ListAdapter;
 import android.widget.ListView;
-
 import com.android.setupwizardlib.template.ListMixin;
 import com.android.setupwizardlib.template.ListViewScrollHandlingDelegate;
 import com.android.setupwizardlib.template.RequireScrollMixin;
@@ -37,130 +36,113 @@ import com.android.setupwizardlib.template.RequireScrollMixin;
  */
 public class GlifListLayout extends GlifLayout {
 
-    /* static section */
-
-    private static final String TAG = "GlifListLayout";
-
-    /* non-static section */
-
-    private ListMixin mListMixin;
-
-    public GlifListLayout(Context context) {
-        this(context, 0, 0);
-    }
-
-    public GlifListLayout(Context context, int template) {
-        this(context, template, 0);
-    }
-
-    public GlifListLayout(Context context, int template, int containerId) {
-        super(context, template, containerId);
-        init(context, null, 0);
-    }
-
-    public GlifListLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(context, attrs, 0);
-    }
-
-    @TargetApi(VERSION_CODES.HONEYCOMB)
-    public GlifListLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        init(context, attrs, defStyleAttr);
-    }
-
-    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
-        mListMixin = new ListMixin(this, attrs, defStyleAttr);
-        registerMixin(ListMixin.class, mListMixin);
-
-        final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class);
-        requireScrollMixin.setScrollHandlingDelegate(
-                new ListViewScrollHandlingDelegate(requireScrollMixin, getListView()));
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        mListMixin.onLayout();
-    }
-
-    @Override
-    protected View onInflateTemplate(LayoutInflater inflater, int template) {
-        if (template == 0) {
-            template = R.layout.suw_glif_list_template;
-        }
-        return super.onInflateTemplate(inflater, template);
-    }
-
-    @Override
-    protected ViewGroup findContainer(int containerId) {
-        if (containerId == 0) {
-            containerId = android.R.id.list;
-        }
-        return super.findContainer(containerId);
-    }
-
-    public ListView getListView() {
-        return mListMixin.getListView();
-    }
-
-    public void setAdapter(ListAdapter adapter) {
-        mListMixin.setAdapter(adapter);
-    }
-
-    public ListAdapter getAdapter() {
-        return mListMixin.getAdapter();
-    }
-
-    /**
-     * @deprecated Use {@link #setDividerInsets(int, int)} instead.
-     */
-    @Deprecated
-    public void setDividerInset(int inset) {
-        mListMixin.setDividerInset(inset);
-    }
-
-    /**
-     * Sets the start inset of the divider. This will use the default divider drawable set in the
-     * theme and apply insets to it.
-     *
-     * @param start The number of pixels to inset on the "start" side of the list divider. Typically
-     *              this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or
-     *              {@code @dimen/suw_items_glif_text_divider_inset}.
-     * @param end The number of pixels to inset on the "end" side of the list divider.
-     *
-     * @see ListMixin#setDividerInsets(int, int)
-     */
-    public void setDividerInsets(int start, int end) {
-        mListMixin.setDividerInsets(start, end);
-    }
-
-    /**
-     * @deprecated Use {@link #getDividerInsetStart()} instead.
-     */
-    @Deprecated
-    public int getDividerInset() {
-        return mListMixin.getDividerInset();
-    }
-
-    /**
-     * @see ListMixin#getDividerInsetStart()
-     */
-    public int getDividerInsetStart() {
-        return mListMixin.getDividerInsetStart();
-    }
-
-    /**
-     * @see ListMixin#getDividerInsetEnd()
-     */
-    public int getDividerInsetEnd() {
-        return mListMixin.getDividerInsetEnd();
-    }
-
-    /**
-     * @see ListMixin#getDivider()
-     */
-    public Drawable getDivider() {
-        return mListMixin.getDivider();
-    }
+  private ListMixin listMixin;
+
+  public GlifListLayout(Context context) {
+    this(context, 0, 0);
+  }
+
+  public GlifListLayout(Context context, int template) {
+    this(context, template, 0);
+  }
+
+  public GlifListLayout(Context context, int template, int containerId) {
+    super(context, template, containerId);
+    init(null, 0);
+  }
+
+  public GlifListLayout(Context context, AttributeSet attrs) {
+    super(context, attrs);
+    init(attrs, 0);
+  }
+
+  @TargetApi(VERSION_CODES.HONEYCOMB)
+  public GlifListLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+    super(context, attrs, defStyleAttr);
+    init(attrs, defStyleAttr);
+  }
+
+  private void init(AttributeSet attrs, int defStyleAttr) {
+    listMixin = new ListMixin(this, attrs, defStyleAttr);
+    registerMixin(ListMixin.class, listMixin);
+
+    final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class);
+    requireScrollMixin.setScrollHandlingDelegate(
+        new ListViewScrollHandlingDelegate(requireScrollMixin, getListView()));
+  }
+
+  @Override
+  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+    super.onLayout(changed, left, top, right, bottom);
+    listMixin.onLayout();
+  }
+
+  @Override
+  protected View onInflateTemplate(LayoutInflater inflater, int template) {
+    if (template == 0) {
+      template = R.layout.suw_glif_list_template;
+    }
+    return super.onInflateTemplate(inflater, template);
+  }
+
+  @Override
+  protected ViewGroup findContainer(int containerId) {
+    if (containerId == 0) {
+      containerId = android.R.id.list;
+    }
+    return super.findContainer(containerId);
+  }
+
+  public ListView getListView() {
+    return listMixin.getListView();
+  }
+
+  public void setAdapter(ListAdapter adapter) {
+    listMixin.setAdapter(adapter);
+  }
+
+  public ListAdapter getAdapter() {
+    return listMixin.getAdapter();
+  }
+
+  /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */
+  @Deprecated
+  public void setDividerInset(int inset) {
+    listMixin.setDividerInset(inset);
+  }
+
+  /**
+   * Sets the start inset of the divider. This will use the default divider drawable set in the
+   * theme and apply insets to it.
+   *
+   * @param start The number of pixels to inset on the "start" side of the list divider. Typically
+   *     this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or
+   *     {@code @dimen/suw_items_glif_text_divider_inset}.
+   * @param end The number of pixels to inset on the "end" side of the list divider.
+   * @see ListMixin#setDividerInsets(int, int)
+   */
+  public void setDividerInsets(int start, int end) {
+    listMixin.setDividerInsets(start, end);
+  }
+
+  /** @deprecated Use {@link #getDividerInsetStart()} instead. */
+  @Deprecated
+  public int getDividerInset() {
+    return listMixin.getDividerInset();
+  }
+
+  /** @see ListMixin#getDividerInsetStart() */
+  public int getDividerInsetStart() {
+    return listMixin.getDividerInsetStart();
+  }
+
+  /** @see ListMixin#getDividerInsetEnd() */
+  public int getDividerInsetEnd() {
+    return listMixin.getDividerInsetEnd();
+  }
+
+  /** @see ListMixin#getDivider() */
+  public Drawable getDivider() {
+    return listMixin.getDivider();
+  }
 }
diff --git a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java
index caf92ac..f65f3ec 100644
--- a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java
+++ b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java
@@ -31,10 +31,8 @@ import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
-
 import java.lang.ref.SoftReference;
 
 /**
@@ -42,263 +40,258 @@ import java.lang.ref.SoftReference;
  * tablets in GLIF layout.
  */
 public class GlifPatternDrawable extends Drawable {
-    /*
-     * This class essentially implements a simple SVG in Java code, with some special handling of
-     * scaling when given different bounds.
-     */
-
-    /* static section */
-
-    @SuppressLint("InlinedApi")
-    private static final int[] ATTRS_PRIMARY_COLOR = new int[]{ android.R.attr.colorPrimary };
-
-    private static final float VIEWBOX_HEIGHT = 768f;
-    private static final float VIEWBOX_WIDTH = 1366f;
-    // X coordinate of scale focus, as a fraction of of the width. (Range is 0 - 1)
-    private static final float SCALE_FOCUS_X = .146f;
-    // Y coordinate of scale focus, as a fraction of of the height. (Range is 0 - 1)
-    private static final float SCALE_FOCUS_Y = .228f;
-
-    // Alpha component of the color to be drawn, on top of the grayscale pattern. (Range is 0 - 1)
-    private static final float COLOR_ALPHA = .8f;
-    // Int version of COLOR_ALPHA. (Range is 0 - 255)
-    private static final int COLOR_ALPHA_INT = (int) (COLOR_ALPHA * 255);
-
-    // Cap the bitmap size, such that it won't hurt the performance too much
-    // and it won't crash due to a very large scale.
-    // The drawable will look blurry above this size.
-    // This is a multiplier applied on top of the viewbox size.
-    // Resulting max cache size = (1.5 x 1366, 1.5 x 768) = (2049, 1152)
-    private static final float MAX_CACHED_BITMAP_SCALE = 1.5f;
-
-    private static final int NUM_PATHS = 7;
-
-    private static SoftReference sBitmapCache;
-    private static Path[] sPatternPaths;
-    private static int[] sPatternLightness;
-
-    public static GlifPatternDrawable getDefault(Context context) {
-        int colorPrimary = 0;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            final TypedArray a = context.obtainStyledAttributes(ATTRS_PRIMARY_COLOR);
-            colorPrimary = a.getColor(0, Color.BLACK);
-            a.recycle();
-        }
-        return new GlifPatternDrawable(colorPrimary);
-    }
-
-    @VisibleForTesting
-    public static void invalidatePattern() {
-        sBitmapCache = null;
-    }
-
-    /* non-static section */
-
-    private int mColor;
-    private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
-    public GlifPatternDrawable(int color) {
-        setColor(color);
+  /*
+   * This class essentially implements a simple SVG in Java code, with some special handling of
+   * scaling when given different bounds.
+   */
+
+  /* static section */
+
+  @SuppressLint("InlinedApi")
+  private static final int[] ATTRS_PRIMARY_COLOR = new int[] {android.R.attr.colorPrimary};
+
+  private static final float VIEWBOX_HEIGHT = 768f;
+  private static final float VIEWBOX_WIDTH = 1366f;
+  // X coordinate of scale focus, as a fraction of the width. (Range is 0 - 1)
+  private static final float SCALE_FOCUS_X = .146f;
+  // Y coordinate of scale focus, as a fraction of the height. (Range is 0 - 1)
+  private static final float SCALE_FOCUS_Y = .228f;
+
+  // Alpha component of the color to be drawn, on top of the grayscale pattern. (Range is 0 - 1)
+  private static final float COLOR_ALPHA = .8f;
+  // Int version of COLOR_ALPHA. (Range is 0 - 255)
+  private static final int COLOR_ALPHA_INT = (int) (COLOR_ALPHA * 255);
+
+  // Cap the bitmap size, such that it won't hurt the performance too much
+  // and it won't crash due to a very large scale.
+  // The drawable will look blurry above this size.
+  // This is a multiplier applied on top of the viewbox size.
+  // Resulting max cache size = (1.5 x 1366, 1.5 x 768) = (2049, 1152)
+  private static final float MAX_CACHED_BITMAP_SCALE = 1.5f;
+
+  private static final int NUM_PATHS = 7;
+
+  private static SoftReference bitmapCache;
+  private static Path[] patternPaths;
+  private static int[] patternLightness;
+
+  public static GlifPatternDrawable getDefault(Context context) {
+    int colorPrimary = 0;
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      final TypedArray a = context.obtainStyledAttributes(ATTRS_PRIMARY_COLOR);
+      colorPrimary = a.getColor(0, Color.BLACK);
+      a.recycle();
     }
-
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        final Rect bounds = getBounds();
-        int drawableWidth = bounds.width();
-        int drawableHeight = bounds.height();
-        Bitmap bitmap = null;
-        if (sBitmapCache != null) {
-            bitmap = sBitmapCache.get();
-        }
-        if (bitmap != null) {
-            final int bitmapWidth = bitmap.getWidth();
-            final int bitmapHeight = bitmap.getHeight();
-            // Invalidate the cache if this drawable is bigger and we can still create a bigger
-            // cache.
-            if (drawableWidth > bitmapWidth
-                    && bitmapWidth < VIEWBOX_WIDTH * MAX_CACHED_BITMAP_SCALE) {
-                bitmap = null;
-            } else if (drawableHeight > bitmapHeight
-                    && bitmapHeight < VIEWBOX_HEIGHT * MAX_CACHED_BITMAP_SCALE) {
-                bitmap = null;
-            }
-        }
-
-        if (bitmap == null) {
-            // Reset the paint so it can be used to draw the paths in renderOnCanvas
-            mTempPaint.reset();
-
-            bitmap = createBitmapCache(drawableWidth, drawableHeight);
-            sBitmapCache = new SoftReference<>(bitmap);
-
-            // Reset the paint to so it can be used to draw the bitmap
-            mTempPaint.reset();
-        }
-
-        canvas.save();
-        canvas.clipRect(bounds);
-
-        scaleCanvasToBounds(canvas, bitmap, bounds);
-        canvas.drawColor(Color.BLACK);
-        mTempPaint.setColor(Color.WHITE);
-        canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
-        canvas.drawColor(mColor);
-
-        canvas.restore();
+    return new GlifPatternDrawable(colorPrimary);
+  }
+
+  @VisibleForTesting
+  public static void invalidatePattern() {
+    bitmapCache = null;
+  }
+
+  /* non-static section */
+
+  private int color;
+  private final Paint tempPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+  public GlifPatternDrawable(int color) {
+    setColor(color);
+  }
+
+  @Override
+  public void draw(@NonNull Canvas canvas) {
+    final Rect bounds = getBounds();
+    int drawableWidth = bounds.width();
+    int drawableHeight = bounds.height();
+    Bitmap bitmap = null;
+    if (bitmapCache != null) {
+      bitmap = bitmapCache.get();
     }
-
-    @VisibleForTesting
-    public Bitmap createBitmapCache(int drawableWidth, int drawableHeight) {
-        float scaleX = drawableWidth / VIEWBOX_WIDTH;
-        float scaleY = drawableHeight / VIEWBOX_HEIGHT;
-        float scale = Math.max(scaleX, scaleY);
-        scale = Math.min(MAX_CACHED_BITMAP_SCALE, scale);
-
-
-        int scaledWidth = (int) (VIEWBOX_WIDTH * scale);
-        int scaledHeight = (int) (VIEWBOX_HEIGHT * scale);
-
-        // Use ALPHA_8 mask to save memory, since the pattern is grayscale only anyway.
-        Bitmap bitmap = Bitmap.createBitmap(
-                scaledWidth,
-                scaledHeight,
-                Bitmap.Config.ALPHA_8);
-        Canvas bitmapCanvas = new Canvas(bitmap);
-        renderOnCanvas(bitmapCanvas, scale);
-        return bitmap;
+    if (bitmap != null) {
+      final int bitmapWidth = bitmap.getWidth();
+      final int bitmapHeight = bitmap.getHeight();
+      // Invalidate the cache if this drawable is bigger and we can still create a bigger
+      // cache.
+      if (drawableWidth > bitmapWidth && bitmapWidth < VIEWBOX_WIDTH * MAX_CACHED_BITMAP_SCALE) {
+        bitmap = null;
+      } else if (drawableHeight > bitmapHeight
+          && bitmapHeight < VIEWBOX_HEIGHT * MAX_CACHED_BITMAP_SCALE) {
+        bitmap = null;
+      }
     }
 
-    private void renderOnCanvas(Canvas canvas, float scale) {
-        canvas.save();
-        canvas.scale(scale, scale);
-
-        mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
-
-        // Draw the pattern by creating the paths, adjusting the colors and drawing them. The path
-        // values are extracted from the SVG of the pattern file.
-
-        if (sPatternPaths == null) {
-            sPatternPaths = new Path[NUM_PATHS];
-            // Lightness values of the pattern, range 0 - 255
-            sPatternLightness = new int[] { 10, 40, 51, 66, 91, 112, 130 };
-
-            Path p = sPatternPaths[0] = new Path();
-            p.moveTo(1029.4f, 357.5f);
-            p.lineTo(1366f, 759.1f);
-            p.lineTo(1366f, 0f);
-            p.lineTo(1137.7f, 0f);
-            p.close();
-
-            p = sPatternPaths[1] = new Path();
-            p.moveTo(1138.1f, 0f);
-            p.rLineTo(-144.8f, 768f);
-            p.rLineTo(372.7f, 0f);
-            p.rLineTo(0f, -524f);
-            p.cubicTo(1290.7f, 121.6f, 1219.2f, 41.1f, 1178.7f, 0f);
-            p.close();
-
-            p = sPatternPaths[2] = new Path();
-            p.moveTo(949.8f, 768f);
-            p.rCubicTo(92.6f, -170.6f, 213f, -440.3f, 269.4f, -768f);
-            p.lineTo(585f, 0f);
-            p.rLineTo(2.1f, 766f);
-            p.close();
-
-            p = sPatternPaths[3] = new Path();
-            p.moveTo(471.1f, 768f);
-            p.rMoveTo(704.5f, 0f);
-            p.cubicTo(1123.6f, 563.3f, 1027.4f, 275.2f, 856.2f, 0f);
-            p.lineTo(476.4f, 0f);
-            p.rLineTo(-5.3f, 768f);
-            p.close();
-
-            p = sPatternPaths[4] = new Path();
-            p.moveTo(323.1f, 768f);
-            p.moveTo(777.5f, 768f);
-            p.cubicTo(661.9f, 348.8f, 427.2f, 21.4f, 401.2f, 25.4f);
-            p.lineTo(323.1f, 768f);
-            p.close();
-
-            p = sPatternPaths[5] = new Path();
-            p.moveTo(178.44286f, 766.8571f);
-            p.lineTo(308.7f, 768f);
-            p.cubicTo(381.7f, 604.6f, 481.6f, 344.3f, 562.2f, 0f);
-            p.lineTo(0f, 0f);
-            p.close();
-
-            p = sPatternPaths[6] = new Path();
-            p.moveTo(146f, 0f);
-            p.lineTo(0f, 0f);
-            p.lineTo(0f, 768f);
-            p.lineTo(394.2f, 768f);
-            p.cubicTo(327.7f, 475.3f, 228.5f, 201f, 146f, 0f);
-            p.close();
-        }
-
-        for (int i = 0; i < NUM_PATHS; i++) {
-            // Color is 0xAARRGGBB, so alpha << 24 will create a color with (alpha)% black.
-            // Although the color components don't really matter, since the backing bitmap cache is
-            // ALPHA_8.
-            mTempPaint.setColor(sPatternLightness[i] << 24);
-            canvas.drawPath(sPatternPaths[i], mTempPaint);
-        }
-
-        canvas.restore();
-        mTempPaint.reset();
-    }
+    if (bitmap == null) {
+      // Reset the paint so it can be used to draw the paths in renderOnCanvas
+      tempPaint.reset();
 
-    @VisibleForTesting
-    public void scaleCanvasToBounds(Canvas canvas, Bitmap bitmap, Rect drawableBounds) {
-        int bitmapWidth = bitmap.getWidth();
-        int bitmapHeight = bitmap.getHeight();
-        float scaleX = drawableBounds.width() / (float) bitmapWidth;
-        float scaleY = drawableBounds.height() / (float) bitmapHeight;
-
-        // First scale both sides to fit independently.
-        canvas.scale(scaleX, scaleY);
-        if (scaleY > scaleX) {
-            // Adjust x-scale to maintain aspect ratio using the pivot, so that more of the texture
-            // and less of the blank space on the left edge is seen.
-            canvas.scale(scaleY / scaleX, 1f, SCALE_FOCUS_X * bitmapWidth, 0f);
-        } else if (scaleX > scaleY) {
-            // Adjust y-scale to maintain aspect ratio using the pivot, so that an intersection of
-            // two "circles" can always be seen.
-            canvas.scale(1f, scaleX / scaleY, 0f, SCALE_FOCUS_Y * bitmapHeight);
-        }
-    }
-
-    @Override
-    public void setAlpha(int i) {
-        // Ignore
-    }
+      bitmap = createBitmapCache(drawableWidth, drawableHeight);
+      bitmapCache = new SoftReference<>(bitmap);
 
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-        // Ignore
+      // Reset the paint to so it can be used to draw the bitmap
+      tempPaint.reset();
     }
 
-    @Override
-    public int getOpacity() {
-        return PixelFormat.UNKNOWN;
+    canvas.save();
+    canvas.clipRect(bounds);
+
+    scaleCanvasToBounds(canvas, bitmap, bounds);
+    canvas.drawColor(Color.BLACK);
+    tempPaint.setColor(Color.WHITE);
+    canvas.drawBitmap(bitmap, 0, 0, tempPaint);
+    canvas.drawColor(color);
+
+    canvas.restore();
+  }
+
+  @VisibleForTesting
+  public Bitmap createBitmapCache(int drawableWidth, int drawableHeight) {
+    float scaleX = drawableWidth / VIEWBOX_WIDTH;
+    float scaleY = drawableHeight / VIEWBOX_HEIGHT;
+    float scale = Math.max(scaleX, scaleY);
+    scale = Math.min(MAX_CACHED_BITMAP_SCALE, scale);
+
+    int scaledWidth = (int) (VIEWBOX_WIDTH * scale);
+    int scaledHeight = (int) (VIEWBOX_HEIGHT * scale);
+
+    // Use ALPHA_8 mask to save memory, since the pattern is grayscale only anyway.
+    Bitmap bitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ALPHA_8);
+    Canvas bitmapCanvas = new Canvas(bitmap);
+    renderOnCanvas(bitmapCanvas, scale);
+    return bitmap;
+  }
+
+  private void renderOnCanvas(Canvas canvas, float scale) {
+    canvas.save();
+    canvas.scale(scale, scale);
+
+    tempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+
+    // Draw the pattern by creating the paths, adjusting the colors and drawing them. The path
+    // values are extracted from the SVG of the pattern file.
+
+    if (patternPaths == null) {
+      patternPaths = new Path[NUM_PATHS];
+      // Lightness values of the pattern, range 0 - 255
+      patternLightness = new int[] {10, 40, 51, 66, 91, 112, 130};
+
+      Path p = patternPaths[0] = new Path();
+      p.moveTo(1029.4f, 357.5f);
+      p.lineTo(1366f, 759.1f);
+      p.lineTo(1366f, 0f);
+      p.lineTo(1137.7f, 0f);
+      p.close();
+
+      p = patternPaths[1] = new Path();
+      p.moveTo(1138.1f, 0f);
+      p.rLineTo(-144.8f, 768f);
+      p.rLineTo(372.7f, 0f);
+      p.rLineTo(0f, -524f);
+      p.cubicTo(1290.7f, 121.6f, 1219.2f, 41.1f, 1178.7f, 0f);
+      p.close();
+
+      p = patternPaths[2] = new Path();
+      p.moveTo(949.8f, 768f);
+      p.rCubicTo(92.6f, -170.6f, 213f, -440.3f, 269.4f, -768f);
+      p.lineTo(585f, 0f);
+      p.rLineTo(2.1f, 766f);
+      p.close();
+
+      p = patternPaths[3] = new Path();
+      p.moveTo(471.1f, 768f);
+      p.rMoveTo(704.5f, 0f);
+      p.cubicTo(1123.6f, 563.3f, 1027.4f, 275.2f, 856.2f, 0f);
+      p.lineTo(476.4f, 0f);
+      p.rLineTo(-5.3f, 768f);
+      p.close();
+
+      p = patternPaths[4] = new Path();
+      p.moveTo(323.1f, 768f);
+      p.moveTo(777.5f, 768f);
+      p.cubicTo(661.9f, 348.8f, 427.2f, 21.4f, 401.2f, 25.4f);
+      p.lineTo(323.1f, 768f);
+      p.close();
+
+      p = patternPaths[5] = new Path();
+      p.moveTo(178.44286f, 766.8571f);
+      p.lineTo(308.7f, 768f);
+      p.cubicTo(381.7f, 604.6f, 481.6f, 344.3f, 562.2f, 0f);
+      p.lineTo(0f, 0f);
+      p.close();
+
+      p = patternPaths[6] = new Path();
+      p.moveTo(146f, 0f);
+      p.lineTo(0f, 0f);
+      p.lineTo(0f, 768f);
+      p.lineTo(394.2f, 768f);
+      p.cubicTo(327.7f, 475.3f, 228.5f, 201f, 146f, 0f);
+      p.close();
     }
 
-    /**
-     * Sets the color used as the base color of this pattern drawable. The alpha component of the
-     * color will be ignored.
-     */
-    public void setColor(int color) {
-        final int r = Color.red(color);
-        final int g = Color.green(color);
-        final int b = Color.blue(color);
-        mColor = Color.argb(COLOR_ALPHA_INT, r, g, b);
-        invalidateSelf();
+    for (int i = 0; i < NUM_PATHS; i++) {
+      // Color is 0xAARRGGBB, so alpha << 24 will create a color with (alpha)% black.
+      // Although the color components don't really matter, since the backing bitmap cache is
+      // ALPHA_8.
+      tempPaint.setColor(patternLightness[i] << 24);
+      canvas.drawPath(patternPaths[i], tempPaint);
     }
 
-    /**
-     * @return The color used as the base color of this pattern drawable. The alpha component of
-     * this is always 255.
-     */
-    public int getColor() {
-        return Color.argb(255, Color.red(mColor), Color.green(mColor), Color.blue(mColor));
+    canvas.restore();
+    tempPaint.reset();
+  }
+
+  @VisibleForTesting
+  public void scaleCanvasToBounds(Canvas canvas, Bitmap bitmap, Rect drawableBounds) {
+    int bitmapWidth = bitmap.getWidth();
+    int bitmapHeight = bitmap.getHeight();
+    float scaleX = drawableBounds.width() / (float) bitmapWidth;
+    float scaleY = drawableBounds.height() / (float) bitmapHeight;
+
+    // First scale both sides to fit independently.
+    canvas.scale(scaleX, scaleY);
+    if (scaleY > scaleX) {
+      // Adjust x-scale to maintain aspect ratio using the pivot, so that more of the texture
+      // and less of the blank space on the left edge is seen.
+      canvas.scale(scaleY / scaleX, 1f, SCALE_FOCUS_X * bitmapWidth, 0f);
+    } else if (scaleX > scaleY) {
+      // Adjust y-scale to maintain aspect ratio using the pivot, so that an intersection of
+      // two "circles" can always be seen.
+      canvas.scale(1f, scaleX / scaleY, 0f, SCALE_FOCUS_Y * bitmapHeight);
     }
+  }
+
+  @Override
+  public void setAlpha(int i) {
+    // Ignore
+  }
+
+  @Override
+  public void setColorFilter(ColorFilter colorFilter) {
+    // Ignore
+  }
+
+  @Override
+  public int getOpacity() {
+    return PixelFormat.UNKNOWN;
+  }
+
+  /**
+   * Sets the color used as the base color of this pattern drawable. The alpha component of the
+   * color will be ignored.
+   */
+  public void setColor(int color) {
+    final int r = Color.red(color);
+    final int g = Color.green(color);
+    final int b = Color.blue(color);
+    this.color = Color.argb(COLOR_ALPHA_INT, r, g, b);
+    invalidateSelf();
+  }
+
+  /**
+   * @return The color used as the base color of this pattern drawable. The alpha component of this
+   *     is always 255.
+   */
+  public int getColor() {
+    return Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+  }
 }
diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardItemsLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardItemsLayout.java
index d520873..6197c4c 100644
--- a/library/main/src/com/android/setupwizardlib/SetupWizardItemsLayout.java
+++ b/library/main/src/com/android/setupwizardlib/SetupWizardItemsLayout.java
@@ -17,34 +17,30 @@
 package com.android.setupwizardlib;
 
 import android.content.Context;
+import androidx.annotation.Nullable;
 import android.util.AttributeSet;
 import android.widget.ListAdapter;
-
-import androidx.annotation.Nullable;
-
 import com.android.setupwizardlib.items.ItemAdapter;
 
-/**
- * @deprecated Use {@link SetupWizardListLayout} instead.
- */
+/** @deprecated Use {@link SetupWizardListLayout} instead. */
 @Deprecated
 public class SetupWizardItemsLayout extends SetupWizardListLayout {
 
-    public SetupWizardItemsLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
+  public SetupWizardItemsLayout(Context context, AttributeSet attrs) {
+    super(context, attrs);
+  }
 
-    public SetupWizardItemsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
+  public SetupWizardItemsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+    super(context, attrs, defStyleAttr);
+  }
 
-    @Override
-    @Nullable
-    public ItemAdapter getAdapter() {
-        final ListAdapter adapter = super.getAdapter();
-        if (adapter instanceof ItemAdapter) {
-            return (ItemAdapter) adapter;
-        }
-        return null;
+  @Override
+  @Nullable
+  public ItemAdapter getAdapter() {
+    final ListAdapter adapter = super.getAdapter();
+    if (adapter instanceof ItemAdapter) {
+      return (ItemAdapter) adapter;
     }
+    return null;
+  }
 }
diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
index 065d2ef..792d102 100644
--- a/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
+++ b/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
@@ -38,7 +38,6 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ScrollView;
 import android.widget.TextView;
-
 import com.android.setupwizardlib.template.HeaderMixin;
 import com.android.setupwizardlib.template.NavigationBarMixin;
 import com.android.setupwizardlib.template.ProgressBarMixin;
@@ -49,385 +48,377 @@ import com.android.setupwizardlib.view.NavigationBar;
 
 public class SetupWizardLayout extends TemplateLayout {
 
-    private static final String TAG = "SetupWizardLayout";
-
-    public SetupWizardLayout(Context context) {
-        super(context, 0, 0);
-        init(null, R.attr.suwLayoutTheme);
-    }
-
-    public SetupWizardLayout(Context context, int template) {
-        this(context, template, 0);
-    }
-
-    public SetupWizardLayout(Context context, int template, int containerId) {
-        super(context, template, containerId);
-        init(null, R.attr.suwLayoutTheme);
-    }
-
-    public SetupWizardLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(attrs, R.attr.suwLayoutTheme);
-    }
-
-    @TargetApi(VERSION_CODES.HONEYCOMB)
-    public SetupWizardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        init(attrs, defStyleAttr);
-    }
-
-    // All the constructors delegate to this init method. The 3-argument constructor is not
-    // available in LinearLayout before v11, so call super with the exact same arguments.
-    private void init(AttributeSet attrs, int defStyleAttr) {
-        registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr));
-        registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this));
-        registerMixin(NavigationBarMixin.class, new NavigationBarMixin(this));
-        final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
-        registerMixin(RequireScrollMixin.class, requireScrollMixin);
-
-        final ScrollView scrollView = getScrollView();
-        if (scrollView != null) {
-            requireScrollMixin.setScrollHandlingDelegate(
-                    new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView));
-        }
-
-        final TypedArray a = getContext().obtainStyledAttributes(attrs,
-                R.styleable.SuwSetupWizardLayout, defStyleAttr, 0);
-
-        // Set the background from XML, either directly or built from a bitmap tile
-        final Drawable background =
-                a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackground);
-        if (background != null) {
-            setLayoutBackground(background);
-        } else {
-            final Drawable backgroundTile =
-                    a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackgroundTile);
-            if (backgroundTile != null) {
-                setBackgroundTile(backgroundTile);
-            }
-        }
-
-        // Set the illustration from XML, either directly or built from image + horizontal tile
-        final Drawable illustration =
-                a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustration);
-        if (illustration != null) {
-            setIllustration(illustration);
-        } else {
-            final Drawable illustrationImage =
-                    a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustrationImage);
-            final Drawable horizontalTile = a.getDrawable(
-                    R.styleable.SuwSetupWizardLayout_suwIllustrationHorizontalTile);
-            if (illustrationImage != null && horizontalTile != null) {
-                setIllustration(illustrationImage, horizontalTile);
-            }
-        }
-
-        // Set the top padding of the illustration
-        int decorPaddingTop = a.getDimensionPixelSize(
-                R.styleable.SuwSetupWizardLayout_suwDecorPaddingTop, -1);
-        if (decorPaddingTop == -1) {
-            decorPaddingTop = getResources().getDimensionPixelSize(R.dimen.suw_decor_padding_top);
-        }
-        setDecorPaddingTop(decorPaddingTop);
-
-
-        // Set the illustration aspect ratio. See Illustration.setAspectRatio(float). This will
-        // override suwDecorPaddingTop if its value is not 0.
-        float illustrationAspectRatio = a.getFloat(
-                R.styleable.SuwSetupWizardLayout_suwIllustrationAspectRatio, -1f);
-        if (illustrationAspectRatio == -1f) {
-            final TypedValue out = new TypedValue();
-            getResources().getValue(R.dimen.suw_illustration_aspect_ratio, out, true);
-            illustrationAspectRatio = out.getFloat();
-        }
-        setIllustrationAspectRatio(illustrationAspectRatio);
-
-        a.recycle();
-    }
-
-    @Override
-    protected Parcelable onSaveInstanceState() {
-        final Parcelable parcelable = super.onSaveInstanceState();
-        final SavedState ss = new SavedState(parcelable);
-        ss.mIsProgressBarShown = isProgressBarShown();
-        return ss;
+  private static final String TAG = "SetupWizardLayout";
+
+  public SetupWizardLayout(Context context) {
+    super(context, 0, 0);
+    init(null, R.attr.suwLayoutTheme);
+  }
+
+  public SetupWizardLayout(Context context, int template) {
+    this(context, template, 0);
+  }
+
+  public SetupWizardLayout(Context context, int template, int containerId) {
+    super(context, template, containerId);
+    init(null, R.attr.suwLayoutTheme);
+  }
+
+  public SetupWizardLayout(Context context, AttributeSet attrs) {
+    super(context, attrs);
+    init(attrs, R.attr.suwLayoutTheme);
+  }
+
+  @TargetApi(VERSION_CODES.HONEYCOMB)
+  public SetupWizardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+    super(context, attrs, defStyleAttr);
+    init(attrs, defStyleAttr);
+  }
+
+  // All the constructors delegate to this init method. The 3-argument constructor is not
+  // available in LinearLayout before v11, so call super with the exact same arguments.
+  private void init(AttributeSet attrs, int defStyleAttr) {
+    registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr));
+    registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this));
+    registerMixin(NavigationBarMixin.class, new NavigationBarMixin(this));
+    final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
+    registerMixin(RequireScrollMixin.class, requireScrollMixin);
+
+    final ScrollView scrollView = getScrollView();
+    if (scrollView != null) {
+      requireScrollMixin.setScrollHandlingDelegate(
+          new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView));
+    }
+
+    final TypedArray a =
+        getContext()
+            .obtainStyledAttributes(attrs, R.styleable.SuwSetupWizardLayout, defStyleAttr, 0);
+
+    // Set the background from XML, either directly or built from a bitmap tile
+    final Drawable background = a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackground);
+    if (background != null) {
+      setLayoutBackground(background);
+    } else {
+      final Drawable backgroundTile =
+          a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackgroundTile);
+      if (backgroundTile != null) {
+        setBackgroundTile(backgroundTile);
+      }
+    }
+
+    // Set the illustration from XML, either directly or built from image + horizontal tile
+    final Drawable illustration = a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustration);
+    if (illustration != null) {
+      setIllustration(illustration);
+    } else {
+      final Drawable illustrationImage =
+          a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustrationImage);
+      final Drawable horizontalTile =
+          a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustrationHorizontalTile);
+      if (illustrationImage != null && horizontalTile != null) {
+        setIllustration(illustrationImage, horizontalTile);
+      }
+    }
+
+    // Set the top padding of the illustration
+    int decorPaddingTop =
+        a.getDimensionPixelSize(R.styleable.SuwSetupWizardLayout_suwDecorPaddingTop, -1);
+    if (decorPaddingTop == -1) {
+      decorPaddingTop = getResources().getDimensionPixelSize(R.dimen.suw_decor_padding_top);
+    }
+    setDecorPaddingTop(decorPaddingTop);
+
+    // Set the illustration aspect ratio. See Illustration.setAspectRatio(float). This will
+    // override suwDecorPaddingTop if its value is not 0.
+    float illustrationAspectRatio =
+        a.getFloat(R.styleable.SuwSetupWizardLayout_suwIllustrationAspectRatio, -1f);
+    if (illustrationAspectRatio == -1f) {
+      final TypedValue out = new TypedValue();
+      getResources().getValue(R.dimen.suw_illustration_aspect_ratio, out, true);
+      illustrationAspectRatio = out.getFloat();
+    }
+    setIllustrationAspectRatio(illustrationAspectRatio);
+
+    a.recycle();
+  }
+
+  @Override
+  protected Parcelable onSaveInstanceState() {
+    final Parcelable parcelable = super.onSaveInstanceState();
+    final SavedState ss = new SavedState(parcelable);
+    ss.isProgressBarShown = isProgressBarShown();
+    return ss;
+  }
+
+  @Override
+  protected void onRestoreInstanceState(Parcelable state) {
+    if (!(state instanceof SavedState)) {
+      Log.w(TAG, "Ignoring restore instance state " + state);
+      super.onRestoreInstanceState(state);
+      return;
+    }
+
+    final SavedState ss = (SavedState) state;
+    super.onRestoreInstanceState(ss.getSuperState());
+    final boolean isProgressBarShown = ss.isProgressBarShown;
+    setProgressBarShown(isProgressBarShown);
+  }
+
+  @Override
+  protected View onInflateTemplate(LayoutInflater inflater, int template) {
+    if (template == 0) {
+      template = R.layout.suw_template;
+    }
+    return inflateTemplate(inflater, R.style.SuwThemeMaterial_Light, template);
+  }
+
+  @Override
+  protected ViewGroup findContainer(int containerId) {
+    if (containerId == 0) {
+      containerId = R.id.suw_layout_content;
+    }
+    return super.findContainer(containerId);
+  }
+
+  public NavigationBar getNavigationBar() {
+    return getMixin(NavigationBarMixin.class).getNavigationBar();
+  }
+
+  public ScrollView getScrollView() {
+    final View view = findManagedViewById(R.id.suw_bottom_scroll_view);
+    return view instanceof ScrollView ? (ScrollView) view : null;
+  }
+
+  public void requireScrollToBottom() {
+    final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class);
+    final NavigationBar navigationBar = getNavigationBar();
+    if (navigationBar != null) {
+      requireScrollMixin.requireScrollWithNavigationBar(navigationBar);
+    } else {
+      Log.e(TAG, "Cannot require scroll. Navigation bar is null.");
+    }
+  }
+
+  public void setHeaderText(int title) {
+    getMixin(HeaderMixin.class).setText(title);
+  }
+
+  public void setHeaderText(CharSequence title) {
+    getMixin(HeaderMixin.class).setText(title);
+  }
+
+  public CharSequence getHeaderText() {
+    return getMixin(HeaderMixin.class).getText();
+  }
+
+  public TextView getHeaderTextView() {
+    return getMixin(HeaderMixin.class).getTextView();
+  }
+
+  /**
+   * Set the illustration of the layout. The drawable will be applied as is, and the bounds will be
+   * set as implemented in {@link com.android.setupwizardlib.view.Illustration}. To create a
+   * suitable drawable from an asset and a horizontal repeating tile, use {@link
+   * #setIllustration(int, int)} instead.
+   *
+   * @param drawable The drawable specifying the illustration.
+   */
+  public void setIllustration(Drawable drawable) {
+    final View view = findManagedViewById(R.id.suw_layout_decor);
+    if (view instanceof Illustration) {
+      final Illustration illustration = (Illustration) view;
+      illustration.setIllustration(drawable);
+    }
+  }
+
+  /**
+   * Set the illustration of the layout, which will be created asset and the horizontal tile as
+   * suitable. On phone layouts (not sw600dp), the asset will be scaled, maintaining aspect ratio.
+   * On tablets (sw600dp), the assets will always have 256dp height and the rest of the illustration
+   * area that the asset doesn't fill will be covered by the horizontalTile.
+   *
+   * @param asset Resource ID of the illustration asset.
+   * @param horizontalTile Resource ID of the horizontally repeating tile for tablet layout.
+   */
+  public void setIllustration(int asset, int horizontalTile) {
+    final View view = findManagedViewById(R.id.suw_layout_decor);
+    if (view instanceof Illustration) {
+      final Illustration illustration = (Illustration) view;
+      final Drawable illustrationDrawable = getIllustration(asset, horizontalTile);
+      illustration.setIllustration(illustrationDrawable);
+    }
+  }
+
+  private void setIllustration(Drawable asset, Drawable horizontalTile) {
+    final View view = findManagedViewById(R.id.suw_layout_decor);
+    if (view instanceof Illustration) {
+      final Illustration illustration = (Illustration) view;
+      final Drawable illustrationDrawable = getIllustration(asset, horizontalTile);
+      illustration.setIllustration(illustrationDrawable);
+    }
+  }
+
+  /**
+   * Sets the aspect ratio of the illustration. This will be the space (padding top) reserved above
+   * the header text. This will override the padding top of the illustration.
+   *
+   * @param aspectRatio The aspect ratio
+   * @see com.android.setupwizardlib.view.Illustration#setAspectRatio(float)
+   */
+  public void setIllustrationAspectRatio(float aspectRatio) {
+    final View view = findManagedViewById(R.id.suw_layout_decor);
+    if (view instanceof Illustration) {
+      final Illustration illustration = (Illustration) view;
+      illustration.setAspectRatio(aspectRatio);
+    }
+  }
+
+  /**
+   * Set the top padding of the decor view. If the decor is an Illustration and the aspect ratio is
+   * set, this value will be overridden.
+   *
+   * 

Note: Currently the default top padding for tablet landscape is 128dp, which is the offset + * of the card from the top. This is likely to change in future versions so this value aligns with + * the height of the illustration instead. + * + * @param paddingTop The top padding in pixels. + */ + public void setDecorPaddingTop(int paddingTop) { + final View view = findManagedViewById(R.id.suw_layout_decor); + if (view != null) { + view.setPadding( + view.getPaddingLeft(), paddingTop, view.getPaddingRight(), view.getPaddingBottom()); + } + } + + /** + * Set the background of the layout, which is expected to be able to extend infinitely. If it is a + * bitmap tile and you want it to repeat, use {@link #setBackgroundTile(int)} instead. + */ + public void setLayoutBackground(Drawable background) { + final View view = findManagedViewById(R.id.suw_layout_decor); + if (view != null) { + //noinspection deprecation + view.setBackgroundDrawable(background); + } + } + + /** + * Set the background of the layout to a repeating bitmap tile. To use a different kind of + * drawable, use {@link #setLayoutBackground(android.graphics.drawable.Drawable)} instead. + */ + public void setBackgroundTile(int backgroundTile) { + final Drawable backgroundTileDrawable = getContext().getResources().getDrawable(backgroundTile); + setBackgroundTile(backgroundTileDrawable); + } + + private void setBackgroundTile(Drawable backgroundTile) { + if (backgroundTile instanceof BitmapDrawable) { + ((BitmapDrawable) backgroundTile).setTileModeXY(TileMode.REPEAT, TileMode.REPEAT); + } + setLayoutBackground(backgroundTile); + } + + private Drawable getIllustration(int asset, int horizontalTile) { + final Context context = getContext(); + final Drawable assetDrawable = context.getResources().getDrawable(asset); + final Drawable tile = context.getResources().getDrawable(horizontalTile); + return getIllustration(assetDrawable, tile); + } + + @SuppressLint("RtlHardcoded") + private Drawable getIllustration(Drawable asset, Drawable horizontalTile) { + final Context context = getContext(); + if (context.getResources().getBoolean(R.bool.suwUseTabletLayout)) { + // If it is a "tablet" (sw600dp), create a LayerDrawable with the horizontal tile. + if (horizontalTile instanceof BitmapDrawable) { + ((BitmapDrawable) horizontalTile).setTileModeX(TileMode.REPEAT); + ((BitmapDrawable) horizontalTile).setGravity(Gravity.TOP); + } + if (asset instanceof BitmapDrawable) { + // Always specify TOP | LEFT, Illustration will flip the entire LayerDrawable. + ((BitmapDrawable) asset).setGravity(Gravity.TOP | Gravity.LEFT); + } + final LayerDrawable layers = new LayerDrawable(new Drawable[] {horizontalTile, asset}); + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + layers.setAutoMirrored(true); + } + return layers; + } else { + // If it is a "phone" (not sw600dp), simply return the illustration + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + asset.setAutoMirrored(true); + } + return asset; + } + } + + public boolean isProgressBarShown() { + return getMixin(ProgressBarMixin.class).isShown(); + } + + /** + * Sets whether the progress bar below the header text is shown or not. The progress bar is a + * lazily inflated ViewStub, which means the progress bar will not actually be part of the view + * hierarchy until the first time this is set to {@code true}. + */ + public void setProgressBarShown(boolean shown) { + getMixin(ProgressBarMixin.class).setShown(shown); + } + + /** @deprecated Use {@link #setProgressBarShown(boolean)} */ + @Deprecated + public void showProgressBar() { + setProgressBarShown(true); + } + + /** @deprecated Use {@link #setProgressBarShown(boolean)} */ + @Deprecated + public void hideProgressBar() { + setProgressBarShown(false); + } + + public void setProgressBarColor(ColorStateList color) { + getMixin(ProgressBarMixin.class).setColor(color); + } + + public ColorStateList getProgressBarColor() { + return getMixin(ProgressBarMixin.class).getColor(); + } + + /* Misc */ + + protected static class SavedState extends BaseSavedState { + + boolean isProgressBarShown = false; + + public SavedState(Parcelable parcelable) { + super(parcelable); + } + + public SavedState(Parcel source) { + super(source); + isProgressBarShown = source.readInt() != 0; } @Override - protected void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - Log.w(TAG, "Ignoring restore instance state " + state); - super.onRestoreInstanceState(state); - return; - } - - final SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - final boolean isProgressBarShown = ss.mIsProgressBarShown; - setProgressBarShown(isProgressBarShown); - } - - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_template; - } - return inflateTemplate(inflater, R.style.SuwThemeMaterial_Light, template); - } - - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_layout_content; - } - return super.findContainer(containerId); - } - - public NavigationBar getNavigationBar() { - return getMixin(NavigationBarMixin.class).getNavigationBar(); - } - - public ScrollView getScrollView() { - final View view = findManagedViewById(R.id.suw_bottom_scroll_view); - return view instanceof ScrollView ? (ScrollView) view : null; - } - - public void requireScrollToBottom() { - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - final NavigationBar navigationBar = getNavigationBar(); - if (navigationBar != null) { - requireScrollMixin.requireScrollWithNavigationBar(navigationBar); - } else { - Log.e(TAG, "Cannot require scroll. Navigation bar is null."); - } - } - - public void setHeaderText(int title) { - getMixin(HeaderMixin.class).setText(title); - } - - public void setHeaderText(CharSequence title) { - getMixin(HeaderMixin.class).setText(title); - } - - public CharSequence getHeaderText() { - return getMixin(HeaderMixin.class).getText(); - } - - public TextView getHeaderTextView() { - return getMixin(HeaderMixin.class).getTextView(); - } - - /** - * Set the illustration of the layout. The drawable will be applied as is, and the bounds will - * be set as implemented in {@link com.android.setupwizardlib.view.Illustration}. To create - * a suitable drawable from an asset and a horizontal repeating tile, use - * {@link #setIllustration(int, int)} instead. - * - * @param drawable The drawable specifying the illustration. - */ - public void setIllustration(Drawable drawable) { - final View view = findManagedViewById(R.id.suw_layout_decor); - if (view instanceof Illustration) { - final Illustration illustration = (Illustration) view; - illustration.setIllustration(drawable); - } + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(isProgressBarShown ? 1 : 0); } - /** - * Set the illustration of the layout, which will be created asset and the horizontal tile as - * suitable. On phone layouts (not sw600dp), the asset will be scaled, maintaining aspect ratio. - * On tablets (sw600dp), the assets will always have 256dp height and the rest of the - * illustration area that the asset doesn't fill will be covered by the horizontalTile. - * - * @param asset Resource ID of the illustration asset. - * @param horizontalTile Resource ID of the horizontally repeating tile for tablet layout. - */ - public void setIllustration(int asset, int horizontalTile) { - final View view = findManagedViewById(R.id.suw_layout_decor); - if (view instanceof Illustration) { - final Illustration illustration = (Illustration) view; - final Drawable illustrationDrawable = getIllustration(asset, horizontalTile); - illustration.setIllustration(illustrationDrawable); - } - } - - private void setIllustration(Drawable asset, Drawable horizontalTile) { - final View view = findManagedViewById(R.id.suw_layout_decor); - if (view instanceof Illustration) { - final Illustration illustration = (Illustration) view; - final Drawable illustrationDrawable = getIllustration(asset, horizontalTile); - illustration.setIllustration(illustrationDrawable); - } - } - - /** - * Sets the aspect ratio of the illustration. This will be the space (padding top) reserved - * above the header text. This will override the padding top of the illustration. - * - * @param aspectRatio The aspect ratio - * @see com.android.setupwizardlib.view.Illustration#setAspectRatio(float) - */ - public void setIllustrationAspectRatio(float aspectRatio) { - final View view = findManagedViewById(R.id.suw_layout_decor); - if (view instanceof Illustration) { - final Illustration illustration = (Illustration) view; - illustration.setAspectRatio(aspectRatio); - } - } + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { - /** - * Set the top padding of the decor view. If the decor is an Illustration and the aspect ratio - * is set, this value will be overridden. - * - *

Note: Currently the default top padding for tablet landscape is 128dp, which is the offset - * of the card from the top. This is likely to change in future versions so this value aligns - * with the height of the illustration instead. - * - * @param paddingTop The top padding in pixels. - */ - public void setDecorPaddingTop(int paddingTop) { - final View view = findManagedViewById(R.id.suw_layout_decor); - if (view != null) { - view.setPadding(view.getPaddingLeft(), paddingTop, view.getPaddingRight(), - view.getPaddingBottom()); - } - } - - /** - * Set the background of the layout, which is expected to be able to extend infinitely. If it is - * a bitmap tile and you want it to repeat, use {@link #setBackgroundTile(int)} instead. - */ - public void setLayoutBackground(Drawable background) { - final View view = findManagedViewById(R.id.suw_layout_decor); - if (view != null) { - //noinspection deprecation - view.setBackgroundDrawable(background); - } - } - - /** - * Set the background of the layout to a repeating bitmap tile. To use a different kind of - * drawable, use {@link #setLayoutBackground(android.graphics.drawable.Drawable)} instead. - */ - public void setBackgroundTile(int backgroundTile) { - final Drawable backgroundTileDrawable = - getContext().getResources().getDrawable(backgroundTile); - setBackgroundTile(backgroundTileDrawable); - } + @Override + public SavedState createFromParcel(Parcel parcel) { + return new SavedState(parcel); + } - private void setBackgroundTile(Drawable backgroundTile) { - if (backgroundTile instanceof BitmapDrawable) { - ((BitmapDrawable) backgroundTile).setTileModeXY(TileMode.REPEAT, TileMode.REPEAT); - } - setLayoutBackground(backgroundTile); - } - - private Drawable getIllustration(int asset, int horizontalTile) { - final Context context = getContext(); - final Drawable assetDrawable = context.getResources().getDrawable(asset); - final Drawable tile = context.getResources().getDrawable(horizontalTile); - return getIllustration(assetDrawable, tile); - } - - @SuppressLint("RtlHardcoded") - private Drawable getIllustration(Drawable asset, Drawable horizontalTile) { - final Context context = getContext(); - if (context.getResources().getBoolean(R.bool.suwUseTabletLayout)) { - // If it is a "tablet" (sw600dp), create a LayerDrawable with the horizontal tile. - if (horizontalTile instanceof BitmapDrawable) { - ((BitmapDrawable) horizontalTile).setTileModeX(TileMode.REPEAT); - ((BitmapDrawable) horizontalTile).setGravity(Gravity.TOP); - } - if (asset instanceof BitmapDrawable) { - // Always specify TOP | LEFT, Illustration will flip the entire LayerDrawable. - ((BitmapDrawable) asset).setGravity(Gravity.TOP | Gravity.LEFT); - } - final LayerDrawable layers = - new LayerDrawable(new Drawable[] { horizontalTile, asset }); - if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - layers.setAutoMirrored(true); - } - return layers; - } else { - // If it is a "phone" (not sw600dp), simply return the illustration - if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - asset.setAutoMirrored(true); - } - return asset; - } - } - - public boolean isProgressBarShown() { - return getMixin(ProgressBarMixin.class).isShown(); - } - - /** - * Sets whether the progress bar below the header text is shown or not. The progress bar is - * a lazily inflated ViewStub, which means the progress bar will not actually be part of the - * view hierarchy until the first time this is set to {@code true}. - */ - public void setProgressBarShown(boolean shown) { - getMixin(ProgressBarMixin.class).setShown(shown); - } - - /** - * @deprecated Use {@link #setProgressBarShown(boolean)} - */ - @Deprecated - public void showProgressBar() { - setProgressBarShown(true); - } - - /** - * @deprecated Use {@link #setProgressBarShown(boolean)} - */ - @Deprecated - public void hideProgressBar() { - setProgressBarShown(false); - } - - public void setProgressBarColor(ColorStateList color) { - getMixin(ProgressBarMixin.class).setColor(color); - } - - public ColorStateList getProgressBarColor() { - return getMixin(ProgressBarMixin.class).getColor(); - } - - /* Misc */ - - protected static class SavedState extends BaseSavedState { - - boolean mIsProgressBarShown = false; - - public SavedState(Parcelable parcelable) { - super(parcelable); - } - - public SavedState(Parcel source) { - super(source); - mIsProgressBarShown = source.readInt() != 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mIsProgressBarShown ? 1 : 0); - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - - @Override - public SavedState createFromParcel(Parcel parcel) { - return new SavedState(parcel); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } } diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java index 050d566..f1c7e11 100644 --- a/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java +++ b/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java @@ -26,141 +26,128 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ListAdapter; import android.widget.ListView; - import com.android.setupwizardlib.template.ListMixin; import com.android.setupwizardlib.template.ListViewScrollHandlingDelegate; import com.android.setupwizardlib.template.RequireScrollMixin; public class SetupWizardListLayout extends SetupWizardLayout { - private static final String TAG = "SetupWizardListLayout"; - - private ListMixin mListMixin; - - public SetupWizardListLayout(Context context) { - this(context, 0, 0); - } - - public SetupWizardListLayout(Context context, int template) { - this(context, template, 0); - } - - public SetupWizardListLayout(Context context, int template, int containerId) { - super(context, template, containerId); - init(context, null, 0); - } - - public SetupWizardListLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - @TargetApi(VERSION_CODES.HONEYCOMB) - public SetupWizardListLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - mListMixin = new ListMixin(this, attrs, defStyleAttr); - registerMixin(ListMixin.class, mListMixin); - - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - requireScrollMixin.setScrollHandlingDelegate( - new ListViewScrollHandlingDelegate(requireScrollMixin, getListView())); - } - - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_list_template; - } - return super.onInflateTemplate(inflater, template); - } - - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = android.R.id.list; - } - return super.findContainer(containerId); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mListMixin.onLayout(); - } - - public ListView getListView() { - return mListMixin.getListView(); - } - - public void setAdapter(ListAdapter adapter) { - mListMixin.setAdapter(adapter); - } - - public ListAdapter getAdapter() { - return mListMixin.getAdapter(); - } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and inset it {@code inset} pixels to the right (or left in RTL layouts). - * - * @param inset The number of pixels to inset on the "start" side of the list divider. Typically - * this will be either {@code @dimen/suw_items_icon_divider_inset} or - * {@code @dimen/suw_items_text_divider_inset}. - * - * @see ListMixin#setDividerInset(int) - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - mListMixin.setDividerInset(inset); - } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start The number of pixels to inset on the "start" side of the list divider. Typically - * this will be either {@code @dimen/suw_items_icon_divider_inset} or - * {@code @dimen/suw_items_text_divider_inset}. - * @param end The number of pixels to inset on the "end" side of the list divider. - * - * @see ListMixin#setDividerInsets(int, int) - */ - public void setDividerInsets(int start, int end) { - mListMixin.setDividerInsets(start, end); - } - - /** - * @deprecated Use {@link #getDividerInsetStart()} instead. - */ - @Deprecated - public int getDividerInset() { - return mListMixin.getDividerInset(); - } - - /** - * @see ListMixin#getDividerInsetStart() - */ - public int getDividerInsetStart() { - return mListMixin.getDividerInsetStart(); - } - - /** - * @see ListMixin#getDividerInsetEnd() - */ - public int getDividerInsetEnd() { - return mListMixin.getDividerInsetEnd(); - } - - /** - * @see ListMixin#getDivider() - */ - public Drawable getDivider() { - return mListMixin.getDivider(); - } + private ListMixin listMixin; + + public SetupWizardListLayout(Context context) { + this(context, 0, 0); + } + + public SetupWizardListLayout(Context context, int template) { + this(context, template, 0); + } + + public SetupWizardListLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, 0); + } + + public SetupWizardListLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public SetupWizardListLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + listMixin = new ListMixin(this, attrs, defStyleAttr); + registerMixin(ListMixin.class, listMixin); + + final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); + requireScrollMixin.setScrollHandlingDelegate( + new ListViewScrollHandlingDelegate(requireScrollMixin, getListView())); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_list_template; + } + return super.onInflateTemplate(inflater, template); + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = android.R.id.list; + } + return super.findContainer(containerId); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + listMixin.onLayout(); + } + + public ListView getListView() { + return listMixin.getListView(); + } + + public void setAdapter(ListAdapter adapter) { + listMixin.setAdapter(adapter); + } + + public ListAdapter getAdapter() { + return listMixin.getAdapter(); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and inset it {@code inset} pixels to the right (or left in RTL layouts). + * + * @param inset The number of pixels to inset on the "start" side of the list divider. Typically + * this will be either {@code @dimen/suw_items_icon_divider_inset} or + * {@code @dimen/suw_items_text_divider_inset}. + * @see ListMixin#setDividerInset(int) + * @deprecated Use {@link #setDividerInsets(int, int)} instead. + */ + @Deprecated + public void setDividerInset(int inset) { + listMixin.setDividerInset(inset); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start The number of pixels to inset on the "start" side of the list divider. Typically + * this will be either {@code @dimen/suw_items_icon_divider_inset} or + * {@code @dimen/suw_items_text_divider_inset}. + * @param end The number of pixels to inset on the "end" side of the list divider. + * @see ListMixin#setDividerInsets(int, int) + */ + public void setDividerInsets(int start, int end) { + listMixin.setDividerInsets(start, end); + } + + /** @deprecated Use {@link #getDividerInsetStart()} instead. */ + @Deprecated + public int getDividerInset() { + return listMixin.getDividerInset(); + } + + /** @see ListMixin#getDividerInsetStart() */ + public int getDividerInsetStart() { + return listMixin.getDividerInsetStart(); + } + + /** @see ListMixin#getDividerInsetEnd() */ + public int getDividerInsetEnd() { + return listMixin.getDividerInsetEnd(); + } + + /** @see ListMixin#getDivider() */ + public Drawable getDivider() { + return listMixin.getDivider(); + } } diff --git a/library/main/src/com/android/setupwizardlib/TemplateLayout.java b/library/main/src/com/android/setupwizardlib/TemplateLayout.java index 0108880..c53e176 100644 --- a/library/main/src/com/android/setupwizardlib/TemplateLayout.java +++ b/library/main/src/com/android/setupwizardlib/TemplateLayout.java @@ -20,256 +20,249 @@ import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.os.Build.VERSION_CODES; +import androidx.annotation.Keep; +import androidx.annotation.LayoutRes; +import androidx.annotation.StyleRes; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; - -import androidx.annotation.Keep; -import androidx.annotation.LayoutRes; -import androidx.annotation.StyleRes; - import com.android.setupwizardlib.template.Mixin; import com.android.setupwizardlib.util.FallbackThemeWrapper; - import java.util.HashMap; import java.util.Map; /** - * A generic template class that inflates a template, provided in the constructor or in - * {@code android:layout} through XML, and adds its children to a "container" in the template. When + * A generic template class that inflates a template, provided in the constructor or in {@code + * android:layout} through XML, and adds its children to a "container" in the template. When * inflating this layout from XML, the {@code android:layout} and {@code suwContainer} attributes * are required. */ public class TemplateLayout extends FrameLayout { - /** - * The container of the actual content. This will be a view in the template, which child views - * will be added to when {@link #addView(View)} is called. - */ - private ViewGroup mContainer; - - private Map, Mixin> mMixins = new HashMap<>(); + /** + * The container of the actual content. This will be a view in the template, which child views + * will be added to when {@link #addView(View)} is called. + */ + private ViewGroup container; - public TemplateLayout(Context context, int template, int containerId) { - super(context); - init(template, containerId, null, R.attr.suwLayoutTheme); - } + private final Map, Mixin> mixins = new HashMap<>(); - public TemplateLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(0, 0, attrs, R.attr.suwLayoutTheme); - } + public TemplateLayout(Context context, int template, int containerId) { + super(context); + init(template, containerId, null, R.attr.suwLayoutTheme); + } - @TargetApi(VERSION_CODES.HONEYCOMB) - public TemplateLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(0, 0, attrs, defStyleAttr); - } + public TemplateLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(0, 0, attrs, R.attr.suwLayoutTheme); + } - // All the constructors delegate to this init method. The 3-argument constructor is not - // available in LinearLayout before v11, so call super with the exact same arguments. - private void init(int template, int containerId, AttributeSet attrs, int defStyleAttr) { - final TypedArray a = getContext().obtainStyledAttributes(attrs, - R.styleable.SuwTemplateLayout, defStyleAttr, 0); - if (template == 0) { - template = a.getResourceId(R.styleable.SuwTemplateLayout_android_layout, 0); - } - if (containerId == 0) { - containerId = a.getResourceId(R.styleable.SuwTemplateLayout_suwContainer, 0); - } - inflateTemplate(template, containerId); + @TargetApi(VERSION_CODES.HONEYCOMB) + public TemplateLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(0, 0, attrs, defStyleAttr); + } - a.recycle(); + // All the constructors delegate to this init method. The 3-argument constructor is not + // available in LinearLayout before v11, so call super with the exact same arguments. + private void init(int template, int containerId, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = + getContext().obtainStyledAttributes(attrs, R.styleable.SuwTemplateLayout, defStyleAttr, 0); + if (template == 0) { + template = a.getResourceId(R.styleable.SuwTemplateLayout_android_layout, 0); } - - /** - * Registers a mixin with a given class. This method should be called in the constructor. - * - * @param cls The class to register the mixin. In most cases, {@code cls} is the same as - * {@code mixin.getClass()}, but {@code cls} can also be a super class of that. In - * the latter case the the mixin must be retrieved using {@code cls} in - * {@link #getMixin(Class)}, not the subclass. - * @param mixin The mixin to be registered. - * @param The class of the mixin to register. This is the same as {@code cls} - */ - protected void registerMixin(Class cls, M mixin) { - mMixins.put(cls, mixin); + if (containerId == 0) { + containerId = a.getResourceId(R.styleable.SuwTemplateLayout_suwContainer, 0); } + inflateTemplate(template, containerId); - /** - * Same as {@link android.view.View#findViewById(int)}, but may include views that are managed - * by this view but not currently added to the view hierarchy. e.g. recycler view or list view - * headers that are not currently shown. - */ - // Returning generic type is the common pattern used for findViewBy* methods - @SuppressWarnings("TypeParameterUnusedInFormals") - public T findManagedViewById(int id) { - return findViewById(id); - } + a.recycle(); + } - /** - * Get a {@link Mixin} from this template registered earlier in - * {@link #registerMixin(Class, Mixin)}. - * - * @param cls The class marker of Mixin being requested. The actual Mixin returned may be a - * subclass of this marker. Note that this must be the same class as registered in - * {@link #registerMixin(Class, Mixin)}, which is not necessarily the - * same as the concrete class of the instance returned by this method. - * @param The type of the class marker. - * @return The mixin marked by {@code cls}, or null if the template does not have a matching - * mixin. - */ - @SuppressWarnings("unchecked") - public M getMixin(Class cls) { - return (M) mMixins.get(cls); - } + /** + * Registers a mixin with a given class. This method should be called in the constructor. + * + * @param cls The class to register the mixin. In most cases, {@code cls} is the same as {@code + * mixin.getClass()}, but {@code cls} can also be a super class of that. In the latter case + * the mixin must be retrieved using {@code cls} in {@link #getMixin(Class)}, not the + * subclass. + * @param mixin The mixin to be registered. + * @param The class of the mixin to register. This is the same as {@code cls} + */ + protected void registerMixin(Class cls, M mixin) { + mixins.put(cls, mixin); + } - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - mContainer.addView(child, index, params); - } + /** + * Same as {@link android.view.View#findViewById(int)}, but may include views that are managed by + * this view but not currently added to the view hierarchy. e.g. recycler view or list view + * headers that are not currently shown. + */ + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public T findManagedViewById(int id) { + return findViewById(id); + } - private void addViewInternal(View child) { - super.addView(child, -1, generateDefaultLayoutParams()); - } + /** + * Get a {@link Mixin} from this template registered earlier in {@link #registerMixin(Class, + * Mixin)}. + * + * @param cls The class marker of Mixin being requested. The actual Mixin returned may be a + * subclass of this marker. Note that this must be the same class as registered in {@link + * #registerMixin(Class, Mixin)}, which is not necessarily the same as the concrete class of + * the instance returned by this method. + * @param The type of the class marker. + * @return The mixin marked by {@code cls}, or null if the template does not have a matching + * mixin. + */ + @SuppressWarnings("unchecked") + public M getMixin(Class cls) { + return (M) mixins.get(cls); + } - private void inflateTemplate(int templateResource, int containerId) { - final LayoutInflater inflater = LayoutInflater.from(getContext()); - final View templateRoot = onInflateTemplate(inflater, templateResource); - addViewInternal(templateRoot); + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + container.addView(child, index, params); + } - mContainer = findContainer(containerId); - if (mContainer == null) { - throw new IllegalArgumentException("Container cannot be null in TemplateLayout"); - } - onTemplateInflated(); - } + private void addViewInternal(View child) { + super.addView(child, -1, generateDefaultLayoutParams()); + } - /** - * This method inflates the template. Subclasses can override this method to customize the - * template inflation, or change to a different default template. The root of the inflated - * layout should be returned, and not added to the view hierarchy. - * - * @param inflater A LayoutInflater to inflate the template. - * @param template The resource ID of the template to be inflated, or 0 if no template is - * specified. - * @return Root of the inflated layout. - */ - protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) { - return inflateTemplate(inflater, 0, template); - } + private void inflateTemplate(int templateResource, int containerId) { + final LayoutInflater inflater = LayoutInflater.from(getContext()); + final View templateRoot = onInflateTemplate(inflater, templateResource); + addViewInternal(templateRoot); - /** - * Inflate the template using the given inflater and theme. The fallback theme will be applied - * to the theme without overriding the values already defined in the theme, but simply providing - * default values for values which have not been defined. This allows templates to add - * additional required theme attributes without breaking existing clients. - * - *

In general, clients should still set the activity theme to the corresponding theme in - * setup wizard lib, so that the content area gets the correct styles as well. - * - * @param inflater A LayoutInflater to inflate the template. - * @param fallbackTheme A fallback theme to apply to the template. If the values defined in the - * fallback theme is already defined in the original theme, the value in - * the original theme takes precedence. - * @param template The layout template to be inflated. - * @return Root of the inflated layout. - * - * @see FallbackThemeWrapper - */ - protected final View inflateTemplate(LayoutInflater inflater, @StyleRes int fallbackTheme, - @LayoutRes int template) { - if (template == 0) { - throw new IllegalArgumentException("android:layout not specified for TemplateLayout"); - } - if (fallbackTheme != 0) { - inflater = LayoutInflater.from( - new FallbackThemeWrapper(inflater.getContext(), fallbackTheme)); - } - return inflater.inflate(template, this, false); + container = findContainer(containerId); + if (container == null) { + throw new IllegalArgumentException("Container cannot be null in TemplateLayout"); } + onTemplateInflated(); + } - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - // Maintain compatibility with the deprecated way of specifying container ID. - containerId = getContainerId(); - } - return (ViewGroup) findViewById(containerId); + /** + * Inflate the template using the given inflater and theme. The fallback theme will be applied to + * the theme without overriding the values already defined in the theme, but simply providing + * default values for values which have not been defined. This allows templates to add additional + * required theme attributes without breaking existing clients. + * + *

In general, clients should still set the activity theme to the corresponding theme in setup + * wizard lib, so that the content area gets the correct styles as well. + * + * @param inflater A LayoutInflater to inflate the template. + * @param fallbackTheme A fallback theme to apply to the template. If the values defined in the + * fallback theme is already defined in the original theme, the value in the original theme + * takes precedence. + * @param template The layout template to be inflated. + * @return Root of the inflated layout. + * @see FallbackThemeWrapper + */ + protected final View inflateTemplate( + LayoutInflater inflater, @StyleRes int fallbackTheme, @LayoutRes int template) { + if (template == 0) { + throw new IllegalArgumentException("android:layout not specified for TemplateLayout"); } - - /** - * This is called after the template has been inflated and added to the view hierarchy. - * Subclasses can implement this method to modify the template as necessary, such as caching - * views retrieved from findViewById, or other view operations that need to be done in code. - * You can think of this as {@link View#onFinishInflate()} but for inflation of the - * template instead of for child views. - */ - protected void onTemplateInflated() { + if (fallbackTheme != 0) { + inflater = + LayoutInflater.from(new FallbackThemeWrapper(inflater.getContext(), fallbackTheme)); } + return inflater.inflate(template, this, false); + } + + /** + * This method inflates the template. Subclasses can override this method to customize the + * template inflation, or change to a different default template. The root of the inflated layout + * should be returned, and not added to the view hierarchy. + * + * @param inflater A LayoutInflater to inflate the template. + * @param template The resource ID of the template to be inflated, or 0 if no template is + * specified. + * @return Root of the inflated layout. + */ + protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) { + return inflateTemplate(inflater, 0, template); + } - /** - * @return ID of the default container for this layout. This will be used to find the container - * ViewGroup, which all children views of this layout will be placed in. - * @deprecated Override {@link #findContainer(int)} instead. - */ - @Deprecated - protected int getContainerId() { - return 0; + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + // Maintain compatibility with the deprecated way of specifying container ID. + containerId = getContainerId(); } + return (ViewGroup) findViewById(containerId); + } - /* Animator support */ + /** + * This is called after the template has been inflated and added to the view hierarchy. Subclasses + * can implement this method to modify the template as necessary, such as caching views retrieved + * from findViewById, or other view operations that need to be done in code. You can think of this + * as {@link View#onFinishInflate()} but for inflation of the template instead of for child views. + */ + protected void onTemplateInflated() {} - private float mXFraction; - private ViewTreeObserver.OnPreDrawListener mPreDrawListener; + /** + * @return ID of the default container for this layout. This will be used to find the container + * ViewGroup, which all children views of this layout will be placed in. + * @deprecated Override {@link #findContainer(int)} instead. + */ + @Deprecated + protected int getContainerId() { + return 0; + } - /** - * Set the X translation as a fraction of the width of this view. Make sure this method is not - * stripped out by proguard when using this with {@link android.animation.ObjectAnimator}. You - * may need to add - * - * -keep @androidx.annotation.Keep class * - * - * to your proguard configuration if you are seeing mysterious {@link NoSuchMethodError} at - * runtime. - */ - @Keep - @TargetApi(VERSION_CODES.HONEYCOMB) - public void setXFraction(float fraction) { - mXFraction = fraction; - final int width = getWidth(); - if (width != 0) { - setTranslationX(width * fraction); - } else { - // If we haven't done a layout pass yet, wait for one and then set the fraction before - // the draw occurs using an OnPreDrawListener. Don't call translationX until we know - // getWidth() has a reliable, non-zero value or else we will see the fragment flicker on - // screen. - if (mPreDrawListener == null) { - mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener); - setXFraction(mXFraction); - return true; - } - }; - getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); - } - } - } + /* Animator support */ + + private float xFraction; + private ViewTreeObserver.OnPreDrawListener preDrawListener; - /** - * Return the X translation as a fraction of the width, as previously set in - * {@link #setXFraction(float)}. - * - * @see #setXFraction(float) - */ - @Keep - @TargetApi(VERSION_CODES.HONEYCOMB) - public float getXFraction() { - return mXFraction; + /** + * Set the X translation as a fraction of the width of this view. Make sure this method is not + * stripped out by proguard when using this with {@link android.animation.ObjectAnimator}. You may + * need to add + * -keep @androidx.annotation.Keep class * + * to your proguard configuration if you are seeing mysterious {@link NoSuchMethodError} + * at runtime. + */ + @Keep + @TargetApi(VERSION_CODES.HONEYCOMB) + public void setXFraction(float fraction) { + xFraction = fraction; + final int width = getWidth(); + if (width != 0) { + setTranslationX(width * fraction); + } else { + // If we haven't done a layout pass yet, wait for one and then set the fraction before + // the draw occurs using an OnPreDrawListener. Don't call translationX until we know + // getWidth() has a reliable, non-zero value or else we will see the fragment flicker on + // screen. + if (preDrawListener == null) { + preDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getViewTreeObserver().removeOnPreDrawListener(preDrawListener); + setXFraction(xFraction); + return true; + } + }; + getViewTreeObserver().addOnPreDrawListener(preDrawListener); + } } + } + + /** + * Return the X translation as a fraction of the width, as previously set in {@link + * #setXFraction(float)}. + * + * @see #setXFraction(float) + */ + @Keep + @TargetApi(VERSION_CODES.HONEYCOMB) + public float getXFraction() { + return xFraction; + } } diff --git a/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java b/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java index f438691..2ea5288 100644 --- a/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java +++ b/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java @@ -24,95 +24,86 @@ import android.view.ViewConfiguration; /** * Helper class to detect the consective-tap gestures on a view. * - *

This class is instantiated and used similar to a GestureDetector, where onTouchEvent should - * be called when there are MotionEvents this detector should know about. + *

This class is instantiated and used similar to a GestureDetector, where onTouchEvent should be + * called when there are MotionEvents this detector should know about. */ public final class ConsecutiveTapsGestureDetector { - public interface OnConsecutiveTapsListener { - /** - * Callback method when the user tapped on the target view X number of times. - */ - void onConsecutiveTaps(int numOfConsecutiveTaps); - } - - private final View mView; - private final OnConsecutiveTapsListener mListener; - private final int mConsecutiveTapTouchSlopSquare; - private final int mConsecutiveTapTimeout; + public interface OnConsecutiveTapsListener { + /** Callback method when the user tapped on the target view X number of times. */ + void onConsecutiveTaps(int numOfConsecutiveTaps); + } - private int mConsecutiveTapsCounter = 0; - private MotionEvent mPreviousTapEvent; + private final View view; + private final OnConsecutiveTapsListener listener; + private final int consecutiveTapTouchSlopSquare; + private final int consecutiveTapTimeout; - /** - * @param listener The listener that responds to the gesture. - * @param view The target view that associated with consecutive-tap gesture. - */ - public ConsecutiveTapsGestureDetector( - OnConsecutiveTapsListener listener, - View view) { - mListener = listener; - mView = view; - int doubleTapSlop = ViewConfiguration.get(mView.getContext()).getScaledDoubleTapSlop(); - mConsecutiveTapTouchSlopSquare = doubleTapSlop * doubleTapSlop; - mConsecutiveTapTimeout = ViewConfiguration.getDoubleTapTimeout(); - } + private int consecutiveTapsCounter = 0; + private MotionEvent previousTapEvent; - /** - * This method should be called from the relevant activity or view, typically in - * onTouchEvent, onInterceptTouchEvent or dispatchTouchEvent. - * - * @param ev The motion event - */ - public void onTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_UP) { - Rect viewRect = new Rect(); - int[] leftTop = new int[2]; - mView.getLocationOnScreen(leftTop); - viewRect.set( - leftTop[0], - leftTop[1], - leftTop[0] + mView.getWidth(), - leftTop[1] + mView.getHeight()); - if (viewRect.contains((int) ev.getX(), (int) ev.getY())) { - if (isConsecutiveTap(ev)) { - mConsecutiveTapsCounter++; - } else { - mConsecutiveTapsCounter = 1; - } - mListener.onConsecutiveTaps(mConsecutiveTapsCounter); - } else { - // Touch outside the target view. Reset counter. - mConsecutiveTapsCounter = 0; - } + /** + * @param listener The listener that responds to the gesture. + * @param view The target view that associated with consecutive-tap gesture. + */ + public ConsecutiveTapsGestureDetector(OnConsecutiveTapsListener listener, View view) { + this.listener = listener; + this.view = view; + int doubleTapSlop = ViewConfiguration.get(this.view.getContext()).getScaledDoubleTapSlop(); + consecutiveTapTouchSlopSquare = doubleTapSlop * doubleTapSlop; + consecutiveTapTimeout = ViewConfiguration.getDoubleTapTimeout(); + } - if (mPreviousTapEvent != null) { - mPreviousTapEvent.recycle(); - } - mPreviousTapEvent = MotionEvent.obtain(ev); + /** + * This method should be called from the relevant activity or view, typically in onTouchEvent, + * onInterceptTouchEvent or dispatchTouchEvent. + * + * @param ev The motion event + */ + public void onTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + Rect viewRect = new Rect(); + int[] leftTop = new int[2]; + view.getLocationOnScreen(leftTop); + viewRect.set( + leftTop[0], leftTop[1], leftTop[0] + view.getWidth(), leftTop[1] + view.getHeight()); + if (viewRect.contains((int) ev.getX(), (int) ev.getY())) { + if (isConsecutiveTap(ev)) { + consecutiveTapsCounter++; + } else { + consecutiveTapsCounter = 1; } - } + listener.onConsecutiveTaps(consecutiveTapsCounter); + } else { + // Touch outside the target view. Reset counter. + consecutiveTapsCounter = 0; + } - /** - * Resets the consecutive-tap counter to zero. - */ - public void resetCounter() { - mConsecutiveTapsCounter = 0; + if (previousTapEvent != null) { + previousTapEvent.recycle(); + } + previousTapEvent = MotionEvent.obtain(ev); } + } - /** - * Returns true if the distance between consecutive tap is within - * {@link #mConsecutiveTapTouchSlopSquare}. False, otherwise. - */ - private boolean isConsecutiveTap(MotionEvent currentTapEvent) { - if (mPreviousTapEvent == null) { - return false; - } + /** Resets the consecutive-tap counter to zero. */ + public void resetCounter() { + consecutiveTapsCounter = 0; + } - double deltaX = mPreviousTapEvent.getX() - currentTapEvent.getX(); - double deltaY = mPreviousTapEvent.getY() - currentTapEvent.getY(); - long deltaTime = currentTapEvent.getEventTime() - mPreviousTapEvent.getEventTime(); - return (deltaX * deltaX + deltaY * deltaY <= mConsecutiveTapTouchSlopSquare) - && deltaTime < mConsecutiveTapTimeout; + /** + * Returns true if the distance between consecutive tap is within {@link + * #consecutiveTapTouchSlopSquare}. False, otherwise. + */ + private boolean isConsecutiveTap(MotionEvent currentTapEvent) { + if (previousTapEvent == null) { + return false; } + + double deltaX = previousTapEvent.getX() - currentTapEvent.getX(); + double deltaY = previousTapEvent.getY() - currentTapEvent.getY(); + long deltaTime = currentTapEvent.getEventTime() - previousTapEvent.getEventTime(); + return (deltaX * deltaX + deltaY * deltaY <= consecutiveTapTouchSlopSquare) + && deltaTime < consecutiveTapTimeout; + } } diff --git a/library/main/src/com/android/setupwizardlib/items/AbstractItem.java b/library/main/src/com/android/setupwizardlib/items/AbstractItem.java index 11a9939..88f9294 100644 --- a/library/main/src/com/android/setupwizardlib/items/AbstractItem.java +++ b/library/main/src/com/android/setupwizardlib/items/AbstractItem.java @@ -25,42 +25,42 @@ import android.util.AttributeSet; */ public abstract class AbstractItem extends AbstractItemHierarchy implements IItem { - public AbstractItem() { - super(); - } + public AbstractItem() { + super(); + } - public AbstractItem(Context context, AttributeSet attrs) { - super(context, attrs); - } + public AbstractItem(Context context, AttributeSet attrs) { + super(context, attrs); + } - @Override - public int getCount() { - return 1; - } + @Override + public int getCount() { + return 1; + } - @Override - public IItem getItemAt(int position) { - return this; - } + @Override + public IItem getItemAt(int position) { + return this; + } - @Override - public ItemHierarchy findItemById(int id) { - if (id == getId()) { - return this; - } - return null; + @Override + public ItemHierarchy findItemById(int id) { + if (id == getId()) { + return this; } + return null; + } - /** - * Convenience method to notify the adapter that the contents of this item has changed. This - * only includes non-structural changes. Changes that causes the item to be removed should use - * the other notification methods. - * - * @see #notifyItemRangeChanged(int, int) - * @see #notifyItemRangeInserted(int, int) - * @see #notifyItemRangeRemoved(int, int) - */ - public void notifyItemChanged() { - notifyItemRangeChanged(0, 1); - } + /** + * Convenience method to notify the adapter that the contents of this item has changed. This only + * includes non-structural changes. Changes that causes the item to be removed should use the + * other notification methods. + * + * @see #notifyItemRangeChanged(int, int) + * @see #notifyItemRangeInserted(int, int) + * @see #notifyItemRangeRemoved(int, int) + */ + public void notifyItemChanged() { + notifyItemRangeChanged(0, 1); + } } diff --git a/library/main/src/com/android/setupwizardlib/items/AbstractItemHierarchy.java b/library/main/src/com/android/setupwizardlib/items/AbstractItemHierarchy.java index 805e7af..e33cc2f 100644 --- a/library/main/src/com/android/setupwizardlib/items/AbstractItemHierarchy.java +++ b/library/main/src/com/android/setupwizardlib/items/AbstractItemHierarchy.java @@ -20,138 +20,123 @@ import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; - import com.android.setupwizardlib.R; - import java.util.ArrayList; -/** - * An abstract item hierarchy; provides default implementation for ID and observers. - */ +/** An abstract item hierarchy; provides default implementation for ID and observers. */ public abstract class AbstractItemHierarchy implements ItemHierarchy { - /* static section */ + /* static section */ - private static final String TAG = "AbstractItemHierarchy"; + private static final String TAG = "AbstractItemHierarchy"; - /* non-static section */ + /* non-static section */ - private ArrayList mObservers = new ArrayList<>(); - private int mId = 0; + private final ArrayList observers = new ArrayList<>(); + private int id = 0; - public AbstractItemHierarchy() { - } + public AbstractItemHierarchy() {} - public AbstractItemHierarchy(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwAbstractItem); - mId = a.getResourceId(R.styleable.SuwAbstractItem_android_id, 0); - a.recycle(); - } + public AbstractItemHierarchy(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwAbstractItem); + id = a.getResourceId(R.styleable.SuwAbstractItem_android_id, 0); + a.recycle(); + } - public void setId(int id) { - mId = id; - } + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public int getViewId() { + return getId(); + } - public int getId() { - return mId; + @Override + public void registerObserver(Observer observer) { + observers.add(observer); + } + + @Override + public void unregisterObserver(Observer observer) { + observers.remove(observer); + } + + /** @see Observer#onChanged(ItemHierarchy) */ + public void notifyChanged() { + for (Observer observer : observers) { + observer.onChanged(this); } + } - public int getViewId() { - return getId(); + /** @see Observer#onItemRangeChanged(ItemHierarchy, int, int) */ + public void notifyItemRangeChanged(int position, int itemCount) { + if (position < 0) { + Log.w(TAG, "notifyItemRangeChanged: Invalid position=" + position); + return; + } + if (itemCount < 0) { + Log.w(TAG, "notifyItemRangeChanged: Invalid itemCount=" + itemCount); + return; } - @Override - public void registerObserver(Observer observer) { - mObservers.add(observer); + for (Observer observer : observers) { + observer.onItemRangeChanged(this, position, itemCount); } + } - @Override - public void unregisterObserver(Observer observer) { - mObservers.remove(observer); + /** @see Observer#onItemRangeInserted(ItemHierarchy, int, int) */ + public void notifyItemRangeInserted(int position, int itemCount) { + if (position < 0) { + Log.w(TAG, "notifyItemRangeInserted: Invalid position=" + position); + return; + } + if (itemCount < 0) { + Log.w(TAG, "notifyItemRangeInserted: Invalid itemCount=" + itemCount); + return; } - /** - * @see Observer#onChanged(ItemHierarchy) - */ - public void notifyChanged() { - for (Observer observer : mObservers) { - observer.onChanged(this); - } + for (Observer observer : observers) { + observer.onItemRangeInserted(this, position, itemCount); } + } - /** - * @see Observer#onItemRangeChanged(ItemHierarchy, int, int) - */ - public void notifyItemRangeChanged(int position, int itemCount) { - if (position < 0) { - Log.w(TAG, "notifyItemRangeChanged: Invalid position=" + position); - return; - } - if (itemCount < 0) { - Log.w(TAG, "notifyItemRangeChanged: Invalid itemCount=" + itemCount); - return; - } - - for (Observer observer : mObservers) { - observer.onItemRangeChanged(this, position, itemCount); - } + /** @see Observer#onItemRangeMoved(ItemHierarchy, int, int, int) */ + public void notifyItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + if (fromPosition < 0) { + Log.w(TAG, "notifyItemRangeMoved: Invalid fromPosition=" + fromPosition); + return; + } + if (toPosition < 0) { + Log.w(TAG, "notifyItemRangeMoved: Invalid toPosition=" + toPosition); + return; + } + if (itemCount < 0) { + Log.w(TAG, "notifyItemRangeMoved: Invalid itemCount=" + itemCount); + return; } - /** - * @see Observer#onItemRangeInserted(ItemHierarchy, int, int) - */ - public void notifyItemRangeInserted(int position, int itemCount) { - if (position < 0) { - Log.w(TAG, "notifyItemRangeInserted: Invalid position=" + position); - return; - } - if (itemCount < 0) { - Log.w(TAG, "notifyItemRangeInserted: Invalid itemCount=" + itemCount); - return; - } - - for (Observer observer : mObservers) { - observer.onItemRangeInserted(this, position, itemCount); - } + for (Observer observer : observers) { + observer.onItemRangeMoved(this, fromPosition, toPosition, itemCount); } + } - /** - * @see Observer#onItemRangeMoved(ItemHierarchy, int, int, int) - */ - public void notifyItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - if (fromPosition < 0) { - Log.w(TAG, "notifyItemRangeMoved: Invalid fromPosition=" + fromPosition); - return; - } - if (toPosition < 0) { - Log.w(TAG, "notifyItemRangeMoved: Invalid toPosition=" + toPosition); - return; - } - if (itemCount < 0) { - Log.w(TAG, "notifyItemRangeMoved: Invalid itemCount=" + itemCount); - return; - } - - for (Observer observer : mObservers) { - observer.onItemRangeMoved(this, fromPosition, toPosition, itemCount); - } + /** @see Observer#onItemRangeRemoved(ItemHierarchy, int, int) */ + public void notifyItemRangeRemoved(int position, int itemCount) { + if (position < 0) { + Log.w(TAG, "notifyItemRangeInserted: Invalid position=" + position); + return; + } + if (itemCount < 0) { + Log.w(TAG, "notifyItemRangeInserted: Invalid itemCount=" + itemCount); + return; } - /** - * @see Observer#onItemRangeRemoved(ItemHierarchy, int, int) - */ - public void notifyItemRangeRemoved(int position, int itemCount) { - if (position < 0) { - Log.w(TAG, "notifyItemRangeInserted: Invalid position=" + position); - return; - } - if (itemCount < 0) { - Log.w(TAG, "notifyItemRangeInserted: Invalid itemCount=" + itemCount); - return; - } - - for (Observer observer : mObservers) { - observer.onItemRangeRemoved(this, position, itemCount); - } + for (Observer observer : observers) { + observer.onItemRangeRemoved(this, position, itemCount); } + } } diff --git a/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java b/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java index 06ce4ac..80b9453 100644 --- a/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java +++ b/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java @@ -21,16 +21,15 @@ import android.util.AttributeSet; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; - import com.android.setupwizardlib.R; - import java.util.ArrayList; /** - * A list item with one or more buttons, declared as - * {@link com.android.setupwizardlib.items.ButtonItem}. + * A list item with one or more buttons, declared as {@link + * com.android.setupwizardlib.items.ButtonItem}. * *

Example usage: + * *

{@code
  * <ButtonBarItem>
  *
@@ -48,81 +47,81 @@ import java.util.ArrayList;
  */
 public class ButtonBarItem extends AbstractItem implements ItemInflater.ItemParent {
 
-    private final ArrayList mButtons = new ArrayList<>();
-    private boolean mVisible = true;
-
-    public ButtonBarItem() {
-        super();
-    }
-
-    public ButtonBarItem(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public int getCount() {
-        return isVisible() ? 1 : 0;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        // The children buttons are enabled and clickable, but the item itself is not
-        return false;
+  private final ArrayList buttons = new ArrayList<>();
+  private boolean visible = true;
+
+  public ButtonBarItem() {
+    super();
+  }
+
+  public ButtonBarItem(Context context, AttributeSet attrs) {
+    super(context, attrs);
+  }
+
+  @Override
+  public int getCount() {
+    return isVisible() ? 1 : 0;
+  }
+
+  @Override
+  public boolean isEnabled() {
+    // The children buttons are enabled and clickable, but the item itself is not
+    return false;
+  }
+
+  @Override
+  public int getLayoutResource() {
+    return R.layout.suw_items_button_bar;
+  }
+
+  public void setVisible(boolean visible) {
+    this.visible = visible;
+  }
+
+  public boolean isVisible() {
+    return visible;
+  }
+
+  @Override
+  public int getViewId() {
+    return getId();
+  }
+
+  @Override
+  public void onBindView(View view) {
+    // Note: The efficiency could be improved by trying to recycle the buttons created by
+    // ButtonItem
+    final LinearLayout layout = (LinearLayout) view;
+    layout.removeAllViews();
+
+    for (ButtonItem buttonItem : buttons) {
+      Button button = buttonItem.createButton(layout);
+      layout.addView(button);
     }
 
-    @Override
-    public int getLayoutResource() {
-        return R.layout.suw_items_button_bar;
-    }
-
-    public void setVisible(boolean visible) {
-        mVisible = visible;
-    }
-
-    public boolean isVisible() {
-        return mVisible;
-    }
+    view.setId(getViewId());
+  }
 
-    @Override
-    public int getViewId() {
-        return getId();
+  @Override
+  public void addChild(ItemHierarchy child) {
+    if (child instanceof ButtonItem) {
+      buttons.add((ButtonItem) child);
+    } else {
+      throw new UnsupportedOperationException("Cannot add non-button item to Button Bar");
     }
+  }
 
-    @Override
-    public void onBindView(View view) {
-        // Note: The efficiency could be improved by trying to recycle the buttons created by
-        // ButtonItem
-        final LinearLayout layout = (LinearLayout) view;
-        layout.removeAllViews();
-
-        for (ButtonItem buttonItem : mButtons) {
-            Button button = buttonItem.createButton(layout);
-            layout.addView(button);
-        }
-
-        view.setId(getViewId());
+  @Override
+  public ItemHierarchy findItemById(int id) {
+    if (getId() == id) {
+      return this;
     }
-
-    @Override
-    public void addChild(ItemHierarchy child) {
-        if (child instanceof ButtonItem) {
-            mButtons.add((ButtonItem) child);
-        } else {
-            throw new UnsupportedOperationException("Cannot add non-button item to Button Bar");
-        }
-    }
-
-    @Override
-    public ItemHierarchy findItemById(int id) {
-        if (getId() == id) {
-            return this;
-        }
-        for (ButtonItem button : mButtons) {
-            final ItemHierarchy item = button.findItemById(id);
-            if (item != null) {
-                return item;
-            }
-        }
-        return null;
+    for (ButtonItem button : buttons) {
+      final ItemHierarchy item = button.findItemById(id);
+      if (item != null) {
+        return item;
+      }
     }
+    return null;
+  }
 }
diff --git a/library/main/src/com/android/setupwizardlib/items/ButtonItem.java b/library/main/src/com/android/setupwizardlib/items/ButtonItem.java
index 07802ae..b398f4d 100644
--- a/library/main/src/com/android/setupwizardlib/items/ButtonItem.java
+++ b/library/main/src/com/android/setupwizardlib/items/ButtonItem.java
@@ -25,7 +25,6 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
-
 import com.android.setupwizardlib.R;
 
 /**
@@ -34,128 +33,123 @@ import com.android.setupwizardlib.R;
  */
 public class ButtonItem extends AbstractItem implements View.OnClickListener {
 
-    public interface OnClickListener {
-        void onClick(ButtonItem item);
-    }
-
-    private boolean mEnabled = true;
-    private CharSequence mText;
-    private int mTheme = R.style.SuwButtonItem;
-    private OnClickListener mListener;
-
-    private Button mButton;
-
-    public ButtonItem() {
-        super();
-    }
-
-    public ButtonItem(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwButtonItem);
-        mEnabled = a.getBoolean(R.styleable.SuwButtonItem_android_enabled, true);
-        mText = a.getText(R.styleable.SuwButtonItem_android_text);
-        mTheme = a.getResourceId(R.styleable.SuwButtonItem_android_theme, R.style.SuwButtonItem);
-        a.recycle();
-    }
-
-    public void setOnClickListener(OnClickListener listener) {
-        mListener = listener;
-    }
-
-    public void setText(CharSequence text) {
-        mText = text;
-    }
-
-    public CharSequence getText() {
-        return mText;
-    }
-
-    /**
-     * The theme to use for this button. This can be used to create button of a particular style
-     * (e.g. a colored or borderless button). Typically {@code android:buttonStyle} will be set in
-     * the theme to change the style applied by the button.
-     *
-     * @param theme Resource ID of the theme
-     */
-    public void setTheme(int theme) {
-        mTheme = theme;
-        mButton = null;
-    }
-
-    /**
-     * @return Resource ID of the theme used by this button.
-     */
-    public int getTheme() {
-        return mTheme;
-    }
-
-    public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
-    }
-
-    @Override
-    public int getCount() {
-        return 0;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return mEnabled;
-    }
-
-    @Override
-    public int getLayoutResource() {
-        return 0;
-    }
-
-    /**
-     * Do not use this since ButtonItem is not directly part of a list.
-     */
-    @Override
-    public final void onBindView(View view) {
-        throw new UnsupportedOperationException("Cannot bind to ButtonItem's view");
-    }
-
-    /**
-     * Create a button according to this button item.
-     *
-     * @param parent The parent of the button, used to retrieve the theme and context for this
-     *               button.
-     * @return A button that can be added to the parent.
-     */
-    protected Button createButton(ViewGroup parent) {
-        if (mButton == null) {
-            Context context = parent.getContext();
-            if (mTheme != 0) {
-                context = new ContextThemeWrapper(context, mTheme);
-            }
-            mButton = createButton(context);
-            mButton.setOnClickListener(this);
-        } else {
-            if (mButton.getParent() instanceof ViewGroup) {
-                // A view cannot be added to a different parent if one already exists. Remove this
-                // button from its parent before returning.
-                ((ViewGroup) mButton.getParent()).removeView(mButton);
-            }
-        }
-        mButton.setEnabled(mEnabled);
-        mButton.setText(mText);
-        mButton.setId(getViewId());
-        return mButton;
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (mListener != null) {
-            mListener.onClick(this);
-        }
-    }
-
-    @SuppressLint("InflateParams")  // This is used similar to Button(Context), so it's OK to not
-                                    // specify the parent.
-    private Button createButton(Context context) {
-        // Inflate a single button from XML, so that when using support lib, it will take advantage
-        // of the injected layout inflater and give us AppCompatButton instead.
-        return (Button) LayoutInflater.from(context).inflate(R.layout.suw_button, null, false);
-    }
+  public interface OnClickListener {
+    void onClick(ButtonItem item);
+  }
+
+  private boolean enabled = true;
+  private CharSequence text;
+  private int theme = R.style.SuwButtonItem;
+  private OnClickListener listener;
+
+  private Button button;
+
+  public ButtonItem() {
+    super();
+  }
+
+  public ButtonItem(Context context, AttributeSet attrs) {
+    super(context, attrs);
+    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwButtonItem);
+    enabled = a.getBoolean(R.styleable.SuwButtonItem_android_enabled, true);
+    text = a.getText(R.styleable.SuwButtonItem_android_text);
+    theme = a.getResourceId(R.styleable.SuwButtonItem_android_theme, R.style.SuwButtonItem);
+    a.recycle();
+  }
+
+  public void setOnClickListener(OnClickListener listener) {
+    this.listener = listener;
+  }
+
+  public void setText(CharSequence text) {
+    this.text = text;
+  }
+
+  public CharSequence getText() {
+    return text;
+  }
+
+  /**
+   * The theme to use for this button. This can be used to create button of a particular style (e.g.
+   * a colored or borderless button). Typically {@code android:buttonStyle} will be set in the theme
+   * to change the style applied by the button.
+   *
+   * @param theme Resource ID of the theme
+   */
+  public void setTheme(int theme) {
+    this.theme = theme;
+    button = null;
+  }
+
+  /** @return Resource ID of the theme used by this button. */
+  public int getTheme() {
+    return theme;
+  }
+
+  public void setEnabled(boolean enabled) {
+    this.enabled = enabled;
+  }
+
+  @Override
+  public int getCount() {
+    return 0;
+  }
+
+  @Override
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  @Override
+  public int getLayoutResource() {
+    return 0;
+  }
+
+  /** Do not use this since ButtonItem is not directly part of a list. */
+  @Override
+  public final void onBindView(View view) {
+    throw new UnsupportedOperationException("Cannot bind to ButtonItem's view");
+  }
+
+  /**
+   * Create a button according to this button item.
+   *
+   * @param parent The parent of the button, used to retrieve the theme and context for this button.
+   * @return A button that can be added to the parent.
+   */
+  protected Button createButton(ViewGroup parent) {
+    if (button == null) {
+      Context context = parent.getContext();
+      if (theme != 0) {
+        context = new ContextThemeWrapper(context, theme);
+      }
+      button = createButton(context);
+      button.setOnClickListener(this);
+    } else {
+      if (button.getParent() instanceof ViewGroup) {
+        // A view cannot be added to a different parent if one already exists. Remove this
+        // button from its parent before returning.
+        ((ViewGroup) button.getParent()).removeView(button);
+      }
+    }
+    button.setEnabled(enabled);
+    button.setText(text);
+    button.setId(getViewId());
+    return button;
+  }
+
+  @SuppressLint("InflateParams") // This is used similar to Button(Context), so it's OK to not
+  // specify the parent.
+  private Button createButton(Context context) {
+    // Inflate a single button from XML, so that when using support lib, it will take advantage
+    // of the injected layout inflater and give us AppCompatButton instead.
+    return (Button) LayoutInflater.from(context).inflate(R.layout.suw_button, null, false);
+  }
+
+  @Override
+  public void onClick(View v) {
+    if (listener != null) {
+      listener.onClick(this);
+    }
+  }
 }
diff --git a/library/main/src/com/android/setupwizardlib/items/IItem.java b/library/main/src/com/android/setupwizardlib/items/IItem.java
index 26391dc..cd29ec2 100644
--- a/library/main/src/com/android/setupwizardlib/items/IItem.java
+++ b/library/main/src/com/android/setupwizardlib/items/IItem.java
@@ -18,31 +18,27 @@ package com.android.setupwizardlib.items;
 
 import android.view.View;
 
-/**
- * Representation of an item in an {@link ItemHierarchy}.
- */
+/** Representation of an item in an {@link ItemHierarchy}. */
 public interface IItem {
 
-    /**
-     * Get the Android resource ID for locating the layout for this item.
-     *
-     * @return Resource ID for the layout of this item. This layout will be used to inflate the View
-     *         passed to {@link #onBindView(android.view.View)}.
-     */
-    int getLayoutResource();
+  /**
+   * Get the Android resource ID for locating the layout for this item.
+   *
+   * @return Resource ID for the layout of this item. This layout will be used to inflate the View
+   *     passed to {@link #onBindView(android.view.View)}.
+   */
+  int getLayoutResource();
 
-    /**
-     * Called by items framework to display the data specified by this item. This method should
-     * update {@code view} to reflect its data.
-     *
-     * @param view A view inflated from {@link #getLayoutResource()}, which should be updated to
-     *             display data from this item. This view may be recycled from other items with the
-     *             same layout resource.
-     */
-    void onBindView(View view);
+  /**
+   * Called by items framework to display the data specified by this item. This method should update
+   * {@code view} to reflect its data.
+   *
+   * @param view A view inflated from {@link #getLayoutResource()}, which should be updated to
+   *     display data from this item. This view may be recycled from other items with the same
+   *     layout resource.
+   */
+  void onBindView(View view);
 
-    /**
-     * @return True if this item is enabled.
-     */
-    boolean isEnabled();
+  /** @return True if this item is enabled. */
+  boolean isEnabled();
 }
diff --git a/library/main/src/com/android/setupwizardlib/items/Item.java b/library/main/src/com/android/setupwizardlib/items/Item.java
index fc8823e..c0d49d3 100644
--- a/library/main/src/com/android/setupwizardlib/items/Item.java
+++ b/library/main/src/com/android/setupwizardlib/items/Item.java
@@ -23,7 +23,6 @@ import android.util.AttributeSet;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
-
 import com.android.setupwizardlib.R;
 
 /**
@@ -32,146 +31,145 @@ import com.android.setupwizardlib.R;
  */
 public class Item extends AbstractItem {
 
-    private boolean mEnabled = true;
-    private Drawable mIcon;
-    private int mLayoutRes;
-    private CharSequence mSummary;
-    private CharSequence mTitle;
-    private boolean mVisible = true;
-
-    public Item() {
-        super();
-        mLayoutRes = getDefaultLayoutResource();
-    }
-
-    public Item(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwItem);
-        mEnabled = a.getBoolean(R.styleable.SuwItem_android_enabled, true);
-        mIcon = a.getDrawable(R.styleable.SuwItem_android_icon);
-        mTitle = a.getText(R.styleable.SuwItem_android_title);
-        mSummary = a.getText(R.styleable.SuwItem_android_summary);
-        mLayoutRes = a.getResourceId(R.styleable.SuwItem_android_layout,
-                getDefaultLayoutResource());
-        mVisible = a.getBoolean(R.styleable.SuwItem_android_visible, true);
-        a.recycle();
-    }
-
-    protected int getDefaultLayoutResource() {
-        return R.layout.suw_items_default;
-    }
-
-    public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
-        notifyItemChanged();
-    }
-
-    @Override
-    public int getCount() {
-        return isVisible() ? 1 : 0;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return mEnabled;
-    }
-
-    public void setIcon(Drawable icon) {
-        mIcon = icon;
-        notifyItemChanged();
-    }
-
-    public Drawable getIcon() {
-        return mIcon;
-    }
-
-    public void setLayoutResource(int layoutResource) {
-        mLayoutRes = layoutResource;
-        notifyItemChanged();
-    }
-
-    @Override
-    public int getLayoutResource() {
-        return mLayoutRes;
-    }
-
-    public void setSummary(CharSequence summary) {
-        mSummary = summary;
-        notifyItemChanged();
-    }
-
-    public CharSequence getSummary() {
-        return mSummary;
-    }
-
-    public void setTitle(CharSequence title) {
-        mTitle = title;
-        notifyItemChanged();
-    }
-
-    public CharSequence getTitle() {
-        return mTitle;
-    }
-
-    public void setVisible(boolean visible) {
-        if (mVisible == visible) {
-            return;
-        }
-        mVisible = visible;
-        if (!visible) {
-            notifyItemRangeRemoved(0, 1);
-        } else {
-            notifyItemRangeInserted(0, 1);
-        }
-    }
-
-    public boolean isVisible() {
-        return mVisible;
-    }
-
-    @Override
-    public int getViewId() {
-        return getId();
-    }
-
-    @Override
-    public void onBindView(View view) {
-        TextView label = (TextView) view.findViewById(R.id.suw_items_title);
-        label.setText(getTitle());
-
-        TextView summaryView = (TextView) view.findViewById(R.id.suw_items_summary);
-        CharSequence summary = getSummary();
-        if (summary != null && summary.length() > 0) {
-            summaryView.setText(summary);
-            summaryView.setVisibility(View.VISIBLE);
-        } else {
-            summaryView.setVisibility(View.GONE);
-        }
-
-        final View iconContainer = view.findViewById(R.id.suw_items_icon_container);
-        final Drawable icon = getIcon();
-        if (icon != null) {
-            final ImageView iconView = (ImageView) view.findViewById(R.id.suw_items_icon);
-            // Set the image drawable to null before setting the state and level to avoid affecting
-            // any recycled drawable in the ImageView
-            iconView.setImageDrawable(null);
-            onMergeIconStateAndLevels(iconView, icon);
-            iconView.setImageDrawable(icon);
-            iconContainer.setVisibility(View.VISIBLE);
-        } else {
-            iconContainer.setVisibility(View.GONE);
-        }
-
-        view.setId(getViewId());
-    }
-
-    /**
-     * Copies state and level information from {@link #getIcon()} to the currently bound view's
-     * ImageView. Subclasses can override this method to change whats being copied from the icon
-     * to the ImageView.
-     */
-    protected void onMergeIconStateAndLevels(ImageView iconView, Drawable icon) {
-        iconView.setImageState(icon.getState(), false /* merge */);
-        iconView.setImageLevel(icon.getLevel());
-    }
+  private boolean enabled = true;
+  private Drawable icon;
+  private int layoutRes;
+  private CharSequence summary;
+  private CharSequence title;
+  private boolean visible = true;
+
+  public Item() {
+    super();
+    layoutRes = getDefaultLayoutResource();
+  }
+
+  public Item(Context context, AttributeSet attrs) {
+    super(context, attrs);
+    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwItem);
+    enabled = a.getBoolean(R.styleable.SuwItem_android_enabled, true);
+    icon = a.getDrawable(R.styleable.SuwItem_android_icon);
+    title = a.getText(R.styleable.SuwItem_android_title);
+    summary = a.getText(R.styleable.SuwItem_android_summary);
+    layoutRes = a.getResourceId(R.styleable.SuwItem_android_layout, getDefaultLayoutResource());
+    visible = a.getBoolean(R.styleable.SuwItem_android_visible, true);
+    a.recycle();
+  }
+
+  protected int getDefaultLayoutResource() {
+    return R.layout.suw_items_default;
+  }
+
+  public void setEnabled(boolean enabled) {
+    this.enabled = enabled;
+    notifyItemChanged();
+  }
+
+  @Override
+  public int getCount() {
+    return isVisible() ? 1 : 0;
+  }
+
+  @Override
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public void setIcon(Drawable icon) {
+    this.icon = icon;
+    notifyItemChanged();
+  }
+
+  public Drawable getIcon() {
+    return icon;
+  }
+
+  public void setLayoutResource(int layoutResource) {
+    layoutRes = layoutResource;
+    notifyItemChanged();
+  }
+
+  @Override
+  public int getLayoutResource() {
+    return layoutRes;
+  }
+
+  public void setSummary(CharSequence summary) {
+    this.summary = summary;
+    notifyItemChanged();
+  }
+
+  public CharSequence getSummary() {
+    return summary;
+  }
+
+  public void setTitle(CharSequence title) {
+    this.title = title;
+    notifyItemChanged();
+  }
+
+  public CharSequence getTitle() {
+    return title;
+  }
+
+  public void setVisible(boolean visible) {
+    if (this.visible == visible) {
+      return;
+    }
+    this.visible = visible;
+    if (!visible) {
+      notifyItemRangeRemoved(0, 1);
+    } else {
+      notifyItemRangeInserted(0, 1);
+    }
+  }
+
+  public boolean isVisible() {
+    return visible;
+  }
+
+  @Override
+  public int getViewId() {
+    return getId();
+  }
+
+  @Override
+  public void onBindView(View view) {
+    TextView label = (TextView) view.findViewById(R.id.suw_items_title);
+    label.setText(getTitle());
+
+    TextView summaryView = (TextView) view.findViewById(R.id.suw_items_summary);
+    CharSequence summary = getSummary();
+    if (summary != null && summary.length() > 0) {
+      summaryView.setText(summary);
+      summaryView.setVisibility(View.VISIBLE);
+    } else {
+      summaryView.setVisibility(View.GONE);
+    }
+
+    final View iconContainer = view.findViewById(R.id.suw_items_icon_container);
+    final Drawable icon = getIcon();
+    if (icon != null) {
+      final ImageView iconView = (ImageView) view.findViewById(R.id.suw_items_icon);
+      // Set the image drawable to null before setting the state and level to avoid affecting
+      // any recycled drawable in the ImageView
+      iconView.setImageDrawable(null);
+      onMergeIconStateAndLevels(iconView, icon);
+      iconView.setImageDrawable(icon);
+      iconContainer.setVisibility(View.VISIBLE);
+    } else {
+      iconContainer.setVisibility(View.GONE);
+    }
+
+    view.setId(getViewId());
+  }
+
+  /**
+   * Copies state and level information from {@link #getIcon()} to the currently bound view's
+   * ImageView. Subclasses can override this method to change whats being copied from the icon to
+   * the ImageView.
+   */
+  protected void onMergeIconStateAndLevels(ImageView iconView, Drawable icon) {
+    iconView.setImageState(icon.getState(), false /* merge */);
+    iconView.setImageLevel(icon.getLevel());
+  }
 }
diff --git a/library/main/src/com/android/setupwizardlib/items/ItemAdapter.java b/library/main/src/com/android/setupwizardlib/items/ItemAdapter.java
index 53285e7..851736c 100644
--- a/library/main/src/com/android/setupwizardlib/items/ItemAdapter.java
+++ b/library/main/src/com/android/setupwizardlib/items/ItemAdapter.java
@@ -23,130 +23,130 @@ import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 
 /**
- * An adapter typically used with ListView to display an
- * {@link com.android.setupwizardlib.items.ItemHierarchy}. The item hierarchy used to create this
- * adapter can be inflated by {@link ItemInflater} from XML.
+ * An adapter typically used with ListView to display an {@link
+ * com.android.setupwizardlib.items.ItemHierarchy}. The item hierarchy used to create this adapter
+ * can be inflated by {@link ItemInflater} from XML.
  */
 public class ItemAdapter extends BaseAdapter implements ItemHierarchy.Observer {
 
-    private final ItemHierarchy mItemHierarchy;
-    private ViewTypes mViewTypes = new ViewTypes();
-
-    public ItemAdapter(ItemHierarchy hierarchy) {
-        mItemHierarchy = hierarchy;
-        mItemHierarchy.registerObserver(this);
-        refreshViewTypes();
-    }
-
-    @Override
-    public int getCount() {
-        return mItemHierarchy.getCount();
-    }
-
-    @Override
-    public IItem getItem(int position) {
-        return mItemHierarchy.getItemAt(position);
-    }
-
-    @Override
-    public long getItemId(int position) {
-        return position;
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        IItem item = getItem(position);
-        int layoutRes = item.getLayoutResource();
-        return mViewTypes.get(layoutRes);
-    }
-
-    @Override
-    public int getViewTypeCount() {
-        return mViewTypes.size();
-    }
-
-    private void refreshViewTypes() {
-        for (int i = 0; i < getCount(); i++) {
-            IItem item = getItem(i);
-            mViewTypes.add(item.getLayoutResource());
-        }
-    }
-
-    @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        IItem item = getItem(position);
-        if (convertView == null) {
-            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-            convertView = inflater.inflate(item.getLayoutResource(), parent, false);
-        }
-        item.onBindView(convertView);
-        return convertView;
-    }
-
-    @Override
-    public void onChanged(ItemHierarchy hierarchy) {
-        refreshViewTypes();
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
-        onChanged(itemHierarchy);
-    }
-
-    @Override
-    public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
-        onChanged(itemHierarchy);
-    }
-
-    @Override
-    public void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition,
-            int itemCount) {
-        onChanged(itemHierarchy);
-    }
-
-    @Override
-    public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
-        onChanged(itemHierarchy);
-    }
-
-    @Override
-    public boolean isEnabled(int position) {
-        return getItem(position).isEnabled();
-    }
-
-    public ItemHierarchy findItemById(int id) {
-        return mItemHierarchy.findItemById(id);
-    }
-
-    public ItemHierarchy getRootItemHierarchy() {
-        return mItemHierarchy;
-    }
-
-    /**
-     * A helper class to pack a sparse set of integers (e.g. resource IDs) to a contiguous list of
-     * integers (e.g. adapter positions), providing mapping to retrieve the original ID from a given
-     * position. This is used to pack the view types of the adapter into contiguous integers from
-     * a given layout resource.
-     */
-    private static class ViewTypes {
-        private SparseIntArray mPositionMap = new SparseIntArray();
-        private int nextPosition = 0;
-
-        public int add(int id) {
-            if (mPositionMap.indexOfKey(id) < 0) {
-                mPositionMap.put(id, nextPosition);
-                nextPosition++;
-            }
-            return mPositionMap.get(id);
-        }
-
-        public int size() {
-            return mPositionMap.size();
-        }
-
-        public int get(int id) {
-            return mPositionMap.get(id);
-        }
-    }
+  private final ItemHierarchy itemHierarchy;
+  private final ViewTypes viewTypes = new ViewTypes();
+
+  public ItemAdapter(ItemHierarchy hierarchy) {
+    itemHierarchy = hierarchy;
+    itemHierarchy.registerObserver(this);
+    refreshViewTypes();
+  }
+
+  @Override
+  public int getCount() {
+    return itemHierarchy.getCount();
+  }
+
+  @Override
+  public IItem getItem(int position) {
+    return itemHierarchy.getItemAt(position);
+  }
+
+  @Override
+  public long getItemId(int position) {
+    return position;
+  }
+
+  @Override
+  public int getItemViewType(int position) {
+    IItem item = getItem(position);
+    int layoutRes = item.getLayoutResource();
+    return viewTypes.get(layoutRes);
+  }
+
+  @Override
+  public int getViewTypeCount() {
+    return viewTypes.size();
+  }
+
+  private void refreshViewTypes() {
+    for (int i = 0; i < getCount(); i++) {
+      IItem item = getItem(i);
+      viewTypes.add(item.getLayoutResource());
+    }
+  }
+
+  @Override
+  public View getView(int position, View convertView, ViewGroup parent) {
+    IItem item = getItem(position);
+    if (convertView == null) {
+      LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+      convertView = inflater.inflate(item.getLayoutResource(), parent, false);
+    }
+    item.onBindView(convertView);
+    return convertView;
+  }
+
+  @Override
+  public void onChanged(ItemHierarchy hierarchy) {
+    refreshViewTypes();
+    notifyDataSetChanged();
+  }
+
+  @Override
+  public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
+    onChanged(itemHierarchy);
+  }
+
+  @Override
+  public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
+    onChanged(itemHierarchy);
+  }
+
+  @Override
+  public void onItemRangeMoved(
+      ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount) {
+    onChanged(itemHierarchy);
+  }
+
+  @Override
+  public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
+    onChanged(itemHierarchy);
+  }
+
+  @Override
+  public boolean isEnabled(int position) {
+    return getItem(position).isEnabled();
+  }
+
+  public ItemHierarchy findItemById(int id) {
+    return itemHierarchy.findItemById(id);
+  }
+
+  public ItemHierarchy getRootItemHierarchy() {
+    return itemHierarchy;
+  }
+
+  /**
+   * A helper class to pack a sparse set of integers (e.g. resource IDs) to a contiguous list of
+   * integers (e.g. adapter positions), providing mapping to retrieve the original ID from a given
+   * position. This is used to pack the view types of the adapter into contiguous integers from a
+   * given layout resource.
+   */
+  private static class ViewTypes {
+    private final SparseIntArray positionMap = new SparseIntArray();
+    private int nextPosition = 0;
+
+    public int add(int id) {
+      if (positionMap.indexOfKey(id) < 0) {
+        positionMap.put(id, nextPosition);
+        nextPosition++;
+      }
+      return positionMap.get(id);
+    }
+
+    public int size() {
+      return positionMap.size();
+    }
+
+    public int get(int id) {
+      return positionMap.get(id);
+    }
+  }
 }
diff --git a/library/main/src/com/android/setupwizardlib/items/ItemGroup.java b/library/main/src/com/android/setupwizardlib/items/ItemGroup.java
index 97b3199..246469f 100644
--- a/library/main/src/com/android/setupwizardlib/items/ItemGroup.java
+++ b/library/main/src/com/android/setupwizardlib/items/ItemGroup.java
@@ -20,301 +20,288 @@ import android.content.Context;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseIntArray;
-
 import java.util.ArrayList;
 import java.util.List;
 
-public class ItemGroup extends AbstractItemHierarchy implements ItemInflater.ItemParent,
-        ItemHierarchy.Observer {
-
-    /* static section */
-
-    private static final String TAG = "ItemGroup";
-
-    /**
-     * Binary search for the closest value that's smaller than or equal to {@code value}, and
-     * return the corresponding key.
-     */
-    private static int binarySearch(SparseIntArray array, int value) {
-        final int size = array.size();
-        int lo = 0;
-        int hi = size - 1;
-
-        while (lo <= hi) {
-            final int mid = (lo + hi) >>> 1;
-            final int midVal = array.valueAt(mid);
-
-            if (midVal < value) {
-                lo = mid + 1;
-            } else if (midVal > value) {
-                hi = mid - 1;
-            } else {
-                return array.keyAt(mid);  // value found
-            }
-        }
-        // Value not found. Return the last item before our search range, which is the closest
-        // value smaller than the value we are looking for.
-        return array.keyAt(lo - 1);
+public class ItemGroup extends AbstractItemHierarchy
+    implements ItemInflater.ItemParent, ItemHierarchy.Observer {
+
+  /* static section */
+
+  private static final String TAG = "ItemGroup";
+
+  /**
+   * Binary search for the closest value that's smaller than or equal to {@code value}, and return
+   * the corresponding key.
+   */
+  private static int binarySearch(SparseIntArray array, int value) {
+    final int size = array.size();
+    int lo = 0;
+    int hi = size - 1;
+
+    while (lo <= hi) {
+      final int mid = (lo + hi) >>> 1;
+      final int midVal = array.valueAt(mid);
+
+      if (midVal < value) {
+        lo = mid + 1;
+      } else if (midVal > value) {
+        hi = mid - 1;
+      } else {
+        return array.keyAt(mid); // value found
+      }
     }
-
-    /**
-     * Same as {@link List#indexOf(Object)}, but using identity comparison rather than
-     * {@link Object#equals(Object)}.
-     */
-    private static  int identityIndexOf(List list, T object) {
-        final int count = list.size();
-        for (int i = 0; i < count; i++) {
-            if (list.get(i) == object) {
-                return i;
-            }
-        }
-        return -1;
+    // Value not found. Return the last item before our search range, which is the closest
+    // value smaller than the value we are looking for.
+    return array.keyAt(lo - 1);
+  }
+
+  /**
+   * Same as {@link List#indexOf(Object)}, but using identity comparison rather than {@link
+   * Object#equals(Object)}.
+   */
+  private static  int identityIndexOf(List list, T object) {
+    final int count = list.size();
+    for (int i = 0; i < count; i++) {
+      if (list.get(i) == object) {
+        return i;
+      }
     }
-
-    /* non-static section */
-
-    private List mChildren = new ArrayList<>();
-
-    /**
-     * A mapping from the index of an item hierarchy in mChildren, to the first position in which
-     * the corresponding child hierarchy represents. For example:
-     *
-     *   ItemHierarchy                 Item               Item Position
-     *       Index
-     *
-     *         0            [         Wi-Fi AP 1       ]        0
-     *                      |         Wi-Fi AP 2       |        1
-     *                      |         Wi-Fi AP 3       |        2
-     *                      |         Wi-Fi AP 4       |        3
-     *                      [         Wi-Fi AP 5       ]        4
-     *
-     *         1            [    ]
-     *
-     *         2            [     Use cellular data    ]        5
-     *
-     *         3            [       Don't connect      ]        6
-     *
-     * For this example of Wi-Fi screen, the following mapping will be produced:
-     *     [ 0 -> 0 | 2 -> 5 | 3 -> 6 ]
-     *
-     * Also note how ItemHierarchy index 1 is not present in the map, because it is empty.
-     *
-     * ItemGroup uses this map to look for which ItemHierarchy an item at a given position belongs
-     * to.
-     */
-    private SparseIntArray mHierarchyStart = new SparseIntArray();
-
-    private int mCount = 0;
-    private boolean mDirty = false;
-
-    public ItemGroup() {
-        super();
+    return -1;
+  }
+
+  /* non-static section */
+
+  private final List children = new ArrayList<>();
+
+  /**
+   * A mapping from the index of an item hierarchy in children, to the first position in which the
+   * corresponding child hierarchy represents. For example:
+   *
+   * 

ItemHierarchy Item Item Position Index + * + *

0 [ Wi-Fi AP 1 ] 0 | Wi-Fi AP 2 | 1 | Wi-Fi AP 3 | 2 | Wi-Fi AP 4 | 3 [ Wi-Fi AP 5 ] 4 + * + *

1 [ ] + * + *

2 [ Use cellular data ] 5 + * + *

3 [ Don't connect ] 6 + * + *

For this example of Wi-Fi screen, the following mapping will be produced: [ 0 -> 0 | 2 -> 5 + * | 3 -> 6 ] + * + *

Also note how ItemHierarchy index 1 is not present in the map, because it is empty. + * + *

ItemGroup uses this map to look for which ItemHierarchy an item at a given position belongs + * to. + */ + private final SparseIntArray hierarchyStart = new SparseIntArray(); + + private int count = 0; + private boolean dirty = false; + + public ItemGroup() { + super(); + } + + public ItemGroup(Context context, AttributeSet attrs) { + // Constructor for XML inflation + super(context, attrs); + } + + /** Add a child hierarchy to this item group. */ + @Override + public void addChild(ItemHierarchy child) { + dirty = true; + children.add(child); + child.registerObserver(this); + + final int count = child.getCount(); + if (count > 0) { + notifyItemRangeInserted(getChildPosition(child), count); } - - public ItemGroup(Context context, AttributeSet attrs) { - // Constructor for XML inflation - super(context, attrs); - } - - /** - * Add a child hierarchy to this item group. - */ - @Override - public void addChild(ItemHierarchy child) { - mDirty = true; - mChildren.add(child); - child.registerObserver(this); - - final int count = child.getCount(); - if (count > 0) { - notifyItemRangeInserted(getChildPosition(child), count); - } + } + + /** + * Remove a previously added child from this item group. + * + * @return True if there is a match for the child and it is removed. False if the child could not + * be found in our list of child hierarchies. + */ + public boolean removeChild(ItemHierarchy child) { + final int childIndex = identityIndexOf(children, child); + final int childPosition = getChildPosition(childIndex); + dirty = true; + if (childIndex != -1) { + final int childCount = child.getCount(); + children.remove(childIndex); + child.unregisterObserver(this); + if (childCount > 0) { + notifyItemRangeRemoved(childPosition, childCount); + } + return true; } + return false; + } - /** - * Remove a previously added child from this item group. - * - * @return True if there is a match for the child and it is removed. False if the child could - * not be found in our list of child hierarchies. - */ - public boolean removeChild(ItemHierarchy child) { - final int childIndex = identityIndexOf(mChildren, child); - final int childPosition = getChildPosition(childIndex); - mDirty = true; - if (childIndex != -1) { - final int childCount = child.getCount(); - mChildren.remove(childIndex); - child.unregisterObserver(this); - if (childCount > 0) { - notifyItemRangeRemoved(childPosition, childCount); - } - return true; - } - return false; + /** Remove all children from this hierarchy. */ + public void clear() { + if (children.isEmpty()) { + return; } - /** - * Remove all children from this hierarchy. - */ - public void clear() { - if (mChildren.size() == 0) { - return; - } - - final int numRemoved = getCount(); + final int numRemoved = getCount(); - for (ItemHierarchy item : mChildren) { - item.unregisterObserver(this); - } - mDirty = true; - mChildren.clear(); - notifyItemRangeRemoved(0, numRemoved); + for (ItemHierarchy item : children) { + item.unregisterObserver(this); } - - @Override - public int getCount() { - updateDataIfNeeded(); - return mCount; + dirty = true; + children.clear(); + notifyItemRangeRemoved(0, numRemoved); + } + + @Override + public int getCount() { + updateDataIfNeeded(); + return count; + } + + @Override + public IItem getItemAt(int position) { + int itemIndex = getItemIndex(position); + ItemHierarchy item = children.get(itemIndex); + int subpos = position - hierarchyStart.get(itemIndex); + return item.getItemAt(subpos); + } + + @Override + public void onChanged(ItemHierarchy hierarchy) { + // Need to set dirty, because our children may have gotten more items. + dirty = true; + notifyChanged(); + } + + /** + * @return The "Item Position" of the given child, or -1 if the child is not found. If the given + * child is empty, position of the next visible item is returned. + */ + private int getChildPosition(ItemHierarchy child) { + // Check the identity of the child rather than using .equals(), because here we want + // to find the index of the instance itself rather than something that equals to it. + return getChildPosition(identityIndexOf(children, child)); + } + + private int getChildPosition(int childIndex) { + updateDataIfNeeded(); + if (childIndex != -1) { + int childPos = -1; + int childCount = children.size(); + for (int i = childIndex; childPos < 0 && i < childCount; i++) { + // Find the position of the first visible child after childIndex. This is required + // when removing the last item from a nested ItemGroup. + childPos = hierarchyStart.get(i, -1); + } + if (childPos < 0) { + // If the last item in a group is being removed, there will be no visible item. + // In that case return the count instead, since that is where the item would have + // been if the child is not empty. + childPos = getCount(); + } + return childPos; } - - @Override - public IItem getItemAt(int position) { - int itemIndex = getItemIndex(position); - ItemHierarchy item = mChildren.get(itemIndex); - int subpos = position - mHierarchyStart.get(itemIndex); - return item.getItemAt(subpos); + return -1; + } + + @Override + public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + // No need to set dirty because onItemRangeChanged does not include any structural changes. + final int childPosition = getChildPosition(itemHierarchy); + if (childPosition >= 0) { + notifyItemRangeChanged(childPosition + positionStart, itemCount); + } else { + Log.e(TAG, "Unexpected child change " + itemHierarchy); } - - @Override - public void onChanged(ItemHierarchy hierarchy) { - // Need to set dirty, because our children may have gotten more items. - mDirty = true; - notifyChanged(); + } + + @Override + public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + dirty = true; + final int childPosition = getChildPosition(itemHierarchy); + if (childPosition >= 0) { + notifyItemRangeInserted(childPosition + positionStart, itemCount); + } else { + Log.e(TAG, "Unexpected child insert " + itemHierarchy); } - - /** - * @return The "Item Position" of the given child, or -1 if the child is not found. If the given - * child is empty, position of the next visible item is returned. - */ - private int getChildPosition(ItemHierarchy child) { - // Check the identity of the child rather than using .equals(), because here we want - // to find the index of the instance itself rather than something that equals to it. - return getChildPosition(identityIndexOf(mChildren, child)); + } + + @Override + public void onItemRangeMoved( + ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount) { + dirty = true; + final int childPosition = getChildPosition(itemHierarchy); + if (childPosition >= 0) { + notifyItemRangeMoved(childPosition + fromPosition, childPosition + toPosition, itemCount); + } else { + Log.e(TAG, "Unexpected child move " + itemHierarchy); } - - private int getChildPosition(int childIndex) { - updateDataIfNeeded(); - if (childIndex != -1) { - int childPos = -1; - int childCount = mChildren.size(); - for (int i = childIndex; childPos < 0 && i < childCount; i++) { - // Find the position of the first visible child after childIndex. This is required - // when removing the last item from a nested ItemGroup. - childPos = mHierarchyStart.get(i, -1); - } - if (childPos < 0) { - // If the last item in a group is being removed, there will be no visible item. - // In that case return the count instead, since that is where the item would have - // been if the child is not empty. - childPos = getCount(); - } - return childPos; - } - return -1; + } + + @Override + public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + dirty = true; + final int childPosition = getChildPosition(itemHierarchy); + if (childPosition >= 0) { + notifyItemRangeRemoved(childPosition + positionStart, itemCount); + } else { + Log.e(TAG, "Unexpected child remove " + itemHierarchy); } + } - @Override - public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - // No need to set dirty because onItemRangeChanged does not include any structural changes. - final int childPosition = getChildPosition(itemHierarchy); - if (childPosition >= 0) { - notifyItemRangeChanged(childPosition + positionStart, itemCount); - } else { - Log.e(TAG, "Unexpected child change " + itemHierarchy); - } + @Override + public ItemHierarchy findItemById(int id) { + if (id == getId()) { + return this; } - - @Override - public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - mDirty = true; - final int childPosition = getChildPosition(itemHierarchy); - if (childPosition >= 0) { - notifyItemRangeInserted(childPosition + positionStart, itemCount); - } else { - Log.e(TAG, "Unexpected child insert " + itemHierarchy); - } + for (ItemHierarchy child : children) { + ItemHierarchy childFindItem = child.findItemById(id); + if (childFindItem != null) { + return childFindItem; + } } - - @Override - public void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition, - int itemCount) { - mDirty = true; - final int childPosition = getChildPosition(itemHierarchy); - if (childPosition >= 0) { - notifyItemRangeMoved(childPosition + fromPosition, childPosition + toPosition, - itemCount); - } else { - Log.e(TAG, "Unexpected child move " + itemHierarchy); + return null; + } + + /** If dirty, this method will recalculate the number of items and hierarchyStart. */ + private void updateDataIfNeeded() { + if (dirty) { + count = 0; + hierarchyStart.clear(); + for (int itemIndex = 0; itemIndex < children.size(); itemIndex++) { + ItemHierarchy item = children.get(itemIndex); + if (item.getCount() > 0) { + hierarchyStart.put(itemIndex, count); } + count += item.getCount(); + } + dirty = false; } - - @Override - public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - mDirty = true; - final int childPosition = getChildPosition(itemHierarchy); - if (childPosition >= 0) { - notifyItemRangeRemoved(childPosition + positionStart, itemCount); - } else { - Log.e(TAG, "Unexpected child remove " + itemHierarchy); - } + } + + /** + * Use binary search to locate the item hierarchy a position is contained in. + * + * @return Index of the item hierarchy which is responsible for the item at {@code position}. + */ + private int getItemIndex(int position) { + updateDataIfNeeded(); + if (position < 0 || position >= count) { + throw new IndexOutOfBoundsException("size=" + count + "; index=" + position); } - - @Override - public ItemHierarchy findItemById(int id) { - if (id == getId()) { - return this; - } - for (ItemHierarchy child : mChildren) { - ItemHierarchy childFindItem = child.findItemById(id); - if (childFindItem != null) { - return childFindItem; - } - } - return null; - } - - /** - * If dirty, this method will recalculate the number of items and mHierarchyStart. - */ - private void updateDataIfNeeded() { - if (mDirty) { - mCount = 0; - mHierarchyStart.clear(); - for (int itemIndex = 0; itemIndex < mChildren.size(); itemIndex++) { - ItemHierarchy item = mChildren.get(itemIndex); - if (item.getCount() > 0) { - mHierarchyStart.put(itemIndex, mCount); - } - mCount += item.getCount(); - } - mDirty = false; - } - } - - /** - * Use binary search to locate the item hierarchy a position is contained in. - * - * @return Index of the item hierarchy which is responsible for the item at {@code position}. - */ - private int getItemIndex(int position) { - updateDataIfNeeded(); - if (position < 0 || position >= mCount) { - throw new IndexOutOfBoundsException("size=" + mCount + "; index=" + position); - } - int result = binarySearch(mHierarchyStart, position); - if (result < 0) { - throw new IllegalStateException("Cannot have item start index < 0"); - } - return result; + int result = binarySearch(hierarchyStart, position); + if (result < 0) { + throw new IllegalStateException("Cannot have item start index < 0"); } + return result; + } } diff --git a/library/main/src/com/android/setupwizardlib/items/ItemHierarchy.java b/library/main/src/com/android/setupwizardlib/items/ItemHierarchy.java index 627b6f0..85e0870 100644 --- a/library/main/src/com/android/setupwizardlib/items/ItemHierarchy.java +++ b/library/main/src/com/android/setupwizardlib/items/ItemHierarchy.java @@ -20,82 +20,69 @@ package com.android.setupwizardlib.items; * Representation of zero or more items in a list. Each instance of ItemHierarchy should be capable * of being wrapped in ItemAdapter and be displayed. * - * For example, {@link com.android.setupwizardlib.items.Item} is a representation of a single item, - * typically with data provided from XML. {@link com.android.setupwizardlib.items.ItemGroup} + *

For example, {@link com.android.setupwizardlib.items.Item} is a representation of a single + * item, typically with data provided from XML. {@link com.android.setupwizardlib.items.ItemGroup} * represents a list of child item hierarchies it contains, but itself does not do any display. */ public interface ItemHierarchy { + /** + * Observer for any changes in this hierarchy. If anything updated that causes this hierarchy to + * show different content, this observer should be called. + */ + interface Observer { /** - * Observer for any changes in this hierarchy. If anything updated that causes this hierarchy to - * show different content, this observer should be called. + * Called when an underlying data update that can cause this hierarchy to show different content + * has occurred. + * + *

Note: This is a catch-all notification, but recycler view will have a harder time figuring + * out the animations for the change, and might even not animate the change at all. */ - interface Observer { - /** - * Called when an underlying data update that can cause this hierarchy to show different - * content has occurred. - * - *

Note: This is a catch-all notification, but recycler view will have a harder time - * figuring out the animations for the change, and might even not animate the change at all. - */ - void onChanged(ItemHierarchy itemHierarchy); + void onChanged(ItemHierarchy itemHierarchy); - /** - * Called when an underlying data update that can cause changes that are local to the given - * items. This method indicates that there are no structural changes like inserting or - * removing items. - */ - void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount); + /** + * Called when an underlying data update that can cause changes that are local to the given + * items. This method indicates that there are no structural changes like inserting or removing + * items. + */ + void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount); - /** - * Called when items are inserted at the given position. - */ - void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount); + /** Called when items are inserted at the given position. */ + void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount); - /** - * Called when the given items are moved to a different position. - */ - void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition, - int itemCount); + /** Called when the given items are moved to a different position. */ + void onItemRangeMoved( + ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount); - /** - * Called when the given items are removed from the item hierarchy. - */ - void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount); - } + /** Called when the given items are removed from the item hierarchy. */ + void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount); + } - /** - * Register an observer to observe changes for this item hierarchy. - */ - void registerObserver(Observer observer); + /** Register an observer to observe changes for this item hierarchy. */ + void registerObserver(Observer observer); - /** - * Unregister a previously registered observer. - */ - void unregisterObserver(Observer observer); + /** Unregister a previously registered observer. */ + void unregisterObserver(Observer observer); - /** - * @return the number of items this item hierarchy represent. - */ - int getCount(); + /** @return the number of items this item hierarchy represent. */ + int getCount(); - /** - * Get the item at position. - * - * @param position An integer from 0 to {@link #getCount()}}, which indicates the position in - * this item hierarchy to get the child item. - * @return A representation of the item at {@code position}. Must not be {@code null}. - */ - IItem getItemAt(int position); + /** + * Get the item at position. + * + * @param position An integer from 0 to {@link #getCount()}}, which indicates the position in this + * item hierarchy to get the child item. + * @return A representation of the item at {@code position}. Must not be {@code null}. + */ + IItem getItemAt(int position); - /** - * Find an item hierarchy within this hierarchy which has the given ID. Or null if no match is - * found. This hierarchy will be returned if our ID matches. Same restrictions for Android - * resource IDs apply to this ID. In fact, typically this ID is a resource ID generated from - * XML. - * - * @param id An ID to search for in this item hierarchy. - * @return An ItemHierarchy which matches the given ID. - */ - ItemHierarchy findItemById(int id); + /** + * Find an item hierarchy within this hierarchy which has the given ID. Or null if no match is + * found. This hierarchy will be returned if our ID matches. Same restrictions for Android + * resource IDs apply to this ID. In fact, typically this ID is a resource ID generated from XML. + * + * @param id An ID to search for in this item hierarchy. + * @return An ItemHierarchy which matches the given ID. + */ + ItemHierarchy findItemById(int id); } diff --git a/library/main/src/com/android/setupwizardlib/items/ItemInflater.java b/library/main/src/com/android/setupwizardlib/items/ItemInflater.java index 618d785..26cdbbd 100644 --- a/library/main/src/com/android/setupwizardlib/items/ItemInflater.java +++ b/library/main/src/com/android/setupwizardlib/items/ItemInflater.java @@ -18,26 +18,24 @@ package com.android.setupwizardlib.items; import android.content.Context; -/** - * Inflate {@link Item} hierarchies from XML files. - */ +/** Inflate {@link Item} hierarchies from XML files. */ public class ItemInflater extends ReflectionInflater { - public interface ItemParent { - void addChild(ItemHierarchy child); - } + public interface ItemParent { + void addChild(ItemHierarchy child); + } - public ItemInflater(Context context) { - super(context); - setDefaultPackage(Item.class.getPackage().getName() + "."); - } + public ItemInflater(Context context) { + super(context); + setDefaultPackage(Item.class.getPackage().getName() + "."); + } - @Override - protected void onAddChildItem(ItemHierarchy parent, ItemHierarchy child) { - if (parent instanceof ItemParent) { - ((ItemParent) parent).addChild(child); - } else { - throw new IllegalArgumentException("Cannot add child item to " + parent); - } + @Override + protected void onAddChildItem(ItemHierarchy parent, ItemHierarchy child) { + if (parent instanceof ItemParent) { + ((ItemParent) parent).addChild(child); + } else { + throw new IllegalArgumentException("Cannot add child item to " + parent); } + } } diff --git a/library/main/src/com/android/setupwizardlib/items/ReflectionInflater.java b/library/main/src/com/android/setupwizardlib/items/ReflectionInflater.java index 8ffa943..3382f56 100644 --- a/library/main/src/com/android/setupwizardlib/items/ReflectionInflater.java +++ b/library/main/src/com/android/setupwizardlib/items/ReflectionInflater.java @@ -17,12 +17,10 @@ package com.android.setupwizardlib.items; import android.content.Context; -import android.util.AttributeSet; -import android.view.InflateException; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import android.util.AttributeSet; +import android.view.InflateException; import java.lang.reflect.Constructor; import java.util.HashMap; @@ -38,107 +36,104 @@ import java.util.HashMap; */ public abstract class ReflectionInflater extends SimpleInflater { - /* static section */ - - private static final Class[] CONSTRUCTOR_SIGNATURE = - new Class[] {Context.class, AttributeSet.class}; - - private static final HashMap> sConstructorMap = new HashMap<>(); - - /* non-static section */ - - // Array used to contain the constructor arguments (Context, AttributeSet), to avoid allocating - // a new array for creation of every item. - private final Object[] mTempConstructorArgs = new Object[2]; - - @Nullable - private String mDefaultPackage; - - @NonNull - private final Context mContext; - - /** - * Create a new inflater instance associated with a particular Context. - * - * @param context The context used to resolve resource IDs. This context is also passed to the - * constructor of the items created as the first argument. - */ - protected ReflectionInflater(@NonNull Context context) { - super(context.getResources()); - mContext = context; + /* static section */ + + private static final Class[] CONSTRUCTOR_SIGNATURE = + new Class[] {Context.class, AttributeSet.class}; + + private static final HashMap> constructorMap = new HashMap<>(); + + /* non-static section */ + + // Array used to contain the constructor arguments (Context, AttributeSet), to avoid allocating + // a new array for creation of every item. + private final Object[] tempConstructorArgs = new Object[2]; + + @Nullable private String defaultPackage; + + @NonNull private final Context context; + + /** + * Create a new inflater instance associated with a particular Context. + * + * @param context The context used to resolve resource IDs. This context is also passed to the + * constructor of the items created as the first argument. + */ + protected ReflectionInflater(@NonNull Context context) { + super(context.getResources()); + this.context = context; + } + + @NonNull + public Context getContext() { + return context; + } + + /** + * Instantiate the class by name. This attempts to instantiate class of the given {@code name} + * found in this inflater's ClassLoader. + * + * @param tagName The full name of the class to be instantiated. + * @param attrs The XML attributes supplied for this instance. + * @return The newly instantiated item. + */ + @NonNull + public final T createItem(String tagName, String prefix, AttributeSet attrs) { + String qualifiedName = tagName; + if (prefix != null && qualifiedName.indexOf('.') == -1) { + qualifiedName = prefix.concat(qualifiedName); } + @SuppressWarnings("unchecked") // qualifiedName should correspond to a subclass of T + Constructor constructor = + (Constructor) constructorMap.get(qualifiedName); - @NonNull - public Context getContext() { - return mContext; - } - - /** - * Instantiate the class by name. This attempts to instantiate class of the given {@code name} - * found in this inflater's ClassLoader. - * - * @param tagName The full name of the class to be instantiated. - * @param attrs The XML attributes supplied for this instance. - * - * @return The newly instantiated item. - */ - @NonNull - public final T createItem(String tagName, String prefix, AttributeSet attrs) { - String qualifiedName = tagName; - if (prefix != null && qualifiedName.indexOf('.') == -1) { - qualifiedName = prefix.concat(qualifiedName); - } + try { + if (constructor == null) { + // Class not found in the cache, see if it's real, and try to add it @SuppressWarnings("unchecked") // qualifiedName should correspond to a subclass of T - Constructor constructor = - (Constructor) sConstructorMap.get(qualifiedName); - - try { - if (constructor == null) { - // Class not found in the cache, see if it's real, and try to add it - @SuppressWarnings("unchecked") // qualifiedName should correspond to a subclass of T - Class clazz = - (Class) mContext.getClassLoader().loadClass(qualifiedName); - constructor = clazz.getConstructor(CONSTRUCTOR_SIGNATURE); - constructor.setAccessible(true); - sConstructorMap.put(tagName, constructor); - } - - mTempConstructorArgs[0] = mContext; - mTempConstructorArgs[1] = attrs; - final T item = constructor.newInstance(mTempConstructorArgs); - mTempConstructorArgs[0] = null; - mTempConstructorArgs[1] = null; - return item; - } catch (Exception e) { - throw new InflateException(attrs.getPositionDescription() - + ": Error inflating class " + qualifiedName, e); - } - } - - @Override - protected T onCreateItem(String tagName, AttributeSet attrs) { - return createItem(tagName, mDefaultPackage, attrs); - } - - /** - * Sets the default package that will be searched for classes to construct for tag names that - * have no explicit package. - * - * @param defaultPackage The default package. This will be prepended to the tag name, so it - * should end with a period. - */ - public void setDefaultPackage(@Nullable String defaultPackage) { - mDefaultPackage = defaultPackage; - } - - /** - * Returns the default package, or null if it is not set. - * - * @see #setDefaultPackage(String) - * @return The default package. - */ - @Nullable - public String getDefaultPackage() { - return mDefaultPackage; + Class clazz = + (Class) context.getClassLoader().loadClass(qualifiedName); + constructor = clazz.getConstructor(CONSTRUCTOR_SIGNATURE); + constructor.setAccessible(true); + constructorMap.put(tagName, constructor); + } + + tempConstructorArgs[0] = context; + tempConstructorArgs[1] = attrs; + final T item = constructor.newInstance(tempConstructorArgs); + tempConstructorArgs[0] = null; + tempConstructorArgs[1] = null; + return item; + } catch (Exception e) { + throw new InflateException( + attrs.getPositionDescription() + ": Error inflating class " + qualifiedName, e); } + } + + @Override + protected T onCreateItem(String tagName, AttributeSet attrs) { + return createItem(tagName, defaultPackage, attrs); + } + + /** + * Sets the default package that will be searched for classes to construct for tag names that have + * no explicit package. + * + * @param defaultPackage The default package. This will be prepended to the tag name, so it should + * end with a period. + */ + public void setDefaultPackage(@Nullable String defaultPackage) { + this.defaultPackage = defaultPackage; + } + + /** + * Returns the default package, or null if it is not set. + * + * @see #setDefaultPackage(String) + * @return The default package. + */ + @Nullable + public String getDefaultPackage() { + return defaultPackage; + } } diff --git a/library/main/src/com/android/setupwizardlib/items/SimpleInflater.java b/library/main/src/com/android/setupwizardlib/items/SimpleInflater.java index 0b12aca..767362c 100644 --- a/library/main/src/com/android/setupwizardlib/items/SimpleInflater.java +++ b/library/main/src/com/android/setupwizardlib/items/SimpleInflater.java @@ -18,18 +18,15 @@ package com.android.setupwizardlib.items; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import androidx.annotation.NonNull; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import android.view.InflateException; - -import androidx.annotation.NonNull; - +import java.io.IOException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; - /** * A simple XML inflater, which takes care of moving the parser to the correct position. Subclasses * need to implement {@link #onCreateItem(String, AttributeSet)} to create an object representation @@ -40,154 +37,154 @@ import java.io.IOException; */ public abstract class SimpleInflater { - private static final String TAG = "SimpleInflater"; - private static final boolean DEBUG = false; - - protected final Resources mResources; - - /** - * Create a new inflater instance associated with a particular Resources bundle. - * - * @param resources The Resources class used to resolve given resource IDs. - */ - protected SimpleInflater(@NonNull Resources resources) { - mResources = resources; + private static final String TAG = "SimpleInflater"; + private static final boolean DEBUG = false; + + protected final Resources mResources; + + /** + * Create a new inflater instance associated with a particular Resources bundle. + * + * @param resources The Resources class used to resolve given resource IDs. + */ + protected SimpleInflater(@NonNull Resources resources) { + mResources = resources; + } + + public Resources getResources() { + return mResources; + } + + /** + * Inflate a new hierarchy from the specified XML resource. Throws InflaterException if there is + * an error. + * + * @param resId ID for an XML resource to load (e.g. R.xml.my_xml) + * @return The root of the inflated hierarchy. + */ + public T inflate(int resId) { + XmlResourceParser parser = getResources().getXml(resId); + try { + return inflate(parser); + } finally { + parser.close(); } - - public Resources getResources() { - return mResources; - } - - /** - * Inflate a new hierarchy from the specified XML resource. Throws InflaterException if there is - * an error. - * - * @param resId ID for an XML resource to load (e.g. R.xml.my_xml) - * @return The root of the inflated hierarchy. - */ - public T inflate(int resId) { - XmlResourceParser parser = getResources().getXml(resId); - try { - return inflate(parser); - } finally { - parser.close(); - } - } - - /** - * Inflate a new hierarchy from the specified XML node. Throws InflaterException if there is an - * error. - *

- * Important   For performance - * reasons, inflation relies heavily on pre-processing of XML files - * that is done at build time. Therefore, it is not currently possible to - * use inflater with an XmlPullParser over a plain XML file at runtime. - * - * @param parser XML dom node containing the description of the hierarchy. - * @return The root of the inflated hierarchy. - */ - public T inflate(XmlPullParser parser) { - final AttributeSet attrs = Xml.asAttributeSet(parser); - T createdItem; - - try { - // Look for the root node. - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - // continue - } - - if (type != XmlPullParser.START_TAG) { - throw new InflateException(parser.getPositionDescription() - + ": No start tag found!"); - } - - createdItem = createItemFromTag(parser.getName(), attrs); - - rInflate(parser, createdItem, attrs); - } catch (XmlPullParserException e) { - throw new InflateException(e.getMessage(), e); - } catch (IOException e) { - throw new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); - } - - return createdItem; + } + + /** + * Inflate a new hierarchy from the specified XML node. Throws InflaterException if there is an + * error. + * + *

Important   For performance reasons, inflation + * relies heavily on pre-processing of XML files that is done at build time. Therefore, it is not + * currently possible to use inflater with an XmlPullParser over a plain XML file at runtime. + * + * @param parser XML dom node containing the description of the hierarchy. + * @return The root of the inflated hierarchy. + */ + public T inflate(XmlPullParser parser) { + final AttributeSet attrs = Xml.asAttributeSet(parser); + T createdItem; + + try { + // Look for the root node. + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // continue + } + + if (type != XmlPullParser.START_TAG) { + throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); + } + + createdItem = createItemFromTag(parser.getName(), attrs); + + rInflate(parser, createdItem, attrs); + } catch (XmlPullParserException e) { + throw new InflateException(e.getMessage(), e); + } catch (IOException e) { + throw new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); } - /** - * This routine is responsible for creating the correct subclass of item - * given the xml element name. - * - * @param tagName The XML tag name for the item to be created. - * @param attrs An AttributeSet of attributes to apply to the item. - * @return The item created. - */ - protected abstract T onCreateItem(String tagName, AttributeSet attrs); - - private T createItemFromTag(String name, AttributeSet attrs) { - try { - T item = onCreateItem(name, attrs); - if (DEBUG) Log.v(TAG, item + " created for <" + name + ">"); - return item; - } catch (InflateException e) { - throw e; - } catch (Exception e) { - throw new InflateException(attrs.getPositionDescription() - + ": Error inflating class " + name, e); - } + return createdItem; + } + + /** + * This routine is responsible for creating the correct subclass of item given the xml element + * name. + * + * @param tagName The XML tag name for the item to be created. + * @param attrs An AttributeSet of attributes to apply to the item. + * @return The item created. + */ + protected abstract T onCreateItem(String tagName, AttributeSet attrs); + + private T createItemFromTag(String name, AttributeSet attrs) { + try { + T item = onCreateItem(name, attrs); + if (DEBUG) { + Log.v(TAG, item + " created for <" + name + ">"); + } + return item; + } catch (InflateException e) { + throw e; + } catch (Exception e) { + throw new InflateException( + attrs.getPositionDescription() + ": Error inflating class " + name, e); } + } - /** - * Recursive method used to descend down the xml hierarchy and instantiate - * items, instantiate their children, and then call onFinishInflate(). - */ - private void rInflate(XmlPullParser parser, T parent, final AttributeSet attrs) - throws XmlPullParserException, IOException { - final int depth = parser.getDepth(); - - int type; - while (((type = parser.next()) != XmlPullParser.END_TAG - || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + /** + * Recursive method used to descend down the xml hierarchy and instantiate items, instantiate + * their children, and then call onFinishInflate(). + */ + private void rInflate(XmlPullParser parser, T parent, final AttributeSet attrs) + throws XmlPullParserException, IOException { + final int depth = parser.getDepth(); - if (type != XmlPullParser.START_TAG) { - continue; - } + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { - if (onInterceptCreateItem(parser, parent, attrs)) { - continue; - } + if (type != XmlPullParser.START_TAG) { + continue; + } - String name = parser.getName(); - T item = createItemFromTag(name, attrs); + if (onInterceptCreateItem(parser, parent, attrs)) { + continue; + } - onAddChildItem(parent, item); + String name = parser.getName(); + T item = createItemFromTag(name, attrs); - rInflate(parser, item, attrs); - } - } + onAddChildItem(parent, item); - /** - * Whether item creation should be intercepted to perform custom handling on the parser rather - * than creating an object from it. This is used in rare cases where a tag doesn't correspond - * to creation of an object. - * - * The parser will be pointing to the start of a tag, you must stop parsing and return when you - * reach the end of this element. That is, this method is responsible for parsing the element - * at the given position together with all of its child tags. - * - * Note that parsing of the root tag cannot be intercepted. - * - * @param parser XML dom node containing the description of the hierarchy. - * @param parent The item that should be the parent of whatever you create. - * @param attrs An AttributeSet of attributes to apply to the item. - * @return True to continue parsing without calling {@link #onCreateItem(String, AttributeSet)}, - * or false if this inflater should proceed to create an item. - */ - protected boolean onInterceptCreateItem(XmlPullParser parser, T parent, AttributeSet attrs) - throws XmlPullParserException { - return false; + rInflate(parser, item, attrs); } - - protected abstract void onAddChildItem(T parent, T child); + } + + /** + * Whether item creation should be intercepted to perform custom handling on the parser rather + * than creating an object from it. This is used in rare cases where a tag doesn't correspond to + * creation of an object. + * + *

The parser will be pointing to the start of a tag, you must stop parsing and return when you + * reach the end of this element. That is, this method is responsible for parsing the element at + * the given position together with all of its child tags. + * + *

Note that parsing of the root tag cannot be intercepted. + * + * @param parser XML dom node containing the description of the hierarchy. + * @param parent The item that should be the parent of whatever you create. + * @param attrs An AttributeSet of attributes to apply to the item. + * @return True to continue parsing without calling {@link #onCreateItem(String, AttributeSet)}, + * or false if this inflater should proceed to create an item. + */ + protected boolean onInterceptCreateItem(XmlPullParser parser, T parent, AttributeSet attrs) + throws XmlPullParserException { + return false; + } + + protected abstract void onAddChildItem(T parent, T child); } diff --git a/library/main/src/com/android/setupwizardlib/span/LinkSpan.java b/library/main/src/com/android/setupwizardlib/span/LinkSpan.java index 3dd783b..49ce1b9 100644 --- a/library/main/src/com/android/setupwizardlib/span/LinkSpan.java +++ b/library/main/src/com/android/setupwizardlib/span/LinkSpan.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.graphics.Typeface; import android.os.Build; +import androidx.annotation.Nullable; import android.text.Selection; import android.text.Spannable; import android.text.TextPaint; @@ -28,126 +29,120 @@ import android.util.Log; import android.view.View; import android.widget.TextView; -import androidx.annotation.Nullable; - /** * A clickable span that will listen for click events and send it back to the context. To use this - * class, implement {@link OnLinkClickListener} in your TextView, or use - * {@link com.android.setupwizardlib.view.RichTextView#setOnClickListener(View.OnClickListener)}. + * class, implement {@link OnLinkClickListener} in your TextView, or use {@link + * com.android.setupwizardlib.view.RichTextView#setOnClickListener(View.OnClickListener)}. * - *

Note on accessibility: For TalkBack to be able to traverse and interact with the links, you + *

Note on accessibility: For TalkBack to be able to traverse and interact with the links, you * should use {@code LinkAccessibilityHelper} in your {@code TextView} subclass. Optionally you can * also use {@code RichTextView}, which includes link support. */ public class LinkSpan extends ClickableSpan { - /* - * Implementation note: When the orientation changes, TextView retains a reference to this span - * instead of writing it to a parcel (ClickableSpan is not Parcelable). If this class has any - * reference to the containing Activity (i.e. the activity context, or any views in the - * activity), it will cause memory leak. - */ + /* + * Implementation note: When the orientation changes, TextView retains a reference to this span + * instead of writing it to a parcel (ClickableSpan is not Parcelable). If this class has any + * reference to the containing Activity (i.e. the activity context, or any views in the + * activity), it will cause memory leak. + */ - /* static section */ + /* static section */ - private static final String TAG = "LinkSpan"; + private static final String TAG = "LinkSpan"; - private static final Typeface TYPEFACE_MEDIUM = - Typeface.create("sans-serif-medium", Typeface.NORMAL); + private static final Typeface TYPEFACE_MEDIUM = + Typeface.create("sans-serif-medium", Typeface.NORMAL); - /** - * @deprecated Use {@link OnLinkClickListener} - */ - @Deprecated - public interface OnClickListener { - void onClick(LinkSpan span); - } + /** @deprecated Use {@link OnLinkClickListener} */ + @Deprecated + public interface OnClickListener { + void onClick(LinkSpan span); + } + + /** + * Listener that is invoked when a link span is clicked. If the containing view of this span + * implements this interface, this will be invoked when the link is clicked. + */ + public interface OnLinkClickListener { /** - * Listener that is invoked when a link span is clicked. If the containing view of this span - * implements this interface, this will be invoked when the link is clicked. + * Called when a link has been clicked. + * + * @param span The span that was clicked. + * @return True if the click was handled, stopping further propagation of the click event. */ - public interface OnLinkClickListener { - - /** - * Called when a link has been clicked. - * - * @param span The span that was clicked. - * @return True if the click was handled, stopping further propagation of the click event. - */ - boolean onLinkClick(LinkSpan span); + boolean onLinkClick(LinkSpan span); + } + + /* non-static section */ + + private final String id; + + public LinkSpan(String id) { + this.id = id; + } + + @Override + public void onClick(View view) { + if (dispatchClick(view)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // Prevent the touch event from bubbling up to the parent views. + view.cancelPendingInputEvents(); + } + } else { + Log.w(TAG, "Dropping click event. No listener attached."); } - - /* non-static section */ - - private final String mId; - - public LinkSpan(String id) { - mId = id; + if (view instanceof TextView) { + // Remove the highlight effect when the click happens by clearing the selection + CharSequence text = ((TextView) view).getText(); + if (text instanceof Spannable) { + Selection.setSelection((Spannable) text, 0); + } } + } - @Override - public void onClick(View view) { - if (dispatchClick(view)) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - // Prevent the touch event from bubbling up to the parent views. - view.cancelPendingInputEvents(); - } - } else { - Log.w(TAG, "Dropping click event. No listener attached."); - } - if (view instanceof TextView) { - // Remove the highlight effect when the click happens by clearing the selection - CharSequence text = ((TextView) view).getText(); - if (text instanceof Spannable) { - Selection.setSelection((Spannable) text, 0); - } - } + private boolean dispatchClick(View view) { + boolean handled = false; + if (view instanceof OnLinkClickListener) { + handled = ((OnLinkClickListener) view).onLinkClick(this); } - - private boolean dispatchClick(View view) { - boolean handled = false; - if (view instanceof OnLinkClickListener) { - handled = ((OnLinkClickListener) view).onLinkClick(this); - } - if (!handled) { - final OnClickListener listener = getLegacyListenerFromContext(view.getContext()); - if (listener != null) { - listener.onClick(this); - handled = true; - } - } - return handled; + if (!handled) { + final OnClickListener listener = getLegacyListenerFromContext(view.getContext()); + if (listener != null) { + listener.onClick(this); + handled = true; + } } - - /** - * @deprecated Deprecated together with {@link OnClickListener} - */ - @Nullable - @Deprecated - private OnClickListener getLegacyListenerFromContext(@Nullable Context context) { - while (true) { - if (context instanceof OnClickListener) { - return (OnClickListener) context; - } else if (context instanceof ContextWrapper) { - // Unwrap any context wrapper, in base the base context implements onClickListener. - // ContextWrappers cannot have circular base contexts, so at some point this will - // reach the one of the other cases and return. - context = ((ContextWrapper) context).getBaseContext(); - } else { - return null; - } - } - } - - @Override - public void updateDrawState(TextPaint drawState) { - super.updateDrawState(drawState); - drawState.setUnderlineText(false); - drawState.setTypeface(TYPEFACE_MEDIUM); - } - - public String getId() { - return mId; + return handled; + } + + /** @deprecated Deprecated together with {@link OnClickListener} */ + @Nullable + @Deprecated + private OnClickListener getLegacyListenerFromContext(@Nullable Context context) { + while (true) { + if (context instanceof OnClickListener) { + return (OnClickListener) context; + } else if (context instanceof ContextWrapper) { + // Unwrap any context wrapper, in base the base context implements onClickListener. + // ContextWrappers cannot have circular base contexts, so at some point this will + // reach the one of the other cases and return. + context = ((ContextWrapper) context).getBaseContext(); + } else { + return null; + } } + } + + @Override + public void updateDrawState(TextPaint drawState) { + super.updateDrawState(drawState); + drawState.setUnderlineText(false); + drawState.setTypeface(TYPEFACE_MEDIUM); + } + + public String getId() { + return id; + } } diff --git a/library/main/src/com/android/setupwizardlib/span/SpanHelper.java b/library/main/src/com/android/setupwizardlib/span/SpanHelper.java index d75e338..4037e6d 100644 --- a/library/main/src/com/android/setupwizardlib/span/SpanHelper.java +++ b/library/main/src/com/android/setupwizardlib/span/SpanHelper.java @@ -23,14 +23,14 @@ import android.text.Spannable; */ public class SpanHelper { - /** - * Add {@code newSpan} at the same start and end indices as {@code oldSpan} and remove - * {@code oldSpan} from the {@code spannable}. - */ - public static void replaceSpan(Spannable spannable, Object oldSpan, Object newSpan) { - final int spanStart = spannable.getSpanStart(oldSpan); - final int spanEnd = spannable.getSpanEnd(oldSpan); - spannable.removeSpan(oldSpan); - spannable.setSpan(newSpan, spanStart, spanEnd, 0); - } + /** + * Add {@code newSpan} at the same start and end indices as {@code oldSpan} and remove {@code + * oldSpan} from the {@code spannable}. + */ + public static void replaceSpan(Spannable spannable, Object oldSpan, Object newSpan) { + final int spanStart = spannable.getSpanStart(oldSpan); + final int spanEnd = spannable.getSpanEnd(oldSpan); + spannable.removeSpan(oldSpan); + spannable.setSpan(newSpan, spanStart, spanEnd, 0); + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ButtonFooterMixin.java b/library/main/src/com/android/setupwizardlib/template/ButtonFooterMixin.java index a8580a3..c872f6b 100644 --- a/library/main/src/com/android/setupwizardlib/template/ButtonFooterMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/ButtonFooterMixin.java @@ -18,6 +18,10 @@ package com.android.setupwizardlib.template; import android.annotation.SuppressLint; import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.StyleRes; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -25,12 +29,6 @@ import android.view.ViewStub; import android.widget.Button; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.annotation.StyleRes; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; @@ -41,134 +39,131 @@ import com.android.setupwizardlib.TemplateLayout; */ public class ButtonFooterMixin implements Mixin { - private final Context mContext; - - @Nullable - private final ViewStub mFooterStub; - - private LinearLayout mButtonContainer; - - /** - * Create a mixin for managing buttons on the footer. - * - * @param layout The {@link TemplateLayout} containing this mixin. - */ - public ButtonFooterMixin(TemplateLayout layout) { - mContext = layout.getContext(); - mFooterStub = (ViewStub) layout.findManagedViewById(R.id.suw_layout_footer); - } - - /** - * Add a button with the given text and style. Common style for GLIF are - * {@code SuwGlifButton.Primary} and {@code SuwGlifButton.Secondary}. - * - * @param text The label for the button. - * @param theme Theme resource to be used for this button. Since this is applied as a theme, - * the resource will typically apply {@code android:buttonStyle} so it will be - * applied to the button as a style as well. - * - * @return The button that was created. - */ - public Button addButton(CharSequence text, @StyleRes int theme) { - Button button = createThemedButton(mContext, theme); - button.setText(text); - return addButton(button); - } - - /** - * Add a button with the given text and style. Common style for GLIF are - * {@code SuwGlifButton.Primary} and {@code SuwGlifButton.Secondary}. - * - * @param text The label for the button. - * @param theme Theme resource to be used for this button. Since this is applied as a theme, - * the resource will typically apply {@code android:buttonStyle} so it will be - * applied to the button as a style as well. - * - * @return The button that was created. - */ - public Button addButton(@StringRes int text, @StyleRes int theme) { - Button button = createThemedButton(mContext, theme); - button.setText(text); - return addButton(button); - } - - /** - * Add a button to the footer. - * - * @param button The button to be added to the footer. - * @return The button that was added. - */ - public Button addButton(Button button) { - final LinearLayout buttonContainer = ensureFooterInflated(); - buttonContainer.addView(button); - return button; - } - - /** - * Add a space to the footer. Spaces will share the remaining space of footer, so for example, - * [Button] [space] [Button] [space] [Button] will give you 3 buttons, left, center, and right - * aligned. - * - * @return The view that was used as space. - */ - public View addSpace() { - final LinearLayout buttonContainer = ensureFooterInflated(); - View space = new View(buttonContainer.getContext()); - space.setLayoutParams(new LayoutParams(0, 0, 1.0f)); - space.setVisibility(View.INVISIBLE); - buttonContainer.addView(space); - return space; + private final Context context; + + @Nullable private final ViewStub footerStub; + + private LinearLayout buttonContainer; + + /** + * Create a mixin for managing buttons on the footer. + * + * @param layout The {@link TemplateLayout} containing this mixin. + */ + public ButtonFooterMixin(TemplateLayout layout) { + context = layout.getContext(); + footerStub = (ViewStub) layout.findManagedViewById(R.id.suw_layout_footer); + } + + /** + * Add a button with the given text and style. Common style for GLIF are {@code + * SuwGlifButton.Primary} and {@code SuwGlifButton.Secondary}. + * + * @param text The label for the button. + * @param theme Theme resource to be used for this button. Since this is applied as a theme, the + * resource will typically apply {@code android:buttonStyle} so it will be applied to the + * button as a style as well. + * @return The button that was created. + */ + public Button addButton(CharSequence text, @StyleRes int theme) { + Button button = createThemedButton(context, theme); + button.setText(text); + return addButton(button); + } + + /** + * Add a button with the given text and style. Common style for GLIF are {@code + * SuwGlifButton.Primary} and {@code SuwGlifButton.Secondary}. + * + * @param text The label for the button. + * @param theme Theme resource to be used for this button. Since this is applied as a theme, the + * resource will typically apply {@code android:buttonStyle} so it will be applied to the + * button as a style as well. + * @return The button that was created. + */ + public Button addButton(@StringRes int text, @StyleRes int theme) { + Button button = createThemedButton(context, theme); + button.setText(text); + return addButton(button); + } + + /** + * Add a button to the footer. + * + * @param button The button to be added to the footer. + * @return The button that was added. + */ + public Button addButton(Button button) { + final LinearLayout buttonContainer = ensureFooterInflated(); + buttonContainer.addView(button); + return button; + } + + /** + * Add a space to the footer. Spaces will share the remaining space of footer, so for example, + * [Button] [space] [Button] [space] [Button] will give you 3 buttons, left, center, and right + * aligned. + * + * @return The view that was used as space. + */ + public View addSpace() { + final LinearLayout buttonContainer = ensureFooterInflated(); + View space = new View(buttonContainer.getContext()); + space.setLayoutParams(new LayoutParams(0, 0, 1.0f)); + space.setVisibility(View.INVISIBLE); + buttonContainer.addView(space); + return space; + } + + /** + * Remove a previously added button. + * + * @param button The button to be removed. + */ + public void removeButton(Button button) { + if (buttonContainer != null) { + buttonContainer.removeView(button); } - - /** - * Remove a previously added button. - * - * @param button The button to be removed. - */ - public void removeButton(Button button) { - if (mButtonContainer != null) { - mButtonContainer.removeView(button); - } - } - - /** - * Remove a previously added space. - * - * @param space The space to be removed. - */ - public void removeSpace(View space) { - if (mButtonContainer != null) { - mButtonContainer.removeView(space); - } + } + + /** + * Remove a previously added space. + * + * @param space The space to be removed. + */ + public void removeSpace(View space) { + if (buttonContainer != null) { + buttonContainer.removeView(space); } - - /** - * Remove all views, including spaces, from the footer. Note that if the footer container is - * already inflated, this will not remove the container itself. - */ - public void removeAllViews() { - if (mButtonContainer != null) { - mButtonContainer.removeAllViews(); - } + } + + /** + * Remove all views, including spaces, from the footer. Note that if the footer container is + * already inflated, this will not remove the container itself. + */ + public void removeAllViews() { + if (buttonContainer != null) { + buttonContainer.removeAllViews(); } - - @NonNull - private LinearLayout ensureFooterInflated() { - if (mButtonContainer == null) { - if (mFooterStub == null) { - throw new IllegalStateException("Footer stub is not found in this template"); - } - mFooterStub.setLayoutResource(R.layout.suw_glif_footer_button_bar); - mButtonContainer = (LinearLayout) mFooterStub.inflate(); - } - return mButtonContainer; - } - - @SuppressLint("InflateParams") - private Button createThemedButton(Context context, @StyleRes int theme) { - // Inflate a single button from XML, which when using support lib, will take advantage of - // the injected layout inflater and give us AppCompatButton instead. - LayoutInflater inflater = LayoutInflater.from(new ContextThemeWrapper(context, theme)); - return (Button) inflater.inflate(R.layout.suw_button, null, false); + } + + @NonNull + private LinearLayout ensureFooterInflated() { + if (buttonContainer == null) { + if (footerStub == null) { + throw new IllegalStateException("Footer stub is not found in this template"); + } + footerStub.setLayoutResource(R.layout.suw_glif_footer_button_bar); + buttonContainer = (LinearLayout) footerStub.inflate(); } + return buttonContainer; + } + + @SuppressLint("InflateParams") + private Button createThemedButton(Context context, @StyleRes int theme) { + // Inflate a single button from XML, which when using support lib, will take advantage of + // the injected layout inflater and give us AppCompatButton instead. + LayoutInflater inflater = LayoutInflater.from(new ContextThemeWrapper(context, theme)); + return (Button) inflater.inflate(R.layout.suw_button, null, false); + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ColoredHeaderMixin.java b/library/main/src/com/android/setupwizardlib/template/ColoredHeaderMixin.java index ccc5aad..acdaa67 100644 --- a/library/main/src/com/android/setupwizardlib/template/ColoredHeaderMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/ColoredHeaderMixin.java @@ -20,54 +20,51 @@ import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.util.AttributeSet; import android.widget.TextView; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; /** * A {@link Mixin} displaying a header text that can be set to different colors. This Mixin is - * registered to the tempalte using HeaderMixin.class, and can be retrieved using: - * {@code (ColoredHeaderMixin) templateLayout.getMixin(HeaderMixin.class}. + * registered to the template using HeaderMixin.class, and can be retrieved using: {@code + * (ColoredHeaderMixin) templateLayout.getMixin(HeaderMixin.class}. */ public class ColoredHeaderMixin extends HeaderMixin { - /** - * {@inheritDoc} - */ - public ColoredHeaderMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { - super(layout, attrs, defStyleAttr); - - final TypedArray a = layout.getContext().obtainStyledAttributes( - attrs, R.styleable.SuwColoredHeaderMixin, defStyleAttr, 0); + /** {@inheritDoc} */ + public ColoredHeaderMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { + super(layout, attrs, defStyleAttr); - // Set the header color - final ColorStateList headerColor = - a.getColorStateList(R.styleable.SuwColoredHeaderMixin_suwHeaderColor); - if (headerColor != null) { - setColor(headerColor); - } + final TypedArray a = + layout + .getContext() + .obtainStyledAttributes(attrs, R.styleable.SuwColoredHeaderMixin, defStyleAttr, 0); - a.recycle(); + // Set the header color + final ColorStateList headerColor = + a.getColorStateList(R.styleable.SuwColoredHeaderMixin_suwHeaderColor); + if (headerColor != null) { + setColor(headerColor); } - /** - * Sets the color of the header text. This can also be set via XML using - * {@code app:suwHeaderColor}. - * - * @param color The text color of the header. - */ - public void setColor(ColorStateList color) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setTextColor(color); - } - } + a.recycle(); + } - /** - * @return The current text color of the header. - */ - public ColorStateList getColor() { - final TextView titleView = getTextView(); - return titleView != null ? titleView.getTextColors() : null; + /** + * Sets the color of the header text. This can also be set via XML using {@code + * app:suwHeaderColor}. + * + * @param color The text color of the header. + */ + public void setColor(ColorStateList color) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setTextColor(color); } + } + + /** @return The current text color of the header. */ + public ColorStateList getColor() { + final TextView titleView = getTextView(); + return titleView != null ? titleView.getTextColors() : null; + } } diff --git a/library/main/src/com/android/setupwizardlib/template/HeaderMixin.java b/library/main/src/com/android/setupwizardlib/template/HeaderMixin.java index 604de9a..7d7fb4a 100644 --- a/library/main/src/com/android/setupwizardlib/template/HeaderMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/HeaderMixin.java @@ -17,80 +17,74 @@ package com.android.setupwizardlib.template; import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.widget.TextView; - import androidx.annotation.AttrRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import android.util.AttributeSet; +import android.widget.TextView; import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; -/** - * A {@link Mixin} for setting and getting the header text. - */ +/** A {@link Mixin} for setting and getting the header text. */ public class HeaderMixin implements Mixin { - private TemplateLayout mTemplateLayout; - - /** - * @param layout The layout this Mixin belongs to. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. - */ - public HeaderMixin(@NonNull TemplateLayout layout, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr) { - mTemplateLayout = layout; + private final TemplateLayout templateLayout; - final TypedArray a = layout.getContext().obtainStyledAttributes( - attrs, R.styleable.SuwHeaderMixin, defStyleAttr, 0); + /** + * @param layout The layout this Mixin belongs to. + * @param attrs XML attributes given to the layout. + * @param defStyleAttr The default style attribute as given to the constructor of the layout. + */ + public HeaderMixin( + @NonNull TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + templateLayout = layout; - // Set the header text - final CharSequence headerText = a.getText(R.styleable.SuwHeaderMixin_suwHeaderText); - if (headerText != null) { - setText(headerText); - } + final TypedArray a = + layout + .getContext() + .obtainStyledAttributes(attrs, R.styleable.SuwHeaderMixin, defStyleAttr, 0); - a.recycle(); + // Set the header text + final CharSequence headerText = a.getText(R.styleable.SuwHeaderMixin_suwHeaderText); + if (headerText != null) { + setText(headerText); } - /** - * @return The TextView displaying the header. - */ - public TextView getTextView() { - return (TextView) mTemplateLayout.findManagedViewById(R.id.suw_layout_title); - } + a.recycle(); + } - /** - * Sets the header text. This can also be set via the XML attribute {@code app:suwHeaderText}. - * - * @param title The resource ID of the text to be set as header. - */ - public void setText(int title) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setText(title); - } - } + /** @return The TextView displaying the header. */ + public TextView getTextView() { + return (TextView) templateLayout.findManagedViewById(R.id.suw_layout_title); + } - /** - * Sets the header text. This can also be set via the XML attribute {@code app:suwHeaderText}. - * - * @param title The text to be set as header. - */ - public void setText(CharSequence title) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setText(title); - } + /** + * Sets the header text. This can also be set via the XML attribute {@code app:suwHeaderText}. + * + * @param title The resource ID of the text to be set as header. + */ + public void setText(int title) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setText(title); } + } - /** - * @return The current header text. - */ - public CharSequence getText() { - final TextView titleView = getTextView(); - return titleView != null ? titleView.getText() : null; + /** + * Sets the header text. This can also be set via the XML attribute {@code app:suwHeaderText}. + * + * @param title The text to be set as header. + */ + public void setText(CharSequence title) { + final TextView titleView = getTextView(); + if (titleView != null) { + titleView.setText(title); } + } + + /** @return The current header text. */ + public CharSequence getText() { + final TextView titleView = getTextView(); + return titleView != null ? titleView.getText() : null; + } } diff --git a/library/main/src/com/android/setupwizardlib/template/IconMixin.java b/library/main/src/com/android/setupwizardlib/template/IconMixin.java index 5f5c915..e28f67d 100644 --- a/library/main/src/com/android/setupwizardlib/template/IconMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/IconMixin.java @@ -19,100 +19,88 @@ package com.android.setupwizardlib.template; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import androidx.annotation.DrawableRes; import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; - -import androidx.annotation.DrawableRes; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; -/** - * A {@link Mixin} for setting an icon on the template layout. - */ +/** A {@link Mixin} for setting an icon on the template layout. */ public class IconMixin implements Mixin { - private TemplateLayout mTemplateLayout; - - /** - * @param layout The template layout that this Mixin is a part of. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. - */ - public IconMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { - mTemplateLayout = layout; - final Context context = layout.getContext(); - - final TypedArray a = - context.obtainStyledAttributes(attrs, R.styleable.SuwIconMixin, defStyleAttr, 0); + private final TemplateLayout templateLayout; - final @DrawableRes int icon = a.getResourceId(R.styleable.SuwIconMixin_android_icon, 0); - if (icon != 0) { - setIcon(icon); - } + /** + * @param layout The template layout that this Mixin is a part of. + * @param attrs XML attributes given to the layout. + * @param defStyleAttr The default style attribute as given to the constructor of the layout. + */ + public IconMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { + templateLayout = layout; + final Context context = layout.getContext(); - a.recycle(); - } - - /** - * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. - * - * @param icon A drawable icon. - */ - public void setIcon(Drawable icon) { - final ImageView iconView = getView(); - if (iconView != null) { - iconView.setImageDrawable(icon); - iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE); - } - } + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwIconMixin, defStyleAttr, 0); - /** - * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. - * - * @param icon A drawable icon resource. - */ - public void setIcon(@DrawableRes int icon) { - final ImageView iconView = getView(); - if (iconView != null) { - // Note: setImageResource on the ImageView is overridden in AppCompatImageView for - // support lib users, which enables vector drawable compat to work on versions pre-L. - iconView.setImageResource(icon); - iconView.setVisibility(icon != 0 ? View.VISIBLE : View.GONE); - } + final @DrawableRes int icon = a.getResourceId(R.styleable.SuwIconMixin_android_icon, 0); + if (icon != 0) { + setIcon(icon); } - /** - * @return The icon previously set in {@link #setIcon(Drawable)} or {@code android:icon} - */ - public Drawable getIcon() { - final ImageView iconView = getView(); - return iconView != null ? iconView.getDrawable() : null; + a.recycle(); + } + + /** + * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. + * + * @param icon A drawable icon. + */ + public void setIcon(Drawable icon) { + final ImageView iconView = getView(); + if (iconView != null) { + iconView.setImageDrawable(icon); + iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE); } - - /** - * Sets the content description of the icon view - */ - public void setContentDescription(CharSequence description) { - final ImageView iconView = getView(); - if (iconView != null) { - iconView.setContentDescription(description); - } + } + + /** + * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. + * + * @param icon A drawable icon resource. + */ + public void setIcon(@DrawableRes int icon) { + final ImageView iconView = getView(); + if (iconView != null) { + // Note: setImageResource on the ImageView is overridden in AppCompatImageView for + // support lib users, which enables vector drawable compat to work on versions pre-L. + iconView.setImageResource(icon); + iconView.setVisibility(icon != 0 ? View.VISIBLE : View.GONE); } - - /** - * @return The content description of the icon view - */ - public CharSequence getContentDescription() { - final ImageView iconView = getView(); - return iconView != null ? iconView.getContentDescription() : null; - } - - /** - * @return The ImageView responsible for displaying the icon. - */ - protected ImageView getView() { - return (ImageView) mTemplateLayout.findManagedViewById(R.id.suw_layout_icon); + } + + /** @return The icon previously set in {@link #setIcon(Drawable)} or {@code android:icon} */ + public Drawable getIcon() { + final ImageView iconView = getView(); + return iconView != null ? iconView.getDrawable() : null; + } + + /** Sets the content description of the icon view */ + public void setContentDescription(CharSequence description) { + final ImageView iconView = getView(); + if (iconView != null) { + iconView.setContentDescription(description); } + } + + /** @return The content description of the icon view */ + public CharSequence getContentDescription() { + final ImageView iconView = getView(); + return iconView != null ? iconView.getContentDescription() : null; + } + + /** @return The ImageView responsible for displaying the icon. */ + protected ImageView getView() { + return (ImageView) templateLayout.findManagedViewById(R.id.suw_layout_icon); + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ListMixin.java b/library/main/src/com/android/setupwizardlib/template/ListMixin.java index cbc29b5..066141e 100644 --- a/library/main/src/com/android/setupwizardlib/template/ListMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/ListMixin.java @@ -21,16 +21,14 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION_CODES; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.widget.HeaderViewListAdapter; import android.widget.ListAdapter; import android.widget.ListView; - -import androidx.annotation.AttrRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.items.ItemAdapter; @@ -38,188 +36,170 @@ import com.android.setupwizardlib.items.ItemGroup; import com.android.setupwizardlib.items.ItemInflater; import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper; -/** - * A {@link Mixin} for interacting with ListViews. - */ +/** A {@link Mixin} for interacting with ListViews. */ public class ListMixin implements Mixin { - private TemplateLayout mTemplateLayout; - - @Nullable - private ListView mListView; - - private Drawable mDivider; - private Drawable mDefaultDivider; - - private int mDividerInsetStart; - private int mDividerInsetEnd; - - /** - * @param layout The layout this mixin belongs to. - */ - public ListMixin(@NonNull TemplateLayout layout, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr) { - mTemplateLayout = layout; - - final Context context = layout.getContext(); - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.SuwListMixin, defStyleAttr, 0); - - final int entries = a.getResourceId(R.styleable.SuwListMixin_android_entries, 0); - if (entries != 0) { - final ItemGroup inflated = - (ItemGroup) new ItemInflater(context).inflate(entries); - setAdapter(new ItemAdapter(inflated)); - } - int dividerInset = - a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInset, -1); - if (dividerInset != -1) { - setDividerInset(dividerInset); - } else { - int dividerInsetStart = - a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetStart, 0); - int dividerInsetEnd = - a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetEnd, 0); - setDividerInsets(dividerInsetStart, dividerInsetEnd); - } - a.recycle(); - } + private final TemplateLayout templateLayout; - /** - * @return The list view contained in the layout, as marked by {@code @android:id/list}. This - * will return {@code null} if the list doesn't exist in the layout. - */ - public ListView getListView() { - return getListViewInternal(); - } + @Nullable private ListView listView; - // Client code can assume getListView() will not be null if they know their template contains - // the list, but this mixin cannot. Any usages of getListView in this mixin needs null checks. - @Nullable - private ListView getListViewInternal() { - if (mListView == null) { - final View list = mTemplateLayout.findManagedViewById(android.R.id.list); - if (list instanceof ListView) { - mListView = (ListView) list; - } - } - return mListView; - } + private Drawable divider; + private Drawable defaultDivider; - /** - * List mixin needs to update the dividers if the layout direction has changed. This method - * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template - * is called. - */ - public void onLayout() { - if (mDivider == null) { - // Update divider in case layout direction has just been resolved - updateDivider(); - } - } + private int dividerInsetStart; + private int dividerInsetEnd; - /** - * Gets the adapter of the list view in this layout. If the adapter is a HeaderViewListAdapter, - * this method will unwrap it and return the underlying adapter. - * - * @return The adapter, or {@code null} if there is no list, or if the list has no adapter. - */ - public ListAdapter getAdapter() { - final ListView listView = getListViewInternal(); - if (listView != null) { - final ListAdapter adapter = listView.getAdapter(); - if (adapter instanceof HeaderViewListAdapter) { - return ((HeaderViewListAdapter) adapter).getWrappedAdapter(); - } - return adapter; - } - return null; - } + /** @param layout The layout this mixin belongs to. */ + public ListMixin( + @NonNull TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + templateLayout = layout; - /** - * Sets the adapter on the list view in this layout. - */ - public void setAdapter(ListAdapter adapter) { - final ListView listView = getListViewInternal(); - if (listView != null) { - listView.setAdapter(adapter); - } - } + final Context context = layout.getContext(); + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwListMixin, defStyleAttr, 0); - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - setDividerInsets(inset, 0); + final int entries = a.getResourceId(R.styleable.SuwListMixin_android_entries, 0); + if (entries != 0) { + final ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(entries); + setAdapter(new ItemAdapter(inflated)); } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start The number of pixels to inset on the "start" side of the list divider. Typically - * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or - * {@code @dimen/suw_items_glif_text_divider_inset}. - * @param end The number of pixels to inset on the "end" side of the list divider. - */ - public void setDividerInsets(int start, int end) { - mDividerInsetStart = start; - mDividerInsetEnd = end; - updateDivider(); + int dividerInset = a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInset, -1); + if (dividerInset != -1) { + setDividerInset(dividerInset); + } else { + int dividerInsetStart = + a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetStart, 0); + int dividerInsetEnd = a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetEnd, 0); + setDividerInsets(dividerInsetStart, dividerInsetEnd); } - - /** - * @return The number of pixels inset on the start side of the divider. - * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. - */ - @Deprecated - public int getDividerInset() { - return getDividerInsetStart(); + a.recycle(); + } + + /** + * @return The list view contained in the layout, as marked by {@code @android:id/list}. This will + * return {@code null} if the list doesn't exist in the layout. + */ + public ListView getListView() { + return getListViewInternal(); + } + + // Client code can assume getListView() will not be null if they know their template contains + // the list, but this mixin cannot. Any usages of getListView in this mixin needs null checks. + @Nullable + private ListView getListViewInternal() { + if (listView == null) { + final View list = templateLayout.findManagedViewById(android.R.id.list); + if (list instanceof ListView) { + listView = (ListView) list; + } } - - /** - * @return The number of pixels inset on the start side of the divider. - */ - public int getDividerInsetStart() { - return mDividerInsetStart; + return listView; + } + + /** + * List mixin needs to update the dividers if the layout direction has changed. This method should + * be called when {@link View#onLayout(boolean, int, int, int, int)} of the template is called. + */ + public void onLayout() { + if (divider == null) { + // Update divider in case layout direction has just been resolved + updateDivider(); } - - /** - * @return The number of pixels inset on the end side of the divider. - */ - public int getDividerInsetEnd() { - return mDividerInsetEnd; + } + + /** + * Gets the adapter of the list view in this layout. If the adapter is a HeaderViewListAdapter, + * this method will unwrap it and return the underlying adapter. + * + * @return The adapter, or {@code null} if there is no list, or if the list has no adapter. + */ + public ListAdapter getAdapter() { + final ListView listView = getListViewInternal(); + if (listView != null) { + final ListAdapter adapter = listView.getAdapter(); + if (adapter instanceof HeaderViewListAdapter) { + return ((HeaderViewListAdapter) adapter).getWrappedAdapter(); + } + return adapter; } - - private void updateDivider() { - final ListView listView = getListViewInternal(); - if (listView == null) { - return; - } - boolean shouldUpdate = true; - if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - shouldUpdate = mTemplateLayout.isLayoutDirectionResolved(); - } - if (shouldUpdate) { - if (mDefaultDivider == null) { - mDefaultDivider = listView.getDivider(); - } - mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable( - mDefaultDivider, - mDividerInsetStart /* start */, - 0 /* top */, - mDividerInsetEnd /* end */, - 0 /* bottom */, - mTemplateLayout); - listView.setDivider(mDivider); - } + return null; + } + + /** Sets the adapter on the list view in this layout. */ + public void setAdapter(ListAdapter adapter) { + final ListView listView = getListViewInternal(); + if (listView != null) { + listView.setAdapter(adapter); } - - /** - * @return The drawable used as the divider. - */ - public Drawable getDivider() { - return mDivider; + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + setDividerInsets(inset, 0); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start The number of pixels to inset on the "start" side of the list divider. Typically + * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or + * {@code @dimen/suw_items_glif_text_divider_inset}. + * @param end The number of pixels to inset on the "end" side of the list divider. + */ + public void setDividerInsets(int start, int end) { + dividerInsetStart = start; + dividerInsetEnd = end; + updateDivider(); + } + + /** + * @return The number of pixels inset on the start side of the divider. + * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. + */ + @Deprecated + public int getDividerInset() { + return getDividerInsetStart(); + } + + /** @return The number of pixels inset on the start side of the divider. */ + public int getDividerInsetStart() { + return dividerInsetStart; + } + + /** @return The number of pixels inset on the end side of the divider. */ + public int getDividerInsetEnd() { + return dividerInsetEnd; + } + + private void updateDivider() { + final ListView listView = getListViewInternal(); + if (listView == null) { + return; } + boolean shouldUpdate = true; + if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + shouldUpdate = templateLayout.isLayoutDirectionResolved(); + } + if (shouldUpdate) { + if (defaultDivider == null) { + defaultDivider = listView.getDivider(); + } + divider = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + defaultDivider, + dividerInsetStart /* start */, + 0 /* top */, + dividerInsetEnd /* end */, + 0 /* bottom */, + templateLayout); + listView.setDivider(divider); + } + } + + /** @return The drawable used as the divider. */ + public Drawable getDivider() { + return divider; + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java b/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java index faea305..8614ff4 100644 --- a/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java +++ b/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java @@ -16,73 +16,67 @@ package com.android.setupwizardlib.template; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.Log; import android.widget.AbsListView; import android.widget.ListAdapter; import android.widget.ListView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate; /** - * {@link ScrollHandlingDelegate} which analyzes scroll events from {@link ListView} and - * notifies {@link RequireScrollMixin} about scrollability changes. + * {@link ScrollHandlingDelegate} which analyzes scroll events from {@link ListView} and notifies + * {@link RequireScrollMixin} about scrollability changes. */ -public class ListViewScrollHandlingDelegate implements ScrollHandlingDelegate, - AbsListView.OnScrollListener { +public class ListViewScrollHandlingDelegate + implements ScrollHandlingDelegate, AbsListView.OnScrollListener { - private static final String TAG = "ListViewDelegate"; + private static final String TAG = "ListViewDelegate"; - private static final int SCROLL_DURATION = 500; + private static final int SCROLL_DURATION = 500; - @NonNull - private final RequireScrollMixin mRequireScrollMixin; + @NonNull private final RequireScrollMixin requireScrollMixin; - @Nullable - private final ListView mListView; + @Nullable private final ListView listView; - public ListViewScrollHandlingDelegate( - @NonNull RequireScrollMixin requireScrollMixin, - @Nullable ListView listView) { - mRequireScrollMixin = requireScrollMixin; - mListView = listView; - } + public ListViewScrollHandlingDelegate( + @NonNull RequireScrollMixin requireScrollMixin, @Nullable ListView listView) { + this.requireScrollMixin = requireScrollMixin; + this.listView = listView; + } - @Override - public void startListening() { - if (mListView != null) { - mListView.setOnScrollListener(this); + @Override + public void startListening() { + if (listView != null) { + listView.setOnScrollListener(this); - final ListAdapter adapter = mListView.getAdapter(); - if (mListView.getLastVisiblePosition() < adapter.getCount()) { - mRequireScrollMixin.notifyScrollabilityChange(true); - } - } else { - Log.w(TAG, "Cannot require scroll. List view is null"); - } + final ListAdapter adapter = listView.getAdapter(); + if (listView.getLastVisiblePosition() < adapter.getCount()) { + requireScrollMixin.notifyScrollabilityChange(true); + } + } else { + Log.w(TAG, "Cannot require scroll. List view is null"); } + } - @Override - public void pageScrollDown() { - if (mListView != null) { - final int height = mListView.getHeight(); - mListView.smoothScrollBy(height, SCROLL_DURATION); - } + @Override + public void pageScrollDown() { + if (listView != null) { + final int height = listView.getHeight(); + listView.smoothScrollBy(height, SCROLL_DURATION); } + } - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) {} - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - if (firstVisibleItem + visibleItemCount >= totalItemCount) { - mRequireScrollMixin.notifyScrollabilityChange(false); - } else { - mRequireScrollMixin.notifyScrollabilityChange(true); - } + @Override + public void onScroll( + AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (firstVisibleItem + visibleItemCount >= totalItemCount) { + requireScrollMixin.notifyScrollabilityChange(false); + } else { + requireScrollMixin.notifyScrollabilityChange(true); } + } } diff --git a/library/main/src/com/android/setupwizardlib/template/Mixin.java b/library/main/src/com/android/setupwizardlib/template/Mixin.java index b7b8893..b460777 100644 --- a/library/main/src/com/android/setupwizardlib/template/Mixin.java +++ b/library/main/src/com/android/setupwizardlib/template/Mixin.java @@ -22,5 +22,4 @@ package com.android.setupwizardlib.template; * @see com.android.setupwizardlib.TemplateLayout#registerMixin(Class, Mixin) * @see com.android.setupwizardlib.TemplateLayout#getMixin(Class) */ -public interface Mixin { -} +public interface Mixin {} diff --git a/library/main/src/com/android/setupwizardlib/template/NavigationBarMixin.java b/library/main/src/com/android/setupwizardlib/template/NavigationBarMixin.java index df35017..2412eda 100644 --- a/library/main/src/com/android/setupwizardlib/template/NavigationBarMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/NavigationBarMixin.java @@ -17,67 +17,60 @@ package com.android.setupwizardlib.template; import android.view.View; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.view.NavigationBar; import com.android.setupwizardlib.view.NavigationBar.NavigationBarListener; -/** - * A {@link Mixin} for interacting with a {@link NavigationBar}. - */ +/** A {@link Mixin} for interacting with a {@link NavigationBar}. */ public class NavigationBarMixin implements Mixin { - private TemplateLayout mTemplateLayout; + private final TemplateLayout templateLayout; - /** - * @param layout The layout this mixin belongs to. - */ - public NavigationBarMixin(TemplateLayout layout) { - mTemplateLayout = layout; - } + /** @param layout The layout this mixin belongs to. */ + public NavigationBarMixin(TemplateLayout layout) { + templateLayout = layout; + } - /** - * @return The navigation bar instance in the layout, or null if the layout does not have a - * navigation bar. - */ - public NavigationBar getNavigationBar() { - final View view = mTemplateLayout.findManagedViewById(R.id.suw_layout_navigation_bar); - return view instanceof NavigationBar ? (NavigationBar) view : null; - } + /** + * @return The navigation bar instance in the layout, or null if the layout does not have a + * navigation bar. + */ + public NavigationBar getNavigationBar() { + final View view = templateLayout.findManagedViewById(R.id.suw_layout_navigation_bar); + return view instanceof NavigationBar ? (NavigationBar) view : null; + } - /** - * Sets the label of the next button. - * - * @param text Label of the next button. - */ - public void setNextButtonText(int text) { - getNavigationBar().getNextButton().setText(text); - } + /** + * Sets the label of the next button. + * + * @param text Label of the next button. + */ + public void setNextButtonText(int text) { + getNavigationBar().getNextButton().setText(text); + } - /** - * Sets the label of the next button. - * - * @param text Label of the next button. - */ - public void setNextButtonText(CharSequence text) { - getNavigationBar().getNextButton().setText(text); - } + /** + * Sets the label of the next button. + * + * @param text Label of the next button. + */ + public void setNextButtonText(CharSequence text) { + getNavigationBar().getNextButton().setText(text); + } - /** - * @return The current label of the next button. - */ - public CharSequence getNextButtonText() { - return getNavigationBar().getNextButton().getText(); - } + /** @return The current label of the next button. */ + public CharSequence getNextButtonText() { + return getNavigationBar().getNextButton().getText(); + } - /** - * Sets the listener to handle back and next button clicks in the navigation bar. - * - * @see NavigationBar#setNavigationBarListener(NavigationBarListener) - * @see NavigationBarListener - */ - public void setNavigationBarListener(NavigationBarListener listener) { - getNavigationBar().setNavigationBarListener(listener); - } + /** + * Sets the listener to handle back and next button clicks in the navigation bar. + * + * @see NavigationBar#setNavigationBarListener(NavigationBarListener) + * @see NavigationBarListener + */ + public void setNavigationBarListener(NavigationBarListener listener) { + getNavigationBar().setNavigationBarListener(listener); + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ProgressBarMixin.java b/library/main/src/com/android/setupwizardlib/template/ProgressBarMixin.java index 504b2f0..0e128c4 100644 --- a/library/main/src/com/android/setupwizardlib/template/ProgressBarMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/ProgressBarMixin.java @@ -19,121 +19,109 @@ package com.android.setupwizardlib.template; import android.content.res.ColorStateList; import android.os.Build; import android.os.Build.VERSION_CODES; +import androidx.annotation.Nullable; import android.view.View; import android.view.ViewStub; import android.widget.ProgressBar; - -import androidx.annotation.Nullable; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; -/** - * A {@link Mixin} for showing a progress bar. - */ +/** A {@link Mixin} for showing a progress bar. */ public class ProgressBarMixin implements Mixin { - private TemplateLayout mTemplateLayout; + private final TemplateLayout templateLayout; - @Nullable - private ColorStateList mColor; + @Nullable private ColorStateList color; - /** - * @param layout The layout this mixin belongs to. - */ - public ProgressBarMixin(TemplateLayout layout) { - mTemplateLayout = layout; - } + /** @param layout The layout this mixin belongs to. */ + public ProgressBarMixin(TemplateLayout layout) { + templateLayout = layout; + } - /** - * @return True if the progress bar is currently shown. - */ - public boolean isShown() { - final View progressBar = mTemplateLayout.findManagedViewById(R.id.suw_layout_progress); - return progressBar != null && progressBar.getVisibility() == View.VISIBLE; - } + /** @return True if the progress bar is currently shown. */ + public boolean isShown() { + final View progressBar = templateLayout.findManagedViewById(R.id.suw_layout_progress); + return progressBar != null && progressBar.getVisibility() == View.VISIBLE; + } - /** - * Sets whether the progress bar is shown. If the progress bar has not been inflated from the - * stub, this method will inflate the progress bar. - * - * @param shown True to show the progress bar, false to hide it. - */ - public void setShown(boolean shown) { - if (shown) { - View progressBar = getProgressBar(); - if (progressBar != null) { - progressBar.setVisibility(View.VISIBLE); - } - } else { - View progressBar = peekProgressBar(); - if (progressBar != null) { - progressBar.setVisibility(View.GONE); - } - } + /** + * Sets whether the progress bar is shown. If the progress bar has not been inflated from the + * stub, this method will inflate the progress bar. + * + * @param shown True to show the progress bar, false to hide it. + */ + public void setShown(boolean shown) { + if (shown) { + View progressBar = getProgressBar(); + if (progressBar != null) { + progressBar.setVisibility(View.VISIBLE); + } + } else { + View progressBar = peekProgressBar(); + if (progressBar != null) { + progressBar.setVisibility(View.GONE); + } } + } - /** - * Gets the progress bar in the layout. If the progress bar has not been used before, it will be - * installed (i.e. inflated from its view stub). - * - * @return The progress bar of this layout. May be null only if the template used doesn't have a - * progress bar built-in. - */ - private ProgressBar getProgressBar() { - final View progressBar = peekProgressBar(); - if (progressBar == null) { - final ViewStub progressBarStub = - (ViewStub) mTemplateLayout.findManagedViewById(R.id.suw_layout_progress_stub); - if (progressBarStub != null) { - progressBarStub.inflate(); - } - setColor(mColor); - } - return peekProgressBar(); + /** + * Gets the progress bar in the layout. If the progress bar has not been used before, it will be + * installed (i.e. inflated from its view stub). + * + * @return The progress bar of this layout. May be null only if the template used doesn't have a + * progress bar built-in. + */ + private ProgressBar getProgressBar() { + final View progressBar = peekProgressBar(); + if (progressBar == null) { + final ViewStub progressBarStub = + (ViewStub) templateLayout.findManagedViewById(R.id.suw_layout_progress_stub); + if (progressBarStub != null) { + progressBarStub.inflate(); + } + setColor(color); } + return peekProgressBar(); + } - /** - * Gets the progress bar in the layout only if it has been installed. - * {@link #setShown(boolean)} should be called before this to ensure the progress bar - * is set up correctly. - * - * @return The progress bar of this layout, or null if the progress bar is not installed. The - * null case can happen either if {@link #setShown(boolean)} with true was - * not called before this, or if the template does not contain a progress bar. - */ - public ProgressBar peekProgressBar() { - return (ProgressBar) mTemplateLayout.findManagedViewById(R.id.suw_layout_progress); - } + /** + * Gets the progress bar in the layout only if it has been installed. {@link #setShown(boolean)} + * should be called before this to ensure the progress bar is set up correctly. + * + * @return The progress bar of this layout, or null if the progress bar is not installed. The null + * case can happen either if {@link #setShown(boolean)} with true was not called before this, + * or if the template does not contain a progress bar. + */ + public ProgressBar peekProgressBar() { + return (ProgressBar) templateLayout.findManagedViewById(R.id.suw_layout_progress); + } - /** - * Sets the color of the indeterminate progress bar. This method is a no-op on SDK < 21. - */ - public void setColor(@Nullable ColorStateList color) { - mColor = color; - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - final ProgressBar bar = peekProgressBar(); - if (bar != null) { - bar.setIndeterminateTintList(color); - if (Build.VERSION.SDK_INT >= VERSION_CODES.M || color != null) { - // There is a bug in Lollipop where setting the progress tint color to null - // will crash with "java.lang.NullPointerException: Attempt to invoke virtual - // method 'int android.graphics.Paint.getAlpha()' on a null object reference" - // at android.graphics.drawable.NinePatchDrawable.draw(:250) - // The bug doesn't affect ProgressBar on M because it uses ShapeDrawable instead - // of NinePatchDrawable. (commit 6a8253fdc9f4574c28b4beeeed90580ffc93734a) - bar.setProgressBackgroundTintList(color); - } - } + /** Sets the color of the indeterminate progress bar. This method is a no-op on SDK < 21. */ + public void setColor(@Nullable ColorStateList color) { + this.color = color; + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + final ProgressBar bar = peekProgressBar(); + if (bar != null) { + bar.setIndeterminateTintList(color); + if (Build.VERSION.SDK_INT >= VERSION_CODES.M || color != null) { + // There is a bug in Lollipop where setting the progress tint color to null + // will crash with "java.lang.NullPointerException: Attempt to invoke virtual + // method 'int android.graphics.Paint.getAlpha()' on a null object reference" + // at android.graphics.drawable.NinePatchDrawable.draw(:250) + // The bug doesn't affect ProgressBar on M because it uses ShapeDrawable instead + // of NinePatchDrawable. (commit 6a8253fdc9f4574c28b4beeeed90580ffc93734a) + bar.setProgressBackgroundTintList(color); } + } } + } - /** - * @return The color previously set in {@link #setColor(ColorStateList)}, or null if the color - * is not set. In case of null, the color of the progress bar will be inherited from the theme. - */ - @Nullable - public ColorStateList getColor() { - return mColor; - } + /** + * @return The color previously set in {@link #setColor(ColorStateList)}, or null if the color is + * not set. In case of null, the color of the progress bar will be inherited from the theme. + */ + @Nullable + public ColorStateList getColor() { + return color; + } } diff --git a/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java b/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java index fd3303b..02bcc1c 100644 --- a/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java @@ -18,244 +18,222 @@ package com.android.setupwizardlib.template; import android.os.Handler; import android.os.Looper; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; - +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.view.NavigationBar; /** - * A mixin to require the a scrollable container (BottomScrollView, RecyclerView or ListView) to - * be scrolled to bottom, making sure that the user sees all content above and below the fold. + * A mixin to require the a scrollable container (BottomScrollView, RecyclerView or ListView) to be + * scrolled to bottom, making sure that the user sees all content above and below the fold. */ public class RequireScrollMixin implements Mixin { - /* static section */ - - /** - * Listener for when the require-scroll state changes. Note that this only requires the user to - * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to - * bottom is not required again. - */ - public interface OnRequireScrollStateChangedListener { + /* static section */ - /** - * Called when require-scroll state changed. - * - * @param scrollNeeded True if the user should be required to scroll to bottom. - */ - void onRequireScrollStateChanged(boolean scrollNeeded); - } + /** + * Listener for when the require-scroll state changes. Note that this only requires the user to + * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to bottom + * is not required again. + */ + public interface OnRequireScrollStateChangedListener { /** - * A delegate to detect scrollability changes and to scroll the page. This provides a layer - * of abstraction for BottomScrollView, RecyclerView and ListView. The delegate should call - * {@link #notifyScrollabilityChange(boolean)} when the view scrollability is changed. - */ - interface ScrollHandlingDelegate { - - /** - * Starts listening to scrollability changes at the target scrollable container. - */ - void startListening(); - - /** - * Scroll the page content down by one page. - */ - void pageScrollDown(); - } - - /* non-static section */ - - @NonNull - private final TemplateLayout mTemplateLayout; - - private final Handler mHandler = new Handler(Looper.getMainLooper()); - - private boolean mRequiringScrollToBottom = false; - - // Whether the user have seen the more button yet. - private boolean mEverScrolledToBottom = false; - - private ScrollHandlingDelegate mDelegate; - - @Nullable - private OnRequireScrollStateChangedListener mListener; - - /** - * @param templateLayout The template containing this mixin - */ - public RequireScrollMixin(@NonNull TemplateLayout templateLayout) { - mTemplateLayout = templateLayout; - } - - /** - * Sets the delegate to handle scrolling. The type of delegate should depend on whether the - * scrolling view is a BottomScrollView, RecyclerView or ListView. - */ - public void setScrollHandlingDelegate(@NonNull ScrollHandlingDelegate delegate) { - mDelegate = delegate; - } - - /** - * Listen to require scroll state changes. When scroll is required, - * {@link OnRequireScrollStateChangedListener#onRequireScrollStateChanged(boolean)} is called - * with {@code true}, and vice versa. - */ - public void setOnRequireScrollStateChangedListener( - @Nullable OnRequireScrollStateChangedListener listener) { - mListener = listener; - } - - /** - * @return The scroll state listener previously set, or {@code null} if none is registered. - */ - public OnRequireScrollStateChangedListener getOnRequireScrollStateChangedListener() { - return mListener; - } - - /** - * Creates an {@link OnClickListener} which if scrolling is required, will scroll the page down, - * and if scrolling is not required, delegates to the wrapped {@code listener}. Note that you - * should call {@link #requireScroll()} as well in order to start requiring scrolling. + * Called when require-scroll state changed. * - * @param listener The listener to be invoked when scrolling is not needed and the user taps on - * the button. If {@code null}, the click listener will be a no-op when scroll - * is not required. - * @return A new {@link OnClickListener} which will scroll the page down or delegate to the - * given listener depending on the current require-scroll state. - */ - public OnClickListener createOnClickListener(@Nullable final OnClickListener listener) { - return new OnClickListener() { - @Override - public void onClick(View view) { - if (mRequiringScrollToBottom) { - mDelegate.pageScrollDown(); - } else if (listener != null) { - listener.onClick(view); - } - } - }; - } - - /** - * Coordinate with the given navigation bar to require scrolling on the page. The more button - * will be shown instead of the next button while scrolling is required. + * @param scrollNeeded True if the user should be required to scroll to bottom. */ - public void requireScrollWithNavigationBar(@NonNull final NavigationBar navigationBar) { - setOnRequireScrollStateChangedListener( - new OnRequireScrollStateChangedListener() { - @Override - public void onRequireScrollStateChanged(boolean scrollNeeded) { - navigationBar.getMoreButton() - .setVisibility(scrollNeeded ? View.VISIBLE : View.GONE); - navigationBar.getNextButton() - .setVisibility(scrollNeeded ? View.GONE : View.VISIBLE); - } - }); - navigationBar.getMoreButton().setOnClickListener(createOnClickListener(null)); - requireScroll(); - } - - /** - * @see #requireScrollWithButton(Button, CharSequence, OnClickListener) - */ - public void requireScrollWithButton( - @NonNull Button button, - @StringRes int moreText, - @Nullable OnClickListener onClickListener) { - requireScrollWithButton(button, button.getContext().getText(moreText), onClickListener); - } - - /** - * Use the given {@code button} to require scrolling. When scrolling is required, the button - * label will change to {@code moreText}, and tapping the button will cause the page to scroll - * down. - * - *

Note: Calling {@link View#setOnClickListener} on the button after this method will remove - * its link to the require-scroll mechanism. If you need to do that, obtain the click listener - * from {@link #createOnClickListener(OnClickListener)}. - * - *

Note: The normal button label is taken from the button's text at the time of calling this - * method. Calling {@link android.widget.TextView#setText} after calling this method causes - * undefined behavior. - * - * @param button The button to use for require scroll. The button's "normal" label is taken from - * the text at the time of calling this method, and the click listener of it will - * be replaced. - * @param moreText The button label when scroll is required. - * @param onClickListener The listener for clicks when scrolling is not required. - */ - public void requireScrollWithButton( - @NonNull final Button button, - final CharSequence moreText, - @Nullable OnClickListener onClickListener) { - final CharSequence nextText = button.getText(); - button.setOnClickListener(createOnClickListener(onClickListener)); - setOnRequireScrollStateChangedListener(new OnRequireScrollStateChangedListener() { - @Override - public void onRequireScrollStateChanged(boolean scrollNeeded) { - button.setText(scrollNeeded ? moreText : nextText); - } + void onRequireScrollStateChanged(boolean scrollNeeded); + } + + /** + * A delegate to detect scrollability changes and to scroll the page. This provides a layer of + * abstraction for BottomScrollView, RecyclerView and ListView. The delegate should call {@link + * #notifyScrollabilityChange(boolean)} when the view scrollability is changed. + */ + interface ScrollHandlingDelegate { + + /** Starts listening to scrollability changes at the target scrollable container. */ + void startListening(); + + /** Scroll the page content down by one page. */ + void pageScrollDown(); + } + + /* non-static section */ + + private final Handler handler = new Handler(Looper.getMainLooper()); + + private boolean requiringScrollToBottom = false; + + // Whether the user have seen the more button yet. + private boolean everScrolledToBottom = false; + + private ScrollHandlingDelegate delegate; + + @Nullable private OnRequireScrollStateChangedListener listener; + + /** @param templateLayout The template containing this mixin */ + public RequireScrollMixin(@NonNull TemplateLayout templateLayout) { + } + + /** + * Sets the delegate to handle scrolling. The type of delegate should depend on whether the + * scrolling view is a BottomScrollView, RecyclerView or ListView. + */ + public void setScrollHandlingDelegate(@NonNull ScrollHandlingDelegate delegate) { + this.delegate = delegate; + } + + /** + * Listen to require scroll state changes. When scroll is required, {@link + * OnRequireScrollStateChangedListener#onRequireScrollStateChanged(boolean)} is called with {@code + * true}, and vice versa. + */ + public void setOnRequireScrollStateChangedListener( + @Nullable OnRequireScrollStateChangedListener listener) { + this.listener = listener; + } + + /** @return The scroll state listener previously set, or {@code null} if none is registered. */ + public OnRequireScrollStateChangedListener getOnRequireScrollStateChangedListener() { + return listener; + } + + /** + * Creates an {@link OnClickListener} which if scrolling is required, will scroll the page down, + * and if scrolling is not required, delegates to the wrapped {@code listener}. Note that you + * should call {@link #requireScroll()} as well in order to start requiring scrolling. + * + * @param listener The listener to be invoked when scrolling is not needed and the user taps on + * the button. If {@code null}, the click listener will be a no-op when scroll is not + * required. + * @return A new {@link OnClickListener} which will scroll the page down or delegate to the given + * listener depending on the current require-scroll state. + */ + public OnClickListener createOnClickListener(@Nullable final OnClickListener listener) { + return new OnClickListener() { + @Override + public void onClick(View view) { + if (requiringScrollToBottom) { + delegate.pageScrollDown(); + } else if (listener != null) { + listener.onClick(view); + } + } + }; + } + + /** + * Coordinate with the given navigation bar to require scrolling on the page. The more button will + * be shown instead of the next button while scrolling is required. + */ + public void requireScrollWithNavigationBar(@NonNull final NavigationBar navigationBar) { + setOnRequireScrollStateChangedListener( + new OnRequireScrollStateChangedListener() { + @Override + public void onRequireScrollStateChanged(boolean scrollNeeded) { + navigationBar.getMoreButton().setVisibility(scrollNeeded ? View.VISIBLE : View.GONE); + navigationBar.getNextButton().setVisibility(scrollNeeded ? View.GONE : View.VISIBLE); + } }); - requireScroll(); - } - - /** - * @return True if scrolling is required. Note that this mixin only requires the user to - * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to - * bottom is not required again. - */ - public boolean isScrollingRequired() { - return mRequiringScrollToBottom; + navigationBar.getMoreButton().setOnClickListener(createOnClickListener(null)); + requireScroll(); + } + + /** @see #requireScrollWithButton(Button, CharSequence, OnClickListener) */ + public void requireScrollWithButton( + @NonNull Button button, @StringRes int moreText, @Nullable OnClickListener onClickListener) { + requireScrollWithButton(button, button.getContext().getText(moreText), onClickListener); + } + + /** + * Use the given {@code button} to require scrolling. When scrolling is required, the button label + * will change to {@code moreText}, and tapping the button will cause the page to scroll down. + * + *

Note: Calling {@link View#setOnClickListener} on the button after this method will remove + * its link to the require-scroll mechanism. If you need to do that, obtain the click listener + * from {@link #createOnClickListener(OnClickListener)}. + * + *

Note: The normal button label is taken from the button's text at the time of calling this + * method. Calling {@link android.widget.TextView#setText} after calling this method causes + * undefined behavior. + * + * @param button The button to use for require scroll. The button's "normal" label is taken from + * the text at the time of calling this method, and the click listener of it will be replaced. + * @param moreText The button label when scroll is required. + * @param onClickListener The listener for clicks when scrolling is not required. + */ + public void requireScrollWithButton( + @NonNull final Button button, + final CharSequence moreText, + @Nullable OnClickListener onClickListener) { + final CharSequence nextText = button.getText(); + button.setOnClickListener(createOnClickListener(onClickListener)); + setOnRequireScrollStateChangedListener( + new OnRequireScrollStateChangedListener() { + @Override + public void onRequireScrollStateChanged(boolean scrollNeeded) { + button.setText(scrollNeeded ? moreText : nextText); + } + }); + requireScroll(); + } + + /** + * @return True if scrolling is required. Note that this mixin only requires the user to scroll to + * the bottom once - if the user scrolled to the bottom and back-up, scrolling to bottom is + * not required again. + */ + public boolean isScrollingRequired() { + return requiringScrollToBottom; + } + + /** + * Start requiring scrolling on the layout. After calling this method, this mixin will start + * listening to scroll events from the scrolling container, and call {@link + * OnRequireScrollStateChangedListener} when the scroll state changes. + */ + public void requireScroll() { + delegate.startListening(); + } + + /** + * {@link ScrollHandlingDelegate} should call this method when the scrollability of the scrolling + * container changed, so this mixin can recompute whether scrolling should be required. + * + * @param canScrollDown True if the view can scroll down further. + */ + void notifyScrollabilityChange(boolean canScrollDown) { + if (canScrollDown == requiringScrollToBottom) { + // Already at the desired require-scroll state + return; } - - /** - * Start requiring scrolling on the layout. After calling this method, this mixin will start - * listening to scroll events from the scrolling container, and call - * {@link OnRequireScrollStateChangedListener} when the scroll state changes. - */ - public void requireScroll() { - mDelegate.startListening(); + if (canScrollDown) { + if (!everScrolledToBottom) { + postScrollStateChange(true); + requiringScrollToBottom = true; + } + } else { + postScrollStateChange(false); + requiringScrollToBottom = false; + everScrolledToBottom = true; } - - /** - * {@link ScrollHandlingDelegate} should call this method when the scrollability of the - * scrolling container changed, so this mixin can recompute whether scrolling should be - * required. - * - * @param canScrollDown True if the view can scroll down further. - */ - void notifyScrollabilityChange(boolean canScrollDown) { - if (canScrollDown == mRequiringScrollToBottom) { - // Already at the desired require-scroll state - return; - } - if (canScrollDown) { - if (!mEverScrolledToBottom) { - postScrollStateChange(true); - mRequiringScrollToBottom = true; - } - } else { - postScrollStateChange(false); - mRequiringScrollToBottom = false; - mEverScrolledToBottom = true; - } - } - - private void postScrollStateChange(final boolean scrollNeeded) { - mHandler.post(new Runnable() { - @Override - public void run() { - if (mListener != null) { - mListener.onRequireScrollStateChanged(scrollNeeded); - } + } + + private void postScrollStateChange(final boolean scrollNeeded) { + handler.post( + new Runnable() { + @Override + public void run() { + if (listener != null) { + listener.onRequireScrollStateChanged(scrollNeeded); } + } }); - } + } } diff --git a/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java b/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java index 9e4d1cf..dcaa379 100644 --- a/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java +++ b/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java @@ -16,12 +16,10 @@ package com.android.setupwizardlib.template; -import android.util.Log; -import android.widget.ScrollView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import android.util.Log; +import android.widget.ScrollView; import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate; import com.android.setupwizardlib.view.BottomScrollView; import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener; @@ -31,51 +29,48 @@ import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener; * notifies {@link RequireScrollMixin} about scrollability changes. */ public class ScrollViewScrollHandlingDelegate - implements ScrollHandlingDelegate, BottomScrollListener { + implements ScrollHandlingDelegate, BottomScrollListener { - private static final String TAG = "ScrollViewDelegate"; + private static final String TAG = "ScrollViewDelegate"; - @NonNull - private final RequireScrollMixin mRequireScrollMixin; + @NonNull private final RequireScrollMixin requireScrollMixin; - @Nullable - private final BottomScrollView mScrollView; + @Nullable private final BottomScrollView scrollView; - public ScrollViewScrollHandlingDelegate( - @NonNull RequireScrollMixin requireScrollMixin, - @Nullable ScrollView scrollView) { - mRequireScrollMixin = requireScrollMixin; - if (scrollView instanceof BottomScrollView) { - mScrollView = (BottomScrollView) scrollView; - } else { - Log.w(TAG, "Cannot set non-BottomScrollView. Found=" + scrollView); - mScrollView = null; - } + public ScrollViewScrollHandlingDelegate( + @NonNull RequireScrollMixin requireScrollMixin, @Nullable ScrollView scrollView) { + this.requireScrollMixin = requireScrollMixin; + if (scrollView instanceof BottomScrollView) { + this.scrollView = (BottomScrollView) scrollView; + } else { + Log.w(TAG, "Cannot set non-BottomScrollView. Found=" + scrollView); + this.scrollView = null; } + } - @Override - public void onScrolledToBottom() { - mRequireScrollMixin.notifyScrollabilityChange(false); - } + @Override + public void onScrolledToBottom() { + requireScrollMixin.notifyScrollabilityChange(false); + } - @Override - public void onRequiresScroll() { - mRequireScrollMixin.notifyScrollabilityChange(true); - } + @Override + public void onRequiresScroll() { + requireScrollMixin.notifyScrollabilityChange(true); + } - @Override - public void startListening() { - if (mScrollView != null) { - mScrollView.setBottomScrollListener(this); - } else { - Log.w(TAG, "Cannot require scroll. Scroll view is null."); - } + @Override + public void startListening() { + if (scrollView != null) { + scrollView.setBottomScrollListener(this); + } else { + Log.w(TAG, "Cannot require scroll. Scroll view is null."); } + } - @Override - public void pageScrollDown() { - if (mScrollView != null) { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - } + @Override + public void pageScrollDown() { + if (scrollView != null) { + scrollView.pageScroll(ScrollView.FOCUS_DOWN); } + } } diff --git a/library/main/src/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java b/library/main/src/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java index b0afaba..8214415 100644 --- a/library/main/src/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java +++ b/library/main/src/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java @@ -23,59 +23,77 @@ import android.graphics.drawable.InsetDrawable; import android.os.Build; import android.view.View; -/** - * Provides convenience methods to handle drawable layout directions in different SDK versions. - */ +/** Provides convenience methods to handle drawable layout directions in different SDK versions. */ public class DrawableLayoutDirectionHelper { - /** - * Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction - * of {@code view}. - */ - @SuppressLint("InlinedApi") // Use of View.LAYOUT_DIRECTION_RTL is guarded by version check - public static InsetDrawable createRelativeInsetDrawable(Drawable drawable, - int insetStart, int insetTop, int insetEnd, int insetBottom, View view) { - boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 - && view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom, - isRtl); - } + /** + * Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction of + * {@code view}. + */ + @SuppressLint("InlinedApi") // Use of View.LAYOUT_DIRECTION_RTL is guarded by version check + public static InsetDrawable createRelativeInsetDrawable( + Drawable drawable, int insetStart, int insetTop, int insetEnd, int insetBottom, View view) { + boolean isRtl = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 + && view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + return createRelativeInsetDrawable( + drawable, insetStart, insetTop, insetEnd, insetBottom, isRtl); + } - /** - * Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction - * of {@code context}. - */ - @SuppressLint("InlinedApi") // Use of View.LAYOUT_DIRECTION_RTL is guarded by version check - public static InsetDrawable createRelativeInsetDrawable(Drawable drawable, - int insetStart, int insetTop, int insetEnd, int insetBottom, Context context) { - boolean isRtl = false; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - final int layoutDirection = - context.getResources().getConfiguration().getLayoutDirection(); - isRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL; - } - return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom, - isRtl); + /** + * Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction of + * {@code context}. + */ + @SuppressLint("InlinedApi") // Use of View.LAYOUT_DIRECTION_RTL is guarded by version check + public static InsetDrawable createRelativeInsetDrawable( + Drawable drawable, + int insetStart, + int insetTop, + int insetEnd, + int insetBottom, + Context context) { + boolean isRtl = false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + final int layoutDirection = context.getResources().getConfiguration().getLayoutDirection(); + isRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL; } + return createRelativeInsetDrawable( + drawable, insetStart, insetTop, insetEnd, insetBottom, isRtl); + } - /** - * Creates an {@link android.graphics.drawable.InsetDrawable} according to - * {@code layoutDirection}. - */ - @SuppressLint("InlinedApi") // Given layoutDirection will not be View.LAYOUT_DIRECTION_RTL if - // SDK version doesn't support it. - public static InsetDrawable createRelativeInsetDrawable(Drawable drawable, - int insetStart, int insetTop, int insetEnd, int insetBottom, int layoutDirection) { - return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom, - layoutDirection == View.LAYOUT_DIRECTION_RTL); - } + /** + * Creates an {@link android.graphics.drawable.InsetDrawable} according to {@code + * layoutDirection}. + */ + @SuppressLint("InlinedApi") // Given layoutDirection will not be View.LAYOUT_DIRECTION_RTL if + // SDK version doesn't support it. + public static InsetDrawable createRelativeInsetDrawable( + Drawable drawable, + int insetStart, + int insetTop, + int insetEnd, + int insetBottom, + int layoutDirection) { + return createRelativeInsetDrawable( + drawable, + insetStart, + insetTop, + insetEnd, + insetBottom, + layoutDirection == View.LAYOUT_DIRECTION_RTL); + } - private static InsetDrawable createRelativeInsetDrawable(Drawable drawable, - int insetStart, int insetTop, int insetEnd, int insetBottom, boolean isRtl) { - if (isRtl) { - return new InsetDrawable(drawable, insetEnd, insetTop, insetStart, insetBottom); - } else { - return new InsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom); - } + private static InsetDrawable createRelativeInsetDrawable( + Drawable drawable, + int insetStart, + int insetTop, + int insetEnd, + int insetBottom, + boolean isRtl) { + if (isRtl) { + return new InsetDrawable(drawable, insetEnd, insetTop, insetStart, insetBottom); + } else { + return new InsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom); } + } } diff --git a/library/main/src/com/android/setupwizardlib/util/FallbackThemeWrapper.java b/library/main/src/com/android/setupwizardlib/util/FallbackThemeWrapper.java index 2ec4f84..96f9162 100644 --- a/library/main/src/com/android/setupwizardlib/util/FallbackThemeWrapper.java +++ b/library/main/src/com/android/setupwizardlib/util/FallbackThemeWrapper.java @@ -18,37 +18,33 @@ package com.android.setupwizardlib.util; import android.content.Context; import android.content.res.Resources.Theme; -import android.view.ContextThemeWrapper; - import androidx.annotation.StyleRes; +import android.view.ContextThemeWrapper; /** - * Same as {@link ContextThemeWrapper}, but the base context's theme attributes take precedence - * over the wrapper context's. This is used to provide default values for theme attributes - * referenced in layouts, to remove the risk of crashing the client because of using the wrong - * theme. + * Same as {@link ContextThemeWrapper}, but the base context's theme attributes take precedence over + * the wrapper context's. This is used to provide default values for theme attributes referenced in + * layouts, to remove the risk of crashing the client because of using the wrong theme. */ public class FallbackThemeWrapper extends ContextThemeWrapper { - /** - * Creates a new context wrapper with the specified theme. - * - * The specified theme will be applied as fallbacks to the base context's theme. Any attributes - * defined in the base context's theme will retain their original values. Otherwise values in - * {@code themeResId} will be used. - * - * @param base The base context. - * @param themeResId The theme to use as fallback. - */ - public FallbackThemeWrapper(Context base, @StyleRes int themeResId) { - super(base, themeResId); - } + /** + * Creates a new context wrapper with the specified theme. + * + *

The specified theme will be applied as fallbacks to the base context's theme. Any attributes + * defined in the base context's theme will retain their original values. Otherwise values in + * {@code themeResId} will be used. + * + * @param base The base context. + * @param themeResId The theme to use as fallback. + */ + public FallbackThemeWrapper(Context base, @StyleRes int themeResId) { + super(base, themeResId); + } - /** - * {@inheritDoc} - */ - @Override - protected void onApplyThemeResource(Theme theme, int resId, boolean first) { - theme.applyStyle(resId, false /* force */); - } + /** {@inheritDoc} */ + @Override + protected void onApplyThemeResource(Theme theme, int resId, boolean first) { + theme.applyStyle(resId, false /* force */); + } } diff --git a/library/main/src/com/android/setupwizardlib/util/Partner.java b/library/main/src/com/android/setupwizardlib/util/Partner.java index 9eaedc3..ce782cb 100644 --- a/library/main/src/com/android/setupwizardlib/util/Partner.java +++ b/library/main/src/com/android/setupwizardlib/util/Partner.java @@ -26,14 +26,13 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.util.Log; - import androidx.annotation.AnyRes; import androidx.annotation.ColorRes; import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; - +import android.util.Log; import java.util.List; /** @@ -46,157 +45,156 @@ import java.util.List; */ public class Partner { - private static final String TAG = "(SUW) Partner"; - - /** Marker action used to discover partner */ - private static final String ACTION_PARTNER_CUSTOMIZATION = - "com.android.setupwizard.action.PARTNER_CUSTOMIZATION"; - - private static boolean sSearched = false; - private static Partner sPartner; - - /** - * Convenience to get a drawable from partner overlay, or if not available, the drawable from - * the original context. - * - * @see #getResourceEntry(android.content.Context, int) - */ - public static Drawable getDrawable(Context context, @DrawableRes int id) { - final ResourceEntry entry = getResourceEntry(context, id); - return entry.resources.getDrawable(entry.id); + private static final String TAG = "(SUW) Partner"; + + /** Marker action used to discover partner. */ + private static final String ACTION_PARTNER_CUSTOMIZATION = + "com.android.setupwizard.action.PARTNER_CUSTOMIZATION"; + + private static boolean searched = false; + @Nullable private static Partner partner; + + /** + * Gets a drawable from partner overlay, or if not available, the drawable from the original + * context. + * + * @see #getResourceEntry(android.content.Context, int) + */ + public static Drawable getDrawable(Context context, @DrawableRes int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getDrawable(entry.id); + } + + /** + * Gets a string from partner overlay, or if not available, the string from the original context. + * + * @see #getResourceEntry(android.content.Context, int) + */ + public static String getString(Context context, @StringRes int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getString(entry.id); + } + + /** + * Gets a color from partner overlay, or if not available, the color from the original context. + */ + public static int getColor(Context context, @ColorRes int id) { + final ResourceEntry resourceEntry = getResourceEntry(context, id); + return resourceEntry.resources.getColor(resourceEntry.id); + } + + /** + * Gets a CharSequence from partner overlay, or if not available, the text from the original + * context. + */ + public static CharSequence getText(Context context, @StringRes int id) { + final ResourceEntry entry = getResourceEntry(context, id); + return entry.resources.getText(entry.id); + } + + /** + * Finds an entry of resource in the overlay package provided by partners. It will first look for + * the resource in the overlay package, and if not available, will return the one in the original + * context. + * + * @return a ResourceEntry in the partner overlay's resources, if one is defined. Otherwise the + * resources from the original context is returned. Clients can then get the resource by + * {@code entry.resources.getString(entry.id)}, or other methods available in {@link + * android.content.res.Resources}. + */ + public static ResourceEntry getResourceEntry(Context context, @AnyRes int id) { + final Partner partner = Partner.get(context); + if (partner != null) { + final Resources ourResources = context.getResources(); + final String name = ourResources.getResourceEntryName(id); + final String type = ourResources.getResourceTypeName(id); + final int partnerId = partner.getIdentifier(name, type); + if (partnerId != 0) { + return new ResourceEntry(partner.resources, partnerId, true); + } } - - /** - * Convenience to get a string from partner overlay, or if not available, the string from the - * original context. - * - * @see #getResourceEntry(android.content.Context, int) - */ - public static String getString(Context context, @StringRes int id) { - final ResourceEntry entry = getResourceEntry(context, id); - return entry.resources.getString(entry.id); - } - - /** - * Convenience method to get color from partner overlay, or if not available, the color from - * the original context. - */ - public static int getColor(Context context, @ColorRes int id) { - final ResourceEntry resourceEntry = getResourceEntry(context, id); - return resourceEntry.resources.getColor(resourceEntry.id); - } - - /** - * Convenience method to get a CharSequence from partner overlay, or if not available, the text - * from the original context. - */ - public static CharSequence getText(Context context, @StringRes int id) { - final ResourceEntry entry = getResourceEntry(context, id); - return entry.resources.getText(entry.id); - } - - /** - * Find an entry of resource in the overlay package provided by partners. It will first look for - * the resource in the overlay package, and if not available, will return the one in the - * original context. - * - * @return a ResourceEntry in the partner overlay's resources, if one is defined. Otherwise the - * resources from the original context is returned. Clients can then get the resource by - * {@code entry.resources.getString(entry.id)}, or other methods available in - * {@link android.content.res.Resources}. - */ - public static ResourceEntry getResourceEntry(Context context, @AnyRes int id) { - final Partner partner = Partner.get(context); - if (partner != null) { - final Resources ourResources = context.getResources(); - final String name = ourResources.getResourceEntryName(id); - final String type = ourResources.getResourceTypeName(id); - final int partnerId = partner.getIdentifier(name, type); - if (partnerId != 0) { - return new ResourceEntry(partner.mResources, partnerId, true); - } - } - return new ResourceEntry(context.getResources(), id, false); + return new ResourceEntry(context.getResources(), id, false); + } + + public static class ResourceEntry { + public Resources resources; + public int id; + public boolean isOverlay; + + ResourceEntry(Resources resources, int id, boolean isOverlay) { + this.resources = resources; + this.id = id; + this.isOverlay = isOverlay; } - - public static class ResourceEntry { - public Resources resources; - public int id; - public boolean isOverlay; - - ResourceEntry(Resources resources, int id, boolean isOverlay) { - this.resources = resources; - this.id = id; - this.isOverlay = isOverlay; + } + + /** + * Finds and returns partner details, or {@code null} if none exists. A partner package is marked + * by a broadcast receiver declared in the manifest that handles the {@code + * com.android.setupwizard.action.PARTNER_CUSTOMIZATION} intent action. The overlay package must + * also be a system package. + */ + public static synchronized Partner get(Context context) { + if (!searched) { + PackageManager pm = context.getPackageManager(); + final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION); + List receivers; + if (VERSION.SDK_INT >= VERSION_CODES.N) { + receivers = + pm.queryBroadcastReceivers( + intent, + PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + } else { + // On versions before N, direct boot doesn't exist. And the MATCH_SYSTEM_ONLY flag + // doesn't exist so we filter for system apps in code below. + receivers = pm.queryBroadcastReceivers(intent, 0); + } + + for (ResolveInfo info : receivers) { + if (info.activityInfo == null) { + continue; } - } - - /** - * Find and return partner details, or {@code null} if none exists. A partner package is marked - * by a broadcast receiver declared in the manifest that handles the - * {@code com.android.setupwizard.action.PARTNER_CUSTOMIZATION} intent action. The overlay - * package must also be a system package. - */ - public static synchronized Partner get(Context context) { - if (!sSearched) { - PackageManager pm = context.getPackageManager(); - final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION); - List receivers; - if (VERSION.SDK_INT >= VERSION_CODES.N) { - receivers = pm.queryBroadcastReceivers( - intent, - PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - } else { - // On versions before N, direct boot doesn't exist. And the MATCH_SYSTEM_ONLY flag - // doesn't exist so we filter for system apps in code below. - receivers = pm.queryBroadcastReceivers(intent, 0); - } - - for (ResolveInfo info : receivers) { - if (info.activityInfo == null) { - continue; - } - final ApplicationInfo appInfo = info.activityInfo.applicationInfo; - if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - try { - final Resources res = pm.getResourcesForApplication(appInfo); - sPartner = new Partner(appInfo.packageName, res); - break; - } catch (NameNotFoundException e) { - Log.w(TAG, "Failed to find resources for " + appInfo.packageName); - } - } - } - sSearched = true; + final ApplicationInfo appInfo = info.activityInfo.applicationInfo; + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + try { + final Resources res = pm.getResourcesForApplication(appInfo); + partner = new Partner(appInfo.packageName, res); + break; + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + appInfo.packageName); + } } - return sPartner; - } - - @VisibleForTesting - public static synchronized void resetForTesting() { - sSearched = false; - sPartner = null; - } - - private final String mPackageName; - private final Resources mResources; - - private Partner(String packageName, Resources res) { - mPackageName = packageName; - mResources = res; - } - - public String getPackageName() { - return mPackageName; - } - - public Resources getResources() { - return mResources; - } - - public int getIdentifier(String name, String defType) { - return mResources.getIdentifier(name, defType, mPackageName); + } + searched = true; } + return partner; + } + + @VisibleForTesting + public static synchronized void resetForTesting() { + searched = false; + partner = null; + } + + private final String packageName; + private final Resources resources; + + private Partner(String packageName, Resources res) { + this.packageName = packageName; + resources = res; + } + + public String getPackageName() { + return packageName; + } + + public Resources getResources() { + return resources; + } + + public int getIdentifier(String name, String defType) { + return resources.getIdentifier(name, defType, packageName); + } } diff --git a/library/main/src/com/android/setupwizardlib/util/ResultCodes.java b/library/main/src/com/android/setupwizardlib/util/ResultCodes.java index a429e73..ea20139 100644 --- a/library/main/src/com/android/setupwizardlib/util/ResultCodes.java +++ b/library/main/src/com/android/setupwizardlib/util/ResultCodes.java @@ -20,9 +20,9 @@ import static android.app.Activity.RESULT_FIRST_USER; public final class ResultCodes { - public static final int RESULT_SKIP = RESULT_FIRST_USER; - public static final int RESULT_RETRY = RESULT_FIRST_USER + 1; - public static final int RESULT_ACTIVITY_NOT_FOUND = RESULT_FIRST_USER + 2; + public static final int RESULT_SKIP = RESULT_FIRST_USER; + public static final int RESULT_RETRY = RESULT_FIRST_USER + 1; + public static final int RESULT_ACTIVITY_NOT_FOUND = RESULT_FIRST_USER + 2; - public static final int RESULT_FIRST_SETUP_USER = RESULT_FIRST_USER + 100; + public static final int RESULT_FIRST_SETUP_USER = RESULT_FIRST_USER + 100; } diff --git a/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java b/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java index 7e3e885..42350cc 100644 --- a/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java +++ b/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java @@ -24,6 +24,7 @@ import android.content.res.TypedArray; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Handler; +import androidx.annotation.RequiresPermission; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -31,331 +32,327 @@ import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; -import androidx.annotation.RequiresPermission; - /** * A helper class to manage the system navigation bar and status bar. This will add various * systemUiVisibility flags to the given Window or View to make them follow the Setup Wizard style. * - * When the useImmersiveMode intent extra is true, a screen in Setup Wizard should hide the system - * bars using methods from this class. For Lollipop, {@link #hideSystemBars(android.view.Window)} - * will completely hide the system navigation bar and change the status bar to transparent, and - * layout the screen contents (usually the illustration) behind it. + *

When the useImmersiveMode intent extra is true, a screen in Setup Wizard should hide the + * system bars using methods from this class. For Lollipop, {@link + * #hideSystemBars(android.view.Window)} will completely hide the system navigation bar and change + * the status bar to transparent, and layout the screen contents (usually the illustration) behind + * it. */ public class SystemBarHelper { - private static final String TAG = "SystemBarHelper"; - - @SuppressLint("InlinedApi") - private static final int DEFAULT_IMMERSIVE_FLAGS = - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - - @SuppressLint("InlinedApi") - private static final int DIALOG_IMMERSIVE_FLAGS = - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - - /** - * Needs to be equal to View.STATUS_BAR_DISABLE_BACK - */ - private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; - - /** - * The maximum number of retries when peeking the decor view. When polling for the decor view, - * waiting it to be installed, set a maximum number of retries. - */ - private static final int PEEK_DECOR_VIEW_RETRIES = 3; - - /** - * Hide the navigation bar for a dialog. - * - *

This will only take effect in versions Lollipop or above. Otherwise this is a no-op. - */ - public static void hideSystemBars(final Dialog dialog) { - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - final Window window = dialog.getWindow(); - temporarilyDisableDialogFocus(window); - addVisibilityFlag(window, DIALOG_IMMERSIVE_FLAGS); - addImmersiveFlagsToDecorView(window, DIALOG_IMMERSIVE_FLAGS); - - // Also set the navigation bar and status bar to transparent color. Note that this - // doesn't work if android.R.boolean.config_enableTranslucentDecor is false. - window.setNavigationBarColor(0); - window.setStatusBarColor(0); - } + private static final String TAG = "SystemBarHelper"; + + @SuppressLint("InlinedApi") + private static final int DEFAULT_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + + @SuppressLint("InlinedApi") + private static final int DIALOG_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + /** Needs to be equal to View.STATUS_BAR_DISABLE_BACK */ + private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; + + /** + * The maximum number of retries when peeking the decor view. When polling for the decor view, + * waiting it to be installed, set a maximum number of retries. + */ + private static final int PEEK_DECOR_VIEW_RETRIES = 3; + + /** + * Hide the navigation bar for a dialog. + * + *

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

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

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

IMPORTANT: Do not assume that users have no way to go back when the back button is hidden. + * Many devices have physical back buttons, and accessibility services like TalkBack may have + * gestures mapped to back. Please use onBackPressed, onKeyDown, or other similar ways to make + * sure back button events are still handled (or ignored) properly. + */ + @RequiresPermission("android.permission.STATUS_BAR") + public static void setBackButtonVisible(final Window window, final boolean visible) { + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + if (visible) { + removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + removeImmersiveFlagsFromDecorView(window, STATUS_BAR_DISABLE_BACK); + } else { + addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + addImmersiveFlagsToDecorView(window, STATUS_BAR_DISABLE_BACK); + } } - - /** - * Sets whether the back button on the software navigation bar is visible. This only works if - * you have the STATUS_BAR permission. Otherwise framework will filter out this flag and this - * method call will not have any effect. - * - *

IMPORTANT: Do not assume that users have no way to go back when the back button is hidden. - * Many devices have physical back buttons, and accessibility services like TalkBack may have - * gestures mapped to back. Please use onBackPressed, onKeyDown, or other similar ways to - * make sure back button events are still handled (or ignored) properly. - */ - @RequiresPermission("android.permission.STATUS_BAR") - public static void setBackButtonVisible(final Window window, final boolean visible) { - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - if (visible) { - removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); - removeImmersiveFlagsFromDecorView(window, STATUS_BAR_DISABLE_BACK); - } else { - addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); - addImmersiveFlagsToDecorView(window, STATUS_BAR_DISABLE_BACK); - } - } + } + + /** + * Set a view to be resized when the keyboard is shown. This will set the bottom margin of the + * view to be immediately above the keyboard, and assumes that the view sits immediately above the + * navigation bar. + * + *

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

This will only take effect in versions Lollipop or above. Otherwise this is a no-op. + * + * @param view The view to be resized when the keyboard is shown. + */ + public static void setImeInsetView(final View view) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + view.setOnApplyWindowInsetsListener(new WindowInsetsListener()); } - - /** - * Set a view to be resized when the keyboard is shown. This will set the bottom margin of the - * view to be immediately above the keyboard, and assumes that the view sits immediately above - * the navigation bar. - * - *

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

This will only take effect in versions Lollipop or above. Otherwise this is a no-op. - * - * @param view The view to be resized when the keyboard is shown. - */ - public static void setImeInsetView(final View view) { - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - view.setOnApplyWindowInsetsListener(new WindowInsetsListener()); - } - } - - /** - * Add the specified immersive flags to the decor view of the window, because - * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} only takes effect when it is added to a view - * instead of the window. - */ - @TargetApi(VERSION_CODES.HONEYCOMB) - private static void addImmersiveFlagsToDecorView(final Window window, final int vis) { - getDecorView(window, new OnDecorViewInstalledListener() { - @Override - public void onDecorViewInstalled(View decorView) { - addVisibilityFlag(decorView, vis); - } + } + + /** + * Add the specified immersive flags to the decor view of the window, because {@link + * View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} only takes effect when it is added to a view instead of + * the window. + */ + @TargetApi(VERSION_CODES.HONEYCOMB) + private static void addImmersiveFlagsToDecorView(final Window window, final int vis) { + getDecorView( + window, + new OnDecorViewInstalledListener() { + @Override + public void onDecorViewInstalled(View decorView) { + addVisibilityFlag(decorView, vis); + } }); - } - - @TargetApi(VERSION_CODES.HONEYCOMB) - private static void removeImmersiveFlagsFromDecorView(final Window window, final int vis) { - getDecorView(window, new OnDecorViewInstalledListener() { - @Override - public void onDecorViewInstalled(View decorView) { - removeVisibilityFlag(decorView, vis); - } + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + private static void removeImmersiveFlagsFromDecorView(final Window window, final int vis) { + getDecorView( + window, + new OnDecorViewInstalledListener() { + @Override + public void onDecorViewInstalled(View decorView) { + removeVisibilityFlag(decorView, vis); + } }); - } - - private static void getDecorView(Window window, OnDecorViewInstalledListener callback) { - new DecorViewFinder().getDecorView(window, callback, PEEK_DECOR_VIEW_RETRIES); - } - - private static class DecorViewFinder { - - private final Handler mHandler = new Handler(); - private Window mWindow; - private int mRetries; - private OnDecorViewInstalledListener mCallback; - - private Runnable mCheckDecorViewRunnable = new Runnable() { - @Override - public void run() { - // Use peekDecorView instead of getDecorView so that clients can still set window - // features after calling this method. - final View decorView = mWindow.peekDecorView(); - if (decorView != null) { - mCallback.onDecorViewInstalled(decorView); - } else { - mRetries--; - if (mRetries >= 0) { - // If the decor view is not installed yet, try again in the next loop. - mHandler.post(mCheckDecorViewRunnable); - } else { - Log.w(TAG, "Cannot get decor view of window: " + mWindow); - } - } + } + + private static void getDecorView(Window window, OnDecorViewInstalledListener callback) { + new DecorViewFinder().getDecorView(window, callback, PEEK_DECOR_VIEW_RETRIES); + } + + private static class DecorViewFinder { + + private final Handler handler = new Handler(); + private Window window; + private int retries; + private OnDecorViewInstalledListener callback; + + private final Runnable checkDecorViewRunnable = + new Runnable() { + @Override + public void run() { + // Use peekDecorView instead of getDecorView so that clients can still set window + // features after calling this method. + final View decorView = window.peekDecorView(); + if (decorView != null) { + callback.onDecorViewInstalled(decorView); + } else { + retries--; + if (retries >= 0) { + // If the decor view is not installed yet, try again in the next loop. + handler.post(checkDecorViewRunnable); + } else { + Log.w(TAG, "Cannot get decor view of window: " + window); + } } + } }; - public void getDecorView(Window window, OnDecorViewInstalledListener callback, - int retries) { - mWindow = window; - mRetries = retries; - mCallback = callback; - mCheckDecorViewRunnable.run(); - } + public void getDecorView(Window window, OnDecorViewInstalledListener callback, int retries) { + this.window = window; + this.retries = retries; + this.callback = callback; + checkDecorViewRunnable.run(); } - - private interface OnDecorViewInstalledListener { - - void onDecorViewInstalled(View decorView); - } - - /** - * Apply a hack to temporarily set the window to not focusable, so that the navigation bar - * will not show up during the transition. - */ - private static void temporarilyDisableDialogFocus(final Window window) { - window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - // Add the SOFT_INPUT_IS_FORWARD_NAVIGATION_FLAG. This is normally done by the system when - // FLAG_NOT_FOCUSABLE is not set. Setting this flag allows IME to be shown automatically - // if the dialog has editable text fields. - window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION); - new Handler().post(new Runnable() { - @Override - public void run() { + } + + private interface OnDecorViewInstalledListener { + + void onDecorViewInstalled(View decorView); + } + + /** + * Apply a hack to temporarily set the window to not focusable, so that the navigation bar will + * not show up during the transition. + */ + private static void temporarilyDisableDialogFocus(final Window window) { + window.setFlags( + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + // Add the SOFT_INPUT_IS_FORWARD_NAVIGATION_FLAG. This is normally done by the system when + // FLAG_NOT_FOCUSABLE is not set. Setting this flag allows IME to be shown automatically + // if the dialog has editable text fields. + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION); + new Handler() + .post( + new Runnable() { + @Override + public void run() { window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - } - }); + } + }); + } + + @TargetApi(VERSION_CODES.LOLLIPOP) + private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener { + private int bottomOffset; + private boolean hasCalculatedBottomOffset = false; + + @Override + public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { + if (!hasCalculatedBottomOffset) { + bottomOffset = getBottomDistance(view); + hasCalculatedBottomOffset = true; + } + + int bottomInset = insets.getSystemWindowInsetBottom(); + + final int bottomMargin = Math.max(insets.getSystemWindowInsetBottom() - bottomOffset, 0); + + final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + // Check that we have enough space to apply the bottom margins before applying it. + // Otherwise the framework may think that the view is empty and exclude it from layout. + if (bottomMargin < lp.bottomMargin + view.getHeight()) { + lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin); + view.setLayoutParams(lp); + bottomInset = 0; + } + + return insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), + bottomInset); } + } - @TargetApi(VERSION_CODES.LOLLIPOP) - private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener { - private int mBottomOffset; - private boolean mHasCalculatedBottomOffset = false; - - @Override - public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { - if (!mHasCalculatedBottomOffset) { - mBottomOffset = getBottomDistance(view); - mHasCalculatedBottomOffset = true; - } - - int bottomInset = insets.getSystemWindowInsetBottom(); - - final int bottomMargin = Math.max( - insets.getSystemWindowInsetBottom() - mBottomOffset, 0); - - final ViewGroup.MarginLayoutParams lp = - (ViewGroup.MarginLayoutParams) view.getLayoutParams(); - // Check that we have enough space to apply the bottom margins before applying it. - // Otherwise the framework may think that the view is empty and exclude it from layout. - if (bottomMargin < lp.bottomMargin + view.getHeight()) { - lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin); - view.setLayoutParams(lp); - bottomInset = 0; - } - - - return insets.replaceSystemWindowInsets( - insets.getSystemWindowInsetLeft(), - insets.getSystemWindowInsetTop(), - insets.getSystemWindowInsetRight(), - bottomInset - ); - } - } - - private static int getBottomDistance(View view) { - int[] coords = new int[2]; - view.getLocationInWindow(coords); - return view.getRootView().getHeight() - coords[1] - view.getHeight(); - } + private static int getBottomDistance(View view) { + int[] coords = new int[2]; + view.getLocationInWindow(coords); + return view.getRootView().getHeight() - coords[1] - view.getHeight(); + } } diff --git a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java index 9230b67..c946bb6 100644 --- a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java +++ b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java @@ -22,390 +22,385 @@ import android.content.res.Resources.Theme; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.Settings; - import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.annotation.VisibleForTesting; - import com.android.setupwizardlib.R; - import java.util.Arrays; public class WizardManagerHelper { - private static final String ACTION_NEXT = "com.android.wizard.NEXT"; - - // EXTRA_SCRIPT_URI and EXTRA_ACTION_ID are used in setup wizard in versions before M and are - // kept for backwards compatibility. - @VisibleForTesting - static final String EXTRA_SCRIPT_URI = "scriptUri"; - @VisibleForTesting - static final String EXTRA_ACTION_ID = "actionId"; - - @VisibleForTesting - static final String EXTRA_WIZARD_BUNDLE = "wizardBundle"; - private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode"; - @VisibleForTesting - static final String EXTRA_IS_FIRST_RUN = "firstRun"; - @VisibleForTesting - static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup"; - @VisibleForTesting - static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup"; - - public static final String EXTRA_THEME = "theme"; - public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode"; - - public static final String SETTINGS_GLOBAL_DEVICE_PROVISIONED = "device_provisioned"; - public static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; - - public static final String THEME_HOLO = "holo"; - public static final String THEME_HOLO_LIGHT = "holo_light"; - public static final String THEME_MATERIAL = "material"; - public static final String THEME_MATERIAL_LIGHT = "material_light"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the - * theme used in setup wizard for Nougat MR1. - */ - public static final String THEME_GLIF = "glif"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in - * setup wizard for Nougat MR1. - */ - public static final String THEME_GLIF_LIGHT = "glif_light"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the - * theme used in setup wizard for O DR. - */ - public static final String THEME_GLIF_V2 = "glif_v2"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in - * setup wizard for O DR. - */ - public static final String THEME_GLIF_V2_LIGHT = "glif_v2_light"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the - * theme used in setup wizard for P. - */ - public static final String THEME_GLIF_V3 = "glif_v3"; - - /** - * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in - * setup wizard for P. - */ - public static final String THEME_GLIF_V3_LIGHT = "glif_v3_light"; - - /** - * Get an intent that will invoke the next step of setup wizard. - * - * @param originalIntent The original intent that was used to start the step, usually via - * {@link android.app.Activity#getIntent()}. - * @param resultCode The result code of the step. See {@link ResultCodes}. - * @return A new intent that can be used with - * {@link android.app.Activity#startActivityForResult(Intent, int)} to start the next - * step of the setup flow. - */ - public static Intent getNextIntent(Intent originalIntent, int resultCode) { - return getNextIntent(originalIntent, resultCode, null); - } - - /** - * Get an intent that will invoke the next step of setup wizard. - * - * @param originalIntent The original intent that was used to start the step, usually via - * {@link android.app.Activity#getIntent()}. - * @param resultCode The result code of the step. See {@link ResultCodes}. - * @param data An intent containing extra result data. - * @return A new intent that can be used with - * {@link android.app.Activity#startActivityForResult(Intent, int)} to start the next - * step of the setup flow. - */ - public static Intent getNextIntent(Intent originalIntent, int resultCode, Intent data) { - Intent intent = new Intent(ACTION_NEXT); - copyWizardManagerExtras(originalIntent, intent); - intent.putExtra(EXTRA_RESULT_CODE, resultCode); - if (data != null && data.getExtras() != null) { - intent.putExtras(data.getExtras()); - } - intent.putExtra(EXTRA_THEME, originalIntent.getStringExtra(EXTRA_THEME)); - - return intent; - } - - /** - * Copy the internal extras used by setup wizard from one intent to another. For low-level use - * only, such as when using {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT} to relay to another - * intent. - * - * @param srcIntent Intent to get the wizard manager extras from. - * @param dstIntent Intent to copy the wizard manager extras to. - */ - public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) { - dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE)); - for (String key : Arrays.asList( - EXTRA_IS_FIRST_RUN, EXTRA_IS_DEFERRED_SETUP, EXTRA_IS_PRE_DEFERRED_SETUP)) { - dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false)); - } - - for (String key : Arrays.asList(EXTRA_THEME, EXTRA_SCRIPT_URI, EXTRA_ACTION_ID)) { - dstIntent.putExtra(key, srcIntent.getStringExtra(key)); - } - } - - /** - * Check whether an intent is intended to be used within the setup wizard flow. - * - * @param intent The intent to be checked, usually from - * {@link android.app.Activity#getIntent()}. - * @return true if the intent passed in was intended to be used with setup wizard. - */ - public static boolean isSetupWizardIntent(Intent intent) { - return intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false); - } - - /** - * Checks whether the current user has completed Setup Wizard. This is true if the current user - * has gone through Setup Wizard. The current user may or may not be the device owner and the - * device owner may have already completed setup wizard. - * - * @param context The context to retrieve the settings. - * @return true if the current user has completed Setup Wizard. - * @see #isDeviceProvisioned(android.content.Context) - */ - public static boolean isUserSetupComplete(Context context) { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - return Settings.Secure.getInt(context.getContentResolver(), - SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; - } else { - // For versions below JB MR1, there are no user profiles. Just return the global device - // provisioned state. - return Settings.Secure.getInt(context.getContentResolver(), - SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1; - } + private static final String ACTION_NEXT = "com.android.wizard.NEXT"; + + // EXTRA_SCRIPT_URI and EXTRA_ACTION_ID are used in setup wizard in versions before M and are + // kept for backwards compatibility. + @VisibleForTesting static final String EXTRA_SCRIPT_URI = "scriptUri"; + @VisibleForTesting static final String EXTRA_ACTION_ID = "actionId"; + + @VisibleForTesting static final String EXTRA_WIZARD_BUNDLE = "wizardBundle"; + private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode"; + @VisibleForTesting static final String EXTRA_IS_FIRST_RUN = "firstRun"; + @VisibleForTesting static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup"; + @VisibleForTesting static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup"; + + public static final String EXTRA_THEME = "theme"; + public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode"; + + public static final String SETTINGS_GLOBAL_DEVICE_PROVISIONED = "device_provisioned"; + public static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; + + public static final String THEME_HOLO = "holo"; + public static final String THEME_HOLO_LIGHT = "holo_light"; + public static final String THEME_MATERIAL = "material"; + public static final String THEME_MATERIAL_LIGHT = "material_light"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme + * used in setup wizard for Nougat MR1. + */ + public static final String THEME_GLIF = "glif"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in + * setup wizard for Nougat MR1. + */ + public static final String THEME_GLIF_LIGHT = "glif_light"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme + * used in setup wizard for O DR. + */ + public static final String THEME_GLIF_V2 = "glif_v2"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in + * setup wizard for O DR. + */ + public static final String THEME_GLIF_V2_LIGHT = "glif_v2_light"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme + * used in setup wizard for P. + */ + public static final String THEME_GLIF_V3 = "glif_v3"; + + /** + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in + * setup wizard for P. + */ + public static final String THEME_GLIF_V3_LIGHT = "glif_v3_light"; + + /** + * Get an intent that will invoke the next step of setup wizard. + * + * @param originalIntent The original intent that was used to start the step, usually via {@link + * android.app.Activity#getIntent()}. + * @param resultCode The result code of the step. See {@link ResultCodes}. + * @return A new intent that can be used with {@link + * android.app.Activity#startActivityForResult(Intent, int)} to start the next step of the + * setup flow. + */ + public static Intent getNextIntent(Intent originalIntent, int resultCode) { + return getNextIntent(originalIntent, resultCode, null); + } + + /** + * Get an intent that will invoke the next step of setup wizard. + * + * @param originalIntent The original intent that was used to start the step, usually via {@link + * android.app.Activity#getIntent()}. + * @param resultCode The result code of the step. See {@link ResultCodes}. + * @param data An intent containing extra result data. + * @return A new intent that can be used with {@link + * android.app.Activity#startActivityForResult(Intent, int)} to start the next step of the + * setup flow. + */ + public static Intent getNextIntent(Intent originalIntent, int resultCode, Intent data) { + Intent intent = new Intent(ACTION_NEXT); + copyWizardManagerExtras(originalIntent, intent); + intent.putExtra(EXTRA_RESULT_CODE, resultCode); + if (data != null && data.getExtras() != null) { + intent.putExtras(data.getExtras()); } - - /** - * Checks whether the device is provisioned. This means that the device has gone through Setup - * Wizard at least once. Note that the user can still be in Setup Wizard even if this is true, - * for a secondary user profile triggered through Settings > Add account. - * - * @param context The context to retrieve the settings. - * @return true if the device is provisioned. - * @see #isUserSetupComplete(android.content.Context) - */ - public static boolean isDeviceProvisioned(Context context) { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - return Settings.Global.getInt(context.getContentResolver(), - SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1; - } else { - return Settings.Secure.getInt(context.getContentResolver(), - SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1; - } - } - - /** - * Checks whether an intent is running in the deferred setup wizard flow. - * - * @param originalIntent The original intent that was used to start the step, usually via - * {@link android.app.Activity#getIntent()}. - * @return true if the intent passed in was running in deferred setup wizard. - */ - public static boolean isDeferredSetupWizard(Intent originalIntent) { - return originalIntent != null - && originalIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false); + intent.putExtra(EXTRA_THEME, originalIntent.getStringExtra(EXTRA_THEME)); + + return intent; + } + + /** + * Copy the internal extras used by setup wizard from one intent to another. For low-level use + * only, such as when using {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT} to relay to another + * intent. + * + * @param srcIntent Intent to get the wizard manager extras from. + * @param dstIntent Intent to copy the wizard manager extras to. + */ + public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) { + dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE)); + for (String key : + Arrays.asList(EXTRA_IS_FIRST_RUN, EXTRA_IS_DEFERRED_SETUP, EXTRA_IS_PRE_DEFERRED_SETUP)) { + dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false)); } - /** - * Checks whether an intent is running in "pre-deferred" setup wizard flow. - * - * @param originalIntent The original intent that was used to start the step, usually via - * {@link android.app.Activity#getIntent()}. - * @return true if the intent passed in was running in "pre-deferred" setup wizard. - */ - public static boolean isPreDeferredSetupWizard(Intent originalIntent) { - return originalIntent != null - && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false); + for (String key : Arrays.asList(EXTRA_THEME, EXTRA_SCRIPT_URI, EXTRA_ACTION_ID)) { + dstIntent.putExtra(key, srcIntent.getStringExtra(key)); } - - /** - * Checks the intent whether the extra indicates that the light theme should be used or not. If - * the theme is not specified in the intent, or the theme specified is unknown, the value def - * will be returned. - * - * @param intent The intent used to start the activity, which the theme extra will be read from. - * @param def The default value if the theme is not specified. - * @return True if the activity started by the given intent should use light theme. - */ - public static boolean isLightTheme(Intent intent, boolean def) { - final String theme = intent.getStringExtra(EXTRA_THEME); - return isLightTheme(theme, def); + } + + /** + * Check whether an intent is intended to be used within the setup wizard flow. + * + * @param intent The intent to be checked, usually from {@link android.app.Activity#getIntent()}. + * @return true if the intent passed in was intended to be used with setup wizard. + */ + public static boolean isSetupWizardIntent(Intent intent) { + return intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false); + } + + /** + * Checks whether the current user has completed Setup Wizard. This is true if the current user + * has gone through Setup Wizard. The current user may or may not be the device owner and the + * device owner may have already completed setup wizard. + * + * @param context The context to retrieve the settings. + * @return true if the current user has completed Setup Wizard. + * @see #isDeviceProvisioned(android.content.Context) + */ + public static boolean isUserSetupComplete(Context context) { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + return Settings.Secure.getInt( + context.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) + == 1; + } else { + // For versions below JB MR1, there are no user profiles. Just return the global device + // provisioned state. + return Settings.Secure.getInt( + context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) + == 1; } - - /** - * Checks whether {@code theme} represents a light or dark theme. If the theme specified is - * unknown, the value def will be returned. - * - * @param theme The theme as specified from an intent sent from setup wizard. - * @param def The default value if the theme is not known. - * @return True if {@code theme} represents a light theme. - */ - public static boolean isLightTheme(String theme, boolean def) { - if (THEME_HOLO_LIGHT.equals(theme) || THEME_MATERIAL_LIGHT.equals(theme) - || THEME_GLIF_LIGHT.equals(theme) || THEME_GLIF_V2_LIGHT.equals(theme) - || THEME_GLIF_V3_LIGHT.equals(theme)) { - return true; - } else if (THEME_HOLO.equals(theme) || THEME_MATERIAL.equals(theme) - || THEME_GLIF.equals(theme) || THEME_GLIF_V2.equals(theme) - || THEME_GLIF_V3.equals(theme)) { - return false; - } else { - return def; - } + } + + /** + * Checks whether the device is provisioned. This means that the device has gone through Setup + * Wizard at least once. Note that the user can still be in Setup Wizard even if this is true, for + * a secondary user profile triggered through Settings > Add account. + * + * @param context The context to retrieve the settings. + * @return true if the device is provisioned. + * @see #isUserSetupComplete(android.content.Context) + */ + public static boolean isDeviceProvisioned(Context context) { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + return Settings.Global.getInt( + context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) + == 1; + } else { + return Settings.Secure.getInt( + context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) + == 1; } - - /** - * Gets the theme style resource defined by this library for the theme specified in the given - * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. - * - * @param intent The intent passed by setup wizard, or one with the theme propagated along using - * {@link #copyWizardManagerExtras(Intent, Intent)}. - * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if - * the given theme is not recognized. - * - * @see #getThemeRes(String, int) - */ - public static @StyleRes int getThemeRes(Intent intent, @StyleRes int defaultTheme) { - final String theme = intent.getStringExtra(EXTRA_THEME); - return getThemeRes(theme, defaultTheme, null); + } + + /** + * Checks whether an intent is running in the deferred setup wizard flow. + * + * @param originalIntent The original intent that was used to start the step, usually via {@link + * android.app.Activity#getIntent()}. + * @return true if the intent passed in was running in deferred setup wizard. + */ + public static boolean isDeferredSetupWizard(Intent originalIntent) { + return originalIntent != null && originalIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false); + } + + /** + * Checks whether an intent is running in "pre-deferred" setup wizard flow. + * + * @param originalIntent The original intent that was used to start the step, usually via {@link + * android.app.Activity#getIntent()}. + * @return true if the intent passed in was running in "pre-deferred" setup wizard. + */ + public static boolean isPreDeferredSetupWizard(Intent originalIntent) { + return originalIntent != null + && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false); + } + + /** + * Checks the intent whether the extra indicates that the light theme should be used or not. If + * the theme is not specified in the intent, or the theme specified is unknown, the value def will + * be returned. + * + * @param intent The intent used to start the activity, which the theme extra will be read from. + * @param def The default value if the theme is not specified. + * @return True if the activity started by the given intent should use light theme. + */ + public static boolean isLightTheme(Intent intent, boolean def) { + final String theme = intent.getStringExtra(EXTRA_THEME); + return isLightTheme(theme, def); + } + + /** + * Checks whether {@code theme} represents a light or dark theme. If the theme specified is + * unknown, the value def will be returned. + * + * @param theme The theme as specified from an intent sent from setup wizard. + * @param def The default value if the theme is not known. + * @return True if {@code theme} represents a light theme. + */ + public static boolean isLightTheme(String theme, boolean def) { + if (THEME_HOLO_LIGHT.equals(theme) + || THEME_MATERIAL_LIGHT.equals(theme) + || THEME_GLIF_LIGHT.equals(theme) + || THEME_GLIF_V2_LIGHT.equals(theme) + || THEME_GLIF_V3_LIGHT.equals(theme)) { + return true; + } else if (THEME_HOLO.equals(theme) + || THEME_MATERIAL.equals(theme) + || THEME_GLIF.equals(theme) + || THEME_GLIF_V2.equals(theme) + || THEME_GLIF_V3.equals(theme)) { + return false; + } else { + return def; } - - /** - * Gets the theme style resource defined by this library for the theme specified in the given - * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. - * - * @param intent The intent passed by setup wizard, or one with the theme propagated along using - * {@link #copyWizardManagerExtras(Intent, Intent)}. - * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if - * the given theme is not recognized. Return the {@code defaultTheme} if the specified - * theme is older than the oldest supported one. - * - * @see #getThemeRes(String, int) - */ - public static @StyleRes int getThemeRes(Intent intent, @StyleRes int defaultTheme, - @Nullable String oldestSupportedTheme) { - final String theme = intent.getStringExtra(EXTRA_THEME); - return getThemeRes(theme, defaultTheme, oldestSupportedTheme); - } - - /** - * Gets the theme style resource defined by this library for the given theme name. For example, - * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. - * - *

If you require extra theme attributes but want to ensure forward compatibility with new - * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in - * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. - * - *

{@code
-     * protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
-     *     super.onApplyThemeResource(theme, resid, first);
-     *     theme.applyStyle(R.style.MyThemeOverlay, true);
-     * }
-     * }
- * - * @param theme The string representation of the theme. - * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the - * given theme is not recognized. - */ - public static @StyleRes int getThemeRes(String theme, @StyleRes int defaultTheme) { - return getThemeRes(theme, defaultTheme, null); - } - - /** - * Gets the theme style resource defined by this library for the given theme name. For example, - * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. - * - *

If you require extra theme attributes but want to ensure forward compatibility with new - * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in - * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. - * - *

{@code
-     * protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
-     *     super.onApplyThemeResource(theme, resid, first);
-     *     theme.applyStyle(R.style.MyThemeOverlay, true);
-     * }
-     * }
- * - * @param theme The string representation of the theme. - * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the - * given theme is not recognized. - */ - public static @StyleRes int getThemeRes(String theme, @StyleRes int defaultTheme, - @Nullable String oldestSupportedTheme) { - int returnedTheme = defaultTheme; - if (theme != null) { - switch (theme) { - case THEME_GLIF_V3_LIGHT: - returnedTheme = R.style.SuwThemeGlifV3_Light; - break; - case THEME_GLIF_V3: - returnedTheme = R.style.SuwThemeGlifV3; - break; - case THEME_GLIF_V2_LIGHT: - returnedTheme = R.style.SuwThemeGlifV2_Light; - break; - case THEME_GLIF_V2: - returnedTheme = R.style.SuwThemeGlifV2; - break; - case THEME_GLIF_LIGHT: - returnedTheme = R.style.SuwThemeGlif_Light; - break; - case THEME_GLIF: - returnedTheme = R.style.SuwThemeGlif; - break; - case THEME_MATERIAL_LIGHT: - returnedTheme = R.style.SuwThemeMaterial_Light; - break; - case THEME_MATERIAL: - returnedTheme = R.style.SuwThemeMaterial; - break; - default: - // fall through - } - - // b/79540471 Return the default theme if the specified theme - // is older than the oldest supported one. - if (oldestSupportedTheme != null - && (getThemeVersion(theme) < getThemeVersion(oldestSupportedTheme))) { - returnedTheme = defaultTheme; - } - } - - return returnedTheme; + } + + /** + * Gets the theme style resource defined by this library for the theme specified in the given + * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. + * + * @param intent The intent passed by setup wizard, or one with the theme propagated along using + * {@link #copyWizardManagerExtras(Intent, Intent)}. + * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if + * the given theme is not recognized. + * @see #getThemeRes(String, int) + */ + public static @StyleRes int getThemeRes(Intent intent, @StyleRes int defaultTheme) { + final String theme = intent.getStringExtra(EXTRA_THEME); + return getThemeRes(theme, defaultTheme, null); + } + + /** + * Gets the theme style resource defined by this library for the theme specified in the given + * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. + * + * @param intent The intent passed by setup wizard, or one with the theme propagated along using + * {@link #copyWizardManagerExtras(Intent, Intent)}. + * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if + * the given theme is not recognized. Return the {@code defaultTheme} if the specified theme + * is older than the oldest supported one. + * @see #getThemeRes(String, int) + */ + public static @StyleRes int getThemeRes( + Intent intent, @StyleRes int defaultTheme, @Nullable String oldestSupportedTheme) { + final String theme = intent.getStringExtra(EXTRA_THEME); + return getThemeRes(theme, defaultTheme, oldestSupportedTheme); + } + + /** + * Gets the theme style resource defined by this library for the given theme name. For example, + * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. + * + *

If you require extra theme attributes but want to ensure forward compatibility with new + * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in + * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. + * + *

{@code
+   * protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
+   *     super.onApplyThemeResource(theme, resid, first);
+   *     theme.applyStyle(R.style.MyThemeOverlay, true);
+   * }
+   * }
+ * + * @param theme The string representation of the theme. + * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the + * given theme is not recognized. + */ + public static @StyleRes int getThemeRes(String theme, @StyleRes int defaultTheme) { + return getThemeRes(theme, defaultTheme, null); + } + + /** + * Gets the theme style resource defined by this library for the given theme name. For example, + * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. + * + *

If you require extra theme attributes but want to ensure forward compatibility with new + * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in + * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. + * + *

{@code
+   * protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
+   *     super.onApplyThemeResource(theme, resid, first);
+   *     theme.applyStyle(R.style.MyThemeOverlay, true);
+   * }
+   * }
+ * + * @param theme The string representation of the theme. + * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the + * given theme is not recognized. + */ + public static @StyleRes int getThemeRes( + String theme, @StyleRes int defaultTheme, @Nullable String oldestSupportedTheme) { + int returnedTheme = defaultTheme; + if (theme != null) { + switch (theme) { + case THEME_GLIF_V3_LIGHT: + returnedTheme = R.style.SuwThemeGlifV3_Light; + break; + case THEME_GLIF_V3: + returnedTheme = R.style.SuwThemeGlifV3; + break; + case THEME_GLIF_V2_LIGHT: + returnedTheme = R.style.SuwThemeGlifV2_Light; + break; + case THEME_GLIF_V2: + returnedTheme = R.style.SuwThemeGlifV2; + break; + case THEME_GLIF_LIGHT: + returnedTheme = R.style.SuwThemeGlif_Light; + break; + case THEME_GLIF: + returnedTheme = R.style.SuwThemeGlif; + break; + case THEME_MATERIAL_LIGHT: + returnedTheme = R.style.SuwThemeMaterial_Light; + break; + case THEME_MATERIAL: + returnedTheme = R.style.SuwThemeMaterial; + break; + default: + // fall through + } + + // b/79540471 Return the default theme if the specified theme + // is older than the oldest supported one. + if (oldestSupportedTheme != null + && (getThemeVersion(theme) < getThemeVersion(oldestSupportedTheme))) { + returnedTheme = defaultTheme; + } } - private static int getThemeVersion(String theme) { - if (theme != null) { - switch (theme) { - case THEME_GLIF_V3_LIGHT: - case THEME_GLIF_V3: - return 4; - case THEME_GLIF_V2_LIGHT: - case THEME_GLIF_V2: - return 3; - case THEME_GLIF_LIGHT: - case THEME_GLIF: - return 2; - case THEME_MATERIAL_LIGHT: - case THEME_MATERIAL: - return 1; - default: - // fall through - } - } - return -1; + return returnedTheme; + } + + private static int getThemeVersion(String theme) { + if (theme != null) { + switch (theme) { + case THEME_GLIF_V3_LIGHT: + case THEME_GLIF_V3: + return 4; + case THEME_GLIF_V2_LIGHT: + case THEME_GLIF_V2: + return 3; + case THEME_GLIF_LIGHT: + case THEME_GLIF: + return 2; + case THEME_MATERIAL_LIGHT: + case THEME_MATERIAL: + return 1; + default: + // fall through + } } + return -1; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/BottomScrollView.java b/library/main/src/com/android/setupwizardlib/view/BottomScrollView.java index eeb40a9..962538f 100644 --- a/library/main/src/com/android/setupwizardlib/view/BottomScrollView.java +++ b/library/main/src/com/android/setupwizardlib/view/BottomScrollView.java @@ -17,12 +17,11 @@ package com.android.setupwizardlib.view; import android.content.Context; +import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; import android.view.View; import android.widget.ScrollView; -import androidx.annotation.VisibleForTesting; - /** * An extension of ScrollView that will invoke a listener callback when the ScrollView needs * scrolling, and when the ScrollView is being scrolled to the bottom. This is often used in Setup @@ -30,75 +29,81 @@ import androidx.annotation.VisibleForTesting; */ public class BottomScrollView extends ScrollView { - public interface BottomScrollListener { - void onScrolledToBottom(); - void onRequiresScroll(); - } + public interface BottomScrollListener { + void onScrolledToBottom(); + + void onRequiresScroll(); + } - private BottomScrollListener mListener; - private int mScrollThreshold; - private boolean mRequiringScroll = false; + private BottomScrollListener listener; + private int scrollThreshold; + private boolean requiringScroll = false; - private final Runnable mCheckScrollRunnable = new Runnable() { + private final Runnable checkScrollRunnable = + new Runnable() { @Override public void run() { - checkScroll(); + checkScroll(); } - }; - - public BottomScrollView(Context context) { - super(context); + }; + + public BottomScrollView(Context context) { + super(context); + } + + public BottomScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BottomScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setBottomScrollListener(BottomScrollListener l) { + listener = l; + } + + @VisibleForTesting + public BottomScrollListener getBottomScrollListener() { + return listener; + } + + @VisibleForTesting + public int getScrollThreshold() { + return scrollThreshold; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + final View child = getChildAt(0); + if (child != null) { + scrollThreshold = Math.max(0, child.getMeasuredHeight() - b + t - getPaddingBottom()); } - - public BottomScrollView(Context context, AttributeSet attrs) { - super(context, attrs); + if (b - t > 0) { + // Post check scroll in the next run loop, so that the callback methods will be invoked + // after the layout pass. This way a new layout pass will be scheduled if view + // properties are changed in the callbacks. + post(checkScrollRunnable); } + } - public BottomScrollView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + if (oldt != t) { + checkScroll(); } - - public void setBottomScrollListener(BottomScrollListener l) { - mListener = l; + } + + private void checkScroll() { + if (listener != null) { + if (getScrollY() >= scrollThreshold) { + listener.onScrolledToBottom(); + } else if (!requiringScroll) { + requiringScroll = true; + listener.onRequiresScroll(); + } } - - @VisibleForTesting - public int getScrollThreshold() { - return mScrollThreshold; - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - final View child = getChildAt(0); - if (child != null) { - mScrollThreshold = Math.max(0, child.getMeasuredHeight() - b + t - getPaddingBottom()); - } - if (b - t > 0) { - // Post check scroll in the next run loop, so that the callback methods will be invoked - // after the layout pass. This way a new layout pass will be scheduled if view - // properties are changed in the callbacks. - post(mCheckScrollRunnable); - } - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - if (oldt != t) { - checkScroll(); - } - } - - private void checkScroll() { - if (mListener != null) { - if (getScrollY() >= mScrollThreshold) { - mListener.onScrolledToBottom(); - } else if (!mRequiringScroll) { - mRequiringScroll = true; - mListener.onRequiresScroll(); - } - } - } - + } } diff --git a/library/main/src/com/android/setupwizardlib/view/ButtonBarLayout.java b/library/main/src/com/android/setupwizardlib/view/ButtonBarLayout.java index f7f5f99..4be4f56 100644 --- a/library/main/src/com/android/setupwizardlib/view/ButtonBarLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/ButtonBarLayout.java @@ -20,105 +20,99 @@ import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; - import com.android.setupwizardlib.R; /** * An extension of LinearLayout that automatically switches to vertical orientation when it can't * fit its child views horizontally. * - * Modified from {@code com.android.internal.widget.ButtonBarLayout} + *

Modified from {@code com.android.internal.widget.ButtonBarLayout} */ public class ButtonBarLayout extends LinearLayout { - private boolean mStacked = false; - private int mOriginalPaddingLeft; - private int mOriginalPaddingRight; + private boolean stacked = false; + private int originalPaddingLeft; + private int originalPaddingRight; - public ButtonBarLayout(Context context) { - super(context); - } + public ButtonBarLayout(Context context) { + super(context); + } - public ButtonBarLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } + public ButtonBarLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); - setStacked(false); + setStacked(false); - boolean needsRemeasure = false; + boolean needsRemeasure = false; - int initialWidthMeasureSpec = widthMeasureSpec; - if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { - // Measure with WRAP_CONTENT, so that we can compare the measured size with the - // available size to see if we need to stack. - initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + int initialWidthMeasureSpec = widthMeasureSpec; + if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { + // Measure with WRAP_CONTENT, so that we can compare the measured size with the + // available size to see if we need to stack. + initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - // We'll need to remeasure again to fill excess space. - needsRemeasure = true; - } + // We'll need to remeasure again to fill excess space. + needsRemeasure = true; + } - super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); + super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); - if (getMeasuredWidth() > widthSize) { - setStacked(true); + if (getMeasuredWidth() > widthSize) { + setStacked(true); - // Measure again in the new orientation. - needsRemeasure = true; - } + // Measure again in the new orientation. + needsRemeasure = true; + } - if (needsRemeasure) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } + if (needsRemeasure) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + } - private void setStacked(boolean stacked) { - if (mStacked == stacked) { - return; - } - mStacked = stacked; - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - LayoutParams childParams = (LayoutParams) child.getLayoutParams(); - if (stacked) { - child.setTag(R.id.suw_original_weight, childParams.weight); - childParams.weight = 0; - } else { - Float weight = (Float) child.getTag(R.id.suw_original_weight); - if (weight != null) { - childParams.weight = weight; - } - } - child.setLayoutParams(childParams); + private void setStacked(boolean stacked) { + if (this.stacked == stacked) { + return; + } + this.stacked = stacked; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + LayoutParams childParams = (LayoutParams) child.getLayoutParams(); + if (stacked) { + child.setTag(R.id.suw_original_weight, childParams.weight); + childParams.weight = 0; + } else { + Float weight = (Float) child.getTag(R.id.suw_original_weight); + if (weight != null) { + childParams.weight = weight; } + } + child.setLayoutParams(childParams); + } - setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); + setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); - // Reverse the child order, so that the primary button is towards the top when vertical - for (int i = childCount - 1; i >= 0; i--) { - bringChildToFront(getChildAt(i)); - } + // Reverse the child order, so that the primary button is towards the top when vertical + for (int i = childCount - 1; i >= 0; i--) { + bringChildToFront(getChildAt(i)); + } - if (stacked) { - // HACK: In the default button bar style, the left and right paddings are not - // balanced to compensate for different alignment for borderless (left) button and - // the raised (right) button. When it's stacked, we want the buttons to be centered, - // so we balance out the paddings here. - mOriginalPaddingLeft = getPaddingLeft(); - mOriginalPaddingRight = getPaddingRight(); - int paddingHorizontal = Math.max(mOriginalPaddingLeft, mOriginalPaddingRight); - setPadding( - paddingHorizontal, getPaddingTop(), paddingHorizontal, getPaddingBottom()); - } else { - setPadding( - mOriginalPaddingLeft, - getPaddingTop(), - mOriginalPaddingRight, - getPaddingBottom()); - } + if (stacked) { + // HACK: In the default button bar style, the left and right paddings are not + // balanced to compensate for different alignment for borderless (left) button and + // the raised (right) button. When it's stacked, we want the buttons to be centered, + // so we balance out the paddings here. + originalPaddingLeft = getPaddingLeft(); + originalPaddingRight = getPaddingRight(); + int paddingHorizontal = Math.max(originalPaddingLeft, originalPaddingRight); + setPadding(paddingHorizontal, getPaddingTop(), paddingHorizontal, getPaddingBottom()); + } else { + setPadding(originalPaddingLeft, getPaddingTop(), originalPaddingRight, getPaddingBottom()); } + } } diff --git a/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java b/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java index 9605f99..fd2319c 100644 --- a/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java @@ -19,74 +19,67 @@ package com.android.setupwizardlib.view; import android.annotation.TargetApi; import android.content.Context; import android.os.Build.VERSION_CODES; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.widget.Checkable; import android.widget.LinearLayout; -import androidx.annotation.Nullable; - /** - * A LinearLayout which is checkable. This will set the checked state when - * {@link #onCreateDrawableState(int)} is called, and can be used with - * {@code android:duplicateParentState} to propagate the drawable state to child views. + * A LinearLayout which is checkable. This will set the checked state when {@link + * #onCreateDrawableState(int)} is called, and can be used with {@code android:duplicateParentState} + * to propagate the drawable state to child views. */ public class CheckableLinearLayout extends LinearLayout implements Checkable { - private boolean mChecked = false; + private boolean checked = false; - public CheckableLinearLayout(Context context) { - super(context); - } + public CheckableLinearLayout(Context context) { + super(context); + } - public CheckableLinearLayout(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } + public CheckableLinearLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } - @TargetApi(VERSION_CODES.HONEYCOMB) - public CheckableLinearLayout( - Context context, - @Nullable AttributeSet attrs, - int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + @TargetApi(VERSION_CODES.HONEYCOMB) + public CheckableLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @TargetApi(VERSION_CODES.LOLLIPOP) - public CheckableLinearLayout( - Context context, - AttributeSet attrs, - int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } + @TargetApi(VERSION_CODES.LOLLIPOP) + public CheckableLinearLayout( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } - { - setFocusable(true); - } + { + setFocusable(true); + } - @Override - protected int[] onCreateDrawableState(int extraSpace) { - if (mChecked) { - final int[] superStates = super.onCreateDrawableState(extraSpace + 1); - final int[] checked = new int[] { android.R.attr.state_checked }; - return mergeDrawableStates(superStates, checked); - } else { - return super.onCreateDrawableState(extraSpace); - } + @Override + protected int[] onCreateDrawableState(int extraSpace) { + if (this.checked) { + final int[] superStates = super.onCreateDrawableState(extraSpace + 1); + final int[] checked = new int[] {android.R.attr.state_checked}; + return mergeDrawableStates(superStates, checked); + } else { + return super.onCreateDrawableState(extraSpace); } + } - @Override - public void setChecked(boolean checked) { - mChecked = checked; - refreshDrawableState(); - } + @Override + public void setChecked(boolean checked) { + this.checked = checked; + refreshDrawableState(); + } - @Override - public boolean isChecked() { - return mChecked; - } + @Override + public boolean isChecked() { + return checked; + } - @Override - public void toggle() { - setChecked(!isChecked()); - } + @Override + public void toggle() { + setChecked(!isChecked()); + } } diff --git a/library/main/src/com/android/setupwizardlib/view/FillContentLayout.java b/library/main/src/com/android/setupwizardlib/view/FillContentLayout.java index 2c28090..b72d4d2 100644 --- a/library/main/src/com/android/setupwizardlib/view/FillContentLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/FillContentLayout.java @@ -21,13 +21,12 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; - import com.android.setupwizardlib.R; /** - * A layout that will measure its children size based on the space it is given, by using its - * {@code android:minWidth}, {@code android:minHeight}, {@code android:maxWidth}, and - * {@code android:maxHeight} values. + * A layout that will measure its children size based on the space it is given, by using its {@code + * android:minWidth}, {@code android:minHeight}, {@code android:maxWidth}, and {@code + * android:maxHeight} values. * *

Typically this is used to show an illustration image or video on the screen. For optimal UX, * those assets typically want to occupy the remaining space available on screen within a certain @@ -42,84 +41,82 @@ import com.android.setupwizardlib.R; */ public class FillContentLayout extends FrameLayout { - private int mMaxWidth; - private int mMaxHeight; - - public FillContentLayout(Context context) { - this(context, null); - } - - public FillContentLayout(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.suwFillContentLayoutStyle); - } - - public FillContentLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } + private int maxWidth; + private int maxHeight; - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - TypedArray a = context.obtainStyledAttributes( - attrs, - R.styleable.SuwFillContentLayout, - defStyleAttr, - 0); + public FillContentLayout(Context context) { + this(context, null); + } - mMaxHeight = a.getDimensionPixelSize( - R.styleable.SuwFillContentLayout_android_maxHeight, -1); - mMaxWidth = a.getDimensionPixelSize(R.styleable.SuwFillContentLayout_android_maxWidth, -1); + public FillContentLayout(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.suwFillContentLayoutStyle); + } - a.recycle(); - } + public FillContentLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // Measure this view with the minWidth and minHeight, without asking the children. - // (Children size is the drawable's intrinsic size, and we don't want that to influence - // the size of the illustration). - setMeasuredDimension( - getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), - getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); - - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - measureIllustrationChild(getChildAt(i), getMeasuredWidth(), getMeasuredHeight()); - } - } + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwFillContentLayout, defStyleAttr, 0); - private void measureIllustrationChild(View child, int parentWidth, int parentHeight) { - // Modified from ViewGroup#measureChildWithMargins - final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + maxHeight = a.getDimensionPixelSize(R.styleable.SuwFillContentLayout_android_maxHeight, -1); + maxWidth = a.getDimensionPixelSize(R.styleable.SuwFillContentLayout_android_maxWidth, -1); - // Create measure specs that are no bigger than min(parentSize, maxSize) + a.recycle(); + } - int childWidthMeasureSpec = getMaxSizeMeasureSpec( - Math.min(mMaxWidth, parentWidth), - getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, - lp.width); - int childHeightMeasureSpec = getMaxSizeMeasureSpec( - Math.min(mMaxHeight, parentHeight), - getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, - lp.height); + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Measure this view with the minWidth and minHeight, without asking the children. + // (Children size is the drawable's intrinsic size, and we don't want that to influence + // the size of the illustration). + setMeasuredDimension( + getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), + getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + measureIllustrationChild(getChildAt(i), getMeasuredWidth(), getMeasuredHeight()); } - - private static int getMaxSizeMeasureSpec(int maxSize, int padding, int childDimension) { - // Modified from ViewGroup#getChildMeasureSpec - int size = Math.max(0, maxSize - padding); - - if (childDimension >= 0) { - // Child wants a specific size... so be it - return MeasureSpec.makeMeasureSpec(childDimension, MeasureSpec.EXACTLY); - } else if (childDimension == LayoutParams.MATCH_PARENT) { - // Child wants to be our size. So be it. - return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); - } else if (childDimension == LayoutParams.WRAP_CONTENT) { - // Child wants to determine its own size. It can't be - // bigger than us. - return MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); - } - return 0; + } + + private void measureIllustrationChild(View child, int parentWidth, int parentHeight) { + // Modified from ViewGroup#measureChildWithMargins + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + // Create measure specs that are no bigger than min(parentSize, maxSize) + + int childWidthMeasureSpec = + getMaxSizeMeasureSpec( + Math.min(maxWidth, parentWidth), + getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, + lp.width); + int childHeightMeasureSpec = + getMaxSizeMeasureSpec( + Math.min(maxHeight, parentHeight), + getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, + lp.height); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + private static int getMaxSizeMeasureSpec(int maxSize, int padding, int childDimension) { + // Modified from ViewGroup#getChildMeasureSpec + int size = Math.max(0, maxSize - padding); + + if (childDimension >= 0) { + // Child wants a specific size... so be it + return MeasureSpec.makeMeasureSpec(childDimension, MeasureSpec.EXACTLY); + } else if (childDimension == LayoutParams.MATCH_PARENT) { + // Child wants to be our size. So be it. + return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + } else if (childDimension == LayoutParams.WRAP_CONTENT) { + // Child wants to determine its own size. It can't be + // bigger than us. + return MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); } + return 0; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/Illustration.java b/library/main/src/com/android/setupwizardlib/view/Illustration.java index c6968f8..e9cd4b5 100644 --- a/library/main/src/com/android/setupwizardlib/view/Illustration.java +++ b/library/main/src/com/android/setupwizardlib/view/Illustration.java @@ -30,7 +30,6 @@ import android.util.LayoutDirection; import android.view.Gravity; import android.view.ViewOutlineProvider; import android.widget.FrameLayout; - import com.android.setupwizardlib.R; /** @@ -39,184 +38,190 @@ import com.android.setupwizardlib.R; * drawable to fit the width of the view and fills the rest with the background. * *

If an aspect ratio is set, then the aspect ratio of the source drawable is maintained. - * Otherwise the the aspect ratio will be ignored, only increasing the width of the illustration. + * Otherwise the aspect ratio will be ignored, only increasing the width of the illustration. */ public class Illustration extends FrameLayout { - // Size of the baseline grid in pixels - private float mBaselineGridSize; - private Drawable mBackground; - private Drawable mIllustration; - private final Rect mViewBounds = new Rect(); - private final Rect mIllustrationBounds = new Rect(); - private float mScale = 1.0f; - private float mAspectRatio = 0.0f; - - public Illustration(Context context) { - super(context); - init(null, 0); - } - - public Illustration(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs, 0); + // Size of the baseline grid in pixels + private float baselineGridSize; + private Drawable background; + private Drawable illustration; + private final Rect viewBounds = new Rect(); + private final Rect illustrationBounds = new Rect(); + private float scale = 1.0f; + private float aspectRatio = 0.0f; + + public Illustration(Context context) { + super(context); + init(null, 0); + } + + public Illustration(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public Illustration(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + // All the constructors delegate to this init method. The 3-argument constructor is not + // available in FrameLayout before v11, so call super with the exact same arguments. + private void init(AttributeSet attrs, int defStyleAttr) { + if (attrs != null) { + TypedArray a = + getContext().obtainStyledAttributes(attrs, R.styleable.SuwIllustration, defStyleAttr, 0); + aspectRatio = a.getFloat(R.styleable.SuwIllustration_suwAspectRatio, 0.0f); + a.recycle(); } - - @TargetApi(VERSION_CODES.HONEYCOMB) - public Illustration(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs, defStyleAttr); + // Number of pixels of the 8dp baseline grid as defined in material design specs + baselineGridSize = getResources().getDisplayMetrics().density * 8; + setWillNotDraw(false); + } + + /** + * The background will be drawn to fill up the rest of the view. It will also be scaled by the + * same amount as the foreground so their textures look the same. + */ + // Override the deprecated setBackgroundDrawable method to support API < 16. View.setBackground + // forwards to setBackgroundDrawable in the framework implementation. + @SuppressWarnings("deprecation") + @Override + public void setBackgroundDrawable(Drawable background) { + if (background == this.background) { + return; } - - // All the constructors delegate to this init method. The 3-argument constructor is not - // available in FrameLayout before v11, so call super with the exact same arguments. - private void init(AttributeSet attrs, int defStyleAttr) { - if (attrs != null) { - TypedArray a = getContext().obtainStyledAttributes(attrs, - R.styleable.SuwIllustration, defStyleAttr, 0); - mAspectRatio = a.getFloat(R.styleable.SuwIllustration_suwAspectRatio, 0.0f); - a.recycle(); - } - // Number of pixels of the 8dp baseline grid as defined in material design specs - mBaselineGridSize = getResources().getDisplayMetrics().density * 8; - setWillNotDraw(false); - } - - /** - * The background will be drawn to fill up the rest of the view. It will also be scaled by the - * same amount as the foreground so their textures look the same. - */ - // Override the deprecated setBackgroundDrawable method to support API < 16. View.setBackground - // forwards to setBackgroundDrawable in the framework implementation. - @SuppressWarnings("deprecation") - @Override - public void setBackgroundDrawable(Drawable background) { - if (background == mBackground) { - return; - } - mBackground = background; - invalidate(); - requestLayout(); + this.background = background; + invalidate(); + requestLayout(); + } + + /** + * Sets the drawable used as the illustration. The drawable is expected to have intrinsic width + * and height defined and will be scaled to fit the width of the view. + */ + public void setIllustration(Drawable illustration) { + if (illustration == this.illustration) { + return; } - - /** - * Sets the drawable used as the illustration. The drawable is expected to have intrinsic - * width and height defined and will be scaled to fit the width of the view. - */ - public void setIllustration(Drawable illustration) { - if (illustration == mIllustration) { - return; - } - mIllustration = illustration; - invalidate(); - requestLayout(); + this.illustration = illustration; + invalidate(); + requestLayout(); + } + + /** + * Set the aspect ratio reserved for the illustration. This overrides the top padding of the view + * according to the width of this view and the aspect ratio. Children views will start being laid + * out below this aspect ratio. + * + * @param aspectRatio A float value specifying the aspect ratio (= width / height). 0 to not + * override the top padding. + */ + public void setAspectRatio(float aspectRatio) { + this.aspectRatio = aspectRatio; + invalidate(); + requestLayout(); + } + + @Override + @Deprecated + public void setForeground(Drawable d) { + setIllustration(d); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (aspectRatio != 0.0f) { + int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + int illustrationHeight = (int) (parentWidth / aspectRatio); + illustrationHeight = (int) (illustrationHeight - (illustrationHeight % baselineGridSize)); + setPadding(0, illustrationHeight, 0, 0); } - - /** - * Set the aspect ratio reserved for the illustration. This overrides the top padding of the - * view according to the width of this view and the aspect ratio. Children views will start - * being laid out below this aspect ratio. - * - * @param aspectRatio A float value specifying the aspect ratio (= width / height). 0 to not - * override the top padding. - */ - public void setAspectRatio(float aspectRatio) { - mAspectRatio = aspectRatio; - invalidate(); - requestLayout(); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + //noinspection AndroidLintInlinedApi + setOutlineProvider(ViewOutlineProvider.BOUNDS); } - - @Override - @Deprecated - public void setForeground(Drawable d) { - setIllustration(d); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int layoutWidth = right - left; + final int layoutHeight = bottom - top; + if (illustration != null) { + int intrinsicWidth = illustration.getIntrinsicWidth(); + int intrinsicHeight = illustration.getIntrinsicHeight(); + + viewBounds.set(0, 0, layoutWidth, layoutHeight); + if (aspectRatio != 0f) { + scale = layoutWidth / (float) intrinsicWidth; + intrinsicWidth = layoutWidth; + intrinsicHeight = (int) (intrinsicHeight * scale); + } + Gravity.apply( + Gravity.FILL_HORIZONTAL | Gravity.TOP, + intrinsicWidth, + intrinsicHeight, + viewBounds, + illustrationBounds); + illustration.setBounds(illustrationBounds); } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mAspectRatio != 0.0f) { - int parentWidth = MeasureSpec.getSize(widthMeasureSpec); - int illustrationHeight = (int) (parentWidth / mAspectRatio); - illustrationHeight = - (int) (illustrationHeight - (illustrationHeight % mBaselineGridSize)); - setPadding(0, illustrationHeight, 0, 0); - } - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - //noinspection AndroidLintInlinedApi - setOutlineProvider(ViewOutlineProvider.BOUNDS); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (background != null) { + // Scale the background bounds by the same scale to compensate for the scale done to the + // canvas in onDraw. + background.setBounds( + 0, + 0, + (int) Math.ceil(layoutWidth / scale), + (int) Math.ceil((layoutHeight - illustrationBounds.height()) / scale)); } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - final int layoutWidth = right - left; - final int layoutHeight = bottom - top; - if (mIllustration != null) { - int intrinsicWidth = mIllustration.getIntrinsicWidth(); - int intrinsicHeight = mIllustration.getIntrinsicHeight(); - - mViewBounds.set(0, 0, layoutWidth, layoutHeight); - if (mAspectRatio != 0f) { - mScale = layoutWidth / (float) intrinsicWidth; - intrinsicWidth = layoutWidth; - intrinsicHeight = (int) (intrinsicHeight * mScale); - } - Gravity.apply(Gravity.FILL_HORIZONTAL | Gravity.TOP, intrinsicWidth, - intrinsicHeight, mViewBounds, mIllustrationBounds); - mIllustration.setBounds(mIllustrationBounds); - } - if (mBackground != null) { - // Scale the background bounds by the same scale to compensate for the scale done to the - // canvas in onDraw. - mBackground.setBounds(0, 0, (int) Math.ceil(layoutWidth / mScale), - (int) Math.ceil((layoutHeight - mIllustrationBounds.height()) / mScale)); - } - super.onLayout(changed, left, top, right, bottom); + super.onLayout(changed, left, top, right, bottom); + } + + @Override + public void onDraw(Canvas canvas) { + if (background != null) { + // Draw the background filling parts not covered by the illustration + canvas.save(); + canvas.translate(0, illustrationBounds.height()); + // Scale the background so its size matches the foreground + canvas.scale(scale, scale, 0, 0); + if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1 + && shouldMirrorDrawable(background, getLayoutDirection())) { + // Flip the illustration for RTL layouts + canvas.scale(-1, 1); + canvas.translate(-background.getBounds().width(), 0); + } + background.draw(canvas); + canvas.restore(); } - - @Override - public void onDraw(Canvas canvas) { - if (mBackground != null) { - // Draw the background filling parts not covered by the illustration - canvas.save(); - canvas.translate(0, mIllustrationBounds.height()); - // Scale the background so its size matches the foreground - canvas.scale(mScale, mScale, 0, 0); - if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1 && - shouldMirrorDrawable(mBackground, getLayoutDirection())) { - // Flip the illustration for RTL layouts - canvas.scale(-1, 1); - canvas.translate(-mBackground.getBounds().width(), 0); - } - mBackground.draw(canvas); - canvas.restore(); - } - if (mIllustration != null) { - canvas.save(); - if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1 && - shouldMirrorDrawable(mIllustration, getLayoutDirection())) { - // Flip the illustration for RTL layouts - canvas.scale(-1, 1); - canvas.translate(-mIllustrationBounds.width(), 0); - } - // Draw the illustration - mIllustration.draw(canvas); - canvas.restore(); - } - super.onDraw(canvas); + if (illustration != null) { + canvas.save(); + if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1 + && shouldMirrorDrawable(illustration, getLayoutDirection())) { + // Flip the illustration for RTL layouts + canvas.scale(-1, 1); + canvas.translate(-illustrationBounds.width(), 0); + } + // Draw the illustration + illustration.draw(canvas); + canvas.restore(); } - - private boolean shouldMirrorDrawable(Drawable drawable, int layoutDirection) { - if (layoutDirection == LayoutDirection.RTL) { - if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - return drawable.isAutoMirrored(); - } else if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - final int flags = getContext().getApplicationInfo().flags; - //noinspection AndroidLintInlinedApi - return (flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0; - } - } - return false; + super.onDraw(canvas); + } + + private boolean shouldMirrorDrawable(Drawable drawable, int layoutDirection) { + if (layoutDirection == LayoutDirection.RTL) { + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + return drawable.isAutoMirrored(); + } else if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + final int flags = getContext().getApplicationInfo().flags; + //noinspection AndroidLintInlinedApi + return (flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0; + } } + return false; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java index 3c188e8..375ee18 100644 --- a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java +++ b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java @@ -28,19 +28,16 @@ import android.media.MediaPlayer.OnPreparedListener; import android.media.MediaPlayer.OnSeekCompleteListener; import android.net.Uri; import android.os.Build.VERSION_CODES; +import androidx.annotation.Nullable; +import androidx.annotation.RawRes; +import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.TextureView.SurfaceTextureListener; import android.view.View; - -import androidx.annotation.Nullable; -import androidx.annotation.RawRes; -import androidx.annotation.VisibleForTesting; - import com.android.setupwizardlib.R; - import java.io.IOException; /** @@ -52,301 +49,295 @@ import java.io.IOException; * should loop back to * *

For optimal file size, use avconv or other video compression tool to remove the unused audio - * track and reduce the size of your video asset: - * avconv -i [input file] -vcodec h264 -crf 20 -an [output_file] + * track and reduce the size of your video asset: avconv -i [input file] -vcodec h264 -crf 20 -an + * [output_file] */ @TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) -public class IllustrationVideoView extends TextureView implements Animatable, +public class IllustrationVideoView extends TextureView + implements Animatable, SurfaceTextureListener, OnPreparedListener, OnSeekCompleteListener, OnInfoListener, OnErrorListener { - private static final String TAG = "IllustrationVideoView"; + private static final String TAG = "IllustrationVideoView"; - protected float mAspectRatio = 1.0f; // initial guess until we know + protected float mAspectRatio = 1.0f; // initial guess until we know - @Nullable // Can be null when media player fails to initialize - protected MediaPlayer mMediaPlayer; + @Nullable // Can be null when media player fails to initialize + protected MediaPlayer mMediaPlayer; - private @RawRes int mVideoResId = 0; + private @RawRes int videoResId = 0; - private String mVideoResPackageName; + private String videoResPackageName; - @VisibleForTesting Surface mSurface; + @VisibleForTesting Surface surface; - private boolean mPrepared; + private boolean prepared; - public IllustrationVideoView(Context context, AttributeSet attrs) { - super(context, attrs); - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SuwIllustrationVideoView); - final int videoResId = a.getResourceId(R.styleable.SuwIllustrationVideoView_suwVideo, 0); - a.recycle(); - setVideoResource(videoResId); + public IllustrationVideoView(Context context, AttributeSet attrs) { + super(context, attrs); + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwIllustrationVideoView); + final int videoResId = a.getResourceId(R.styleable.SuwIllustrationVideoView_suwVideo, 0); + a.recycle(); + setVideoResource(videoResId); - // By default the video scales without interpolation, resulting in jagged edges in the - // video. This works around it by making the view go through scaling, which will apply - // anti-aliasing effects. - setScaleX(0.9999999f); - setScaleX(0.9999999f); + // By default the video scales without interpolation, resulting in jagged edges in the + // video. This works around it by making the view go through scaling, which will apply + // anti-aliasing effects. + setScaleX(0.9999999f); + setScaleX(0.9999999f); - setSurfaceTextureListener(this); + setSurfaceTextureListener(this); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + if (height < width * mAspectRatio) { + // Height constraint is tighter. Need to scale down the width to fit aspect ratio. + width = (int) (height / mAspectRatio); + } else { + // Width constraint is tighter. Need to scale down the height to fit aspect ratio. + height = (int) (width * mAspectRatio); } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - - if (height < width * mAspectRatio) { - // Height constraint is tighter. Need to scale down the width to fit aspect ratio. - width = (int) (height / mAspectRatio); - } else { - // Width constraint is tighter. Need to scale down the height to fit aspect ratio. - height = (int) (width * mAspectRatio); - } - - super.onMeasure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + super.onMeasure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + + /** + * Set the video and video package name to be played by this view. + * + * @param videoResId Resource ID of the video, typically an MP4 under res/raw. + * @param videoResPackageName The package name of videoResId. + */ + public void setVideoResource(@RawRes int videoResId, String videoResPackageName) { + if (videoResId != this.videoResId + || (videoResPackageName != null && !videoResPackageName.equals(this.videoResPackageName))) { + this.videoResId = videoResId; + this.videoResPackageName = videoResPackageName; + createMediaPlayer(); + } + } + + /** + * Set the video to be played by this view. + * + * @param resId Resource ID of the video, typically an MP4 under res/raw. + */ + public void setVideoResource(@RawRes int resId) { + setVideoResource(resId, getContext().getPackageName()); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (hasWindowFocus) { + start(); + } else { + stop(); + } + } + + /** + * Creates a media player for the current URI. The media player will be started immediately if the + * view's window is visible. If there is an existing media player, it will be released. + */ + protected void createMediaPlayer() { + if (mMediaPlayer != null) { + mMediaPlayer.release(); + } + if (surface == null || videoResId == 0) { + return; } + mMediaPlayer = new MediaPlayer(); - /** - * Set the video and video package name to be played by this view. - * - * @param videoResId Resource ID of the video, typically an MP4 under res/raw. - * @param videoResPackageName The package name of videoResId. - */ - public void setVideoResource(@RawRes int videoResId, String videoResPackageName) { - if (videoResId != mVideoResId - || (videoResPackageName != null && !videoResPackageName.equals( - mVideoResPackageName))) { - mVideoResId = videoResId; - mVideoResPackageName = videoResPackageName; - createMediaPlayer(); - } - } + mMediaPlayer.setSurface(surface); + mMediaPlayer.setOnPreparedListener(this); + mMediaPlayer.setOnSeekCompleteListener(this); + mMediaPlayer.setOnInfoListener(this); + mMediaPlayer.setOnErrorListener(this); - /** - * Set the video to be played by this view. - * - * @param resId Resource ID of the video, typically an MP4 under res/raw. - */ - public void setVideoResource(@RawRes int resId) { - setVideoResource(resId, getContext().getPackageName()); - } + setVideoResourceInternal(videoResId, videoResPackageName); + } - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - if (hasWindowFocus) { - start(); - } else { - stop(); - } + private void setVideoResourceInternal(@RawRes int videoRes, String videoResPackageName) { + Uri uri = Uri.parse("android.resource://" + videoResPackageName + "/" + videoRes); + try { + mMediaPlayer.setDataSource(getContext(), uri, null); + mMediaPlayer.prepareAsync(); + } catch (IOException e) { + Log.wtf(TAG, "Unable to set data source", e); } + } - /** - * Creates a media player for the current URI. The media player will be started immediately if - * the view's window is visible. If there is an existing media player, it will be released. - */ - protected void createMediaPlayer() { - if (mMediaPlayer != null) { - mMediaPlayer.release(); - } - if (mSurface == null || mVideoResId == 0) { - return; - } - - mMediaPlayer = new MediaPlayer(); - - mMediaPlayer.setSurface(mSurface); - mMediaPlayer.setOnPreparedListener(this); - mMediaPlayer.setOnSeekCompleteListener(this); - mMediaPlayer.setOnInfoListener(this); - mMediaPlayer.setOnErrorListener(this); - - setVideoResourceInternal(mVideoResId, mVideoResPackageName); + protected void createSurface() { + if (surface != null) { + surface.release(); + surface = null; } - - private void setVideoResourceInternal(@RawRes int videoRes, String videoResPackageName) { - Uri uri = Uri.parse("android.resource://" + videoResPackageName + "/" + videoRes); - try { - mMediaPlayer.setDataSource(getContext(), uri); - mMediaPlayer.prepareAsync(); - } catch (IOException e) { - Log.wtf(TAG, "Unable to set data source", e); - } + // Reattach only if it has been previously released + SurfaceTexture surfaceTexture = getSurfaceTexture(); + if (surfaceTexture != null) { + setVisibility(View.INVISIBLE); + surface = new Surface(surfaceTexture); } - - protected void createSurface() { - if (mSurface != null) { - mSurface.release(); - mSurface = null; - } - // Reattach only if it has been previously released - SurfaceTexture surfaceTexture = getSurfaceTexture(); - if (surfaceTexture != null) { - setVisibility(View.INVISIBLE); - mSurface = new Surface(surfaceTexture); - } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + if (visibility == View.VISIBLE) { + reattach(); + } else { + release(); } - - @Override - protected void onWindowVisibilityChanged(int visibility) { - super.onWindowVisibilityChanged(visibility); - if (visibility == View.VISIBLE) { - reattach(); - } else { - release(); - } + } + + /** + * Whether the media player should play the video in a continuous loop. The default value is true. + */ + protected boolean shouldLoop() { + return true; + } + + /** + * Release any resources used by this view. This is automatically called in + * onSurfaceTextureDestroyed so in most cases you don't have to call this. + */ + public void release() { + if (mMediaPlayer != null) { + mMediaPlayer.release(); + mMediaPlayer = null; + prepared = false; } - - /** - * Whether the media player should play the video in a continuous loop. The default value is - * true. - */ - protected boolean shouldLoop() { - return true; + if (surface != null) { + surface.release(); + surface = null; } + } - /** - * Release any resources used by this view. This is automatically called in - * onSurfaceTextureDestroyed so in most cases you don't have to call this. - */ - public void release() { - if (mMediaPlayer != null) { - mMediaPlayer.release(); - mMediaPlayer = null; - mPrepared = false; - } - if (mSurface != null) { - mSurface.release(); - mSurface = null; - } + private void reattach() { + if (surface == null) { + initVideo(); } + } - private void reattach() { - if (mSurface == null) { - initVideo(); - } + private void initVideo() { + if (getWindowVisibility() != View.VISIBLE) { + return; } - - private void initVideo() { - if (getWindowVisibility() != View.VISIBLE) { - return; - } - createSurface(); - if (mSurface != null) { - createMediaPlayer(); - } else { - Log.w(TAG, "Surface creation failed"); - } + createSurface(); + if (surface != null) { + createMediaPlayer(); + } else { + Log.w(TAG, "Surface creation failed"); } + } - protected void onRenderingStart() { - } + protected void onRenderingStart() {} - /* SurfaceTextureListener methods */ + /* SurfaceTextureListener methods */ - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { - // Keep the view hidden until video starts - setVisibility(View.INVISIBLE); - initVideo(); - } + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { + // Keep the view hidden until video starts + setVisibility(View.INVISIBLE); + initVideo(); + } - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { - } + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {} - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { - release(); - return true; - } + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + release(); + return true; + } - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { - } + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {} - /* Animatable methods */ + /* Animatable methods */ - @Override - public void start() { - if (mPrepared && mMediaPlayer != null && !mMediaPlayer.isPlaying()) { - mMediaPlayer.start(); - } + @Override + public void start() { + if (prepared && mMediaPlayer != null && !mMediaPlayer.isPlaying()) { + mMediaPlayer.start(); } + } - @Override - public void stop() { - if (mPrepared && mMediaPlayer != null) { - mMediaPlayer.pause(); - } + @Override + public void stop() { + if (prepared && mMediaPlayer != null) { + mMediaPlayer.pause(); } + } - @Override - public boolean isRunning() { - return mMediaPlayer != null && mMediaPlayer.isPlaying(); - } + @Override + public boolean isRunning() { + return mMediaPlayer != null && mMediaPlayer.isPlaying(); + } - /* MediaPlayer callbacks */ + /* MediaPlayer callbacks */ - @Override - public boolean onInfo(MediaPlayer mp, int what, int extra) { - if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { - // Video available, show view now - setVisibility(View.VISIBLE); - onRenderingStart(); - } - return false; + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { + // Video available, show view now + setVisibility(View.VISIBLE); + onRenderingStart(); } - - @Override - public void onPrepared(MediaPlayer mp) { - mPrepared = true; - mp.setLooping(shouldLoop()); - - float aspectRatio = 0.0f; - if (mp.getVideoWidth() > 0 && mp.getVideoHeight() > 0) { - aspectRatio = (float) mp.getVideoHeight() / mp.getVideoWidth(); - } else { - Log.w(TAG, "Unexpected video size=" + mp.getVideoWidth() + "x" - + mp.getVideoHeight()); - } - if (Float.compare(mAspectRatio, aspectRatio) != 0) { - mAspectRatio = aspectRatio; - requestLayout(); - } - if (getWindowVisibility() == View.VISIBLE) { - start(); - } + return false; + } + + @Override + public void onPrepared(MediaPlayer mp) { + prepared = true; + mp.setLooping(shouldLoop()); + + float aspectRatio = 0.0f; + if (mp.getVideoWidth() > 0 && mp.getVideoHeight() > 0) { + aspectRatio = (float) mp.getVideoHeight() / mp.getVideoWidth(); + } else { + Log.w(TAG, "Unexpected video size=" + mp.getVideoWidth() + "x" + mp.getVideoHeight()); } - - @Override - public void onSeekComplete(MediaPlayer mp) { - if (isPrepared()) { - mp.start(); - } else { - Log.wtf(TAG, "Seek complete but media player not prepared"); - } + if (Float.compare(mAspectRatio, aspectRatio) != 0) { + mAspectRatio = aspectRatio; + requestLayout(); } - - public int getCurrentPosition() { - return mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition(); + if (getWindowVisibility() == View.VISIBLE) { + start(); } - - protected boolean isPrepared() { - return mPrepared; + } + + @Override + public void onSeekComplete(MediaPlayer mp) { + if (isPrepared()) { + mp.start(); + } else { + Log.wtf(TAG, "Seek complete but media player not prepared"); } + } - @Override - public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { - Log.w(TAG, "MediaPlayer error. what=" + what + " extra=" + extra); - return false; - } + public int getCurrentPosition() { + return mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition(); + } + + protected boolean isPrepared() { + return prepared; + } + + @Override + public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { + Log.w(TAG, "MediaPlayer error. what=" + what + " extra=" + extra); + return false; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java b/library/main/src/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java index 02fdcc7..c6a38f6 100644 --- a/library/main/src/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/IntrinsicSizeFrameLayout.java @@ -22,71 +22,71 @@ import android.content.res.TypedArray; import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.widget.FrameLayout; - import com.android.setupwizardlib.R; /** * A FrameLayout subclass that has an "intrinsic size", which is the size it wants to be if that is - * within the constraints given by the parent. The intrinsic size can be set with the - * {@code android:width} and {@code android:height} attributes in XML. + * within the constraints given by the parent. The intrinsic size can be set with the {@code + * android:width} and {@code android:height} attributes in XML. * - * Note that for the intrinsic size to be meaningful, {@code android:layout_width} and/or - * {@code android:layout_height} will need to be {@code wrap_content}. + *

Note that for the intrinsic size to be meaningful, {@code android:layout_width} and/or {@code + * android:layout_height} will need to be {@code wrap_content}. */ public class IntrinsicSizeFrameLayout extends FrameLayout { - private int mIntrinsicHeight = 0; - private int mIntrinsicWidth = 0; + private int intrinsicHeight = 0; + private int intrinsicWidth = 0; - public IntrinsicSizeFrameLayout(Context context) { - super(context); - init(context, null, 0); - } + public IntrinsicSizeFrameLayout(Context context) { + super(context); + init(context, null, 0); + } - public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } + public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } - @TargetApi(VERSION_CODES.HONEYCOMB) - public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } + @TargetApi(VERSION_CODES.HONEYCOMB) + public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SuwIntrinsicSizeFrameLayout, defStyleAttr, 0); - mIntrinsicHeight = - a.getDimensionPixelSize(R.styleable.SuwIntrinsicSizeFrameLayout_android_height, 0); - mIntrinsicWidth = - a.getDimensionPixelSize(R.styleable.SuwIntrinsicSizeFrameLayout_android_width, 0); - a.recycle(); - } + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = + context.obtainStyledAttributes( + attrs, R.styleable.SuwIntrinsicSizeFrameLayout, defStyleAttr, 0); + intrinsicHeight = + a.getDimensionPixelSize(R.styleable.SuwIntrinsicSizeFrameLayout_android_height, 0); + intrinsicWidth = + a.getDimensionPixelSize(R.styleable.SuwIntrinsicSizeFrameLayout_android_width, 0); + a.recycle(); + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(getIntrinsicMeasureSpec(widthMeasureSpec, mIntrinsicWidth), - getIntrinsicMeasureSpec(heightMeasureSpec, mIntrinsicHeight)); - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure( + getIntrinsicMeasureSpec(widthMeasureSpec, intrinsicWidth), + getIntrinsicMeasureSpec(heightMeasureSpec, intrinsicHeight)); + } - private int getIntrinsicMeasureSpec(int measureSpec, int intrinsicSize) { - if (intrinsicSize <= 0) { - // Intrinsic size is not set, just return the original spec - return measureSpec; - } - final int mode = MeasureSpec.getMode(measureSpec); - final int size = MeasureSpec.getSize(measureSpec); - if (mode == MeasureSpec.UNSPECIFIED) { - // Parent did not give any constraint, so we'll be the intrinsic size - return MeasureSpec.makeMeasureSpec(mIntrinsicHeight, MeasureSpec.EXACTLY); - } else if (mode == MeasureSpec.AT_MOST) { - // If intrinsic size is within parents constraint, take the intrinsic size. - // Otherwise take the parents size because that's closest to the intrinsic size. - return MeasureSpec.makeMeasureSpec(Math.min(size, mIntrinsicHeight), - MeasureSpec.EXACTLY); - } - // Parent specified EXACTLY, or in all other cases, just return the original spec - return measureSpec; + private int getIntrinsicMeasureSpec(int measureSpec, int intrinsicSize) { + if (intrinsicSize <= 0) { + // Intrinsic size is not set, just return the original spec + return measureSpec; + } + final int mode = MeasureSpec.getMode(measureSpec); + final int size = MeasureSpec.getSize(measureSpec); + if (mode == MeasureSpec.UNSPECIFIED) { + // Parent did not give any constraint, so we'll be the intrinsic size + return MeasureSpec.makeMeasureSpec(intrinsicHeight, MeasureSpec.EXACTLY); + } else if (mode == MeasureSpec.AT_MOST) { + // If intrinsic size is within parents constraint, take the intrinsic size. + // Otherwise take the parents size because that's closest to the intrinsic size. + return MeasureSpec.makeMeasureSpec(Math.min(size, intrinsicHeight), MeasureSpec.EXACTLY); } + // Parent specified EXACTLY, or in all other cases, just return the original spec + return measureSpec; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/NavigationBar.java b/library/main/src/com/android/setupwizardlib/view/NavigationBar.java index 9971bac..1044e56 100644 --- a/library/main/src/com/android/setupwizardlib/view/NavigationBar.java +++ b/library/main/src/com/android/setupwizardlib/view/NavigationBar.java @@ -21,14 +21,12 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build.VERSION_CODES; +import androidx.annotation.StyleableRes; import android.util.AttributeSet; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; - -import androidx.annotation.StyleableRes; - import com.android.setupwizardlib.R; /** @@ -40,104 +38,105 @@ import com.android.setupwizardlib.R; */ public class NavigationBar extends LinearLayout implements View.OnClickListener { - /** - * An interface to listen to events of the navigation bar, namely when the user clicks on the - * back or next button. - */ - public interface NavigationBarListener { - void onNavigateBack(); - void onNavigateNext(); + /** + * An interface to listen to events of the navigation bar, namely when the user clicks on the back + * or next button. + */ + public interface NavigationBarListener { + void onNavigateBack(); + + void onNavigateNext(); + } + + private static int getNavbarTheme(Context context) { + // Normally we can automatically guess the theme by comparing the foreground color against + // the background color. But we also allow specifying explicitly using suwNavBarTheme. + TypedArray attributes = + context.obtainStyledAttributes( + new int[] { + R.attr.suwNavBarTheme, android.R.attr.colorForeground, android.R.attr.colorBackground + }); + @StyleableRes int suwNavBarTheme = 0; + @StyleableRes int colorForeground = 1; + @StyleableRes int colorBackground = 2; + int theme = attributes.getResourceId(suwNavBarTheme, 0); + if (theme == 0) { + // Compare the value of the foreground against the background color to see if current + // theme is light-on-dark or dark-on-light. + float[] foregroundHsv = new float[3]; + float[] backgroundHsv = new float[3]; + Color.colorToHSV(attributes.getColor(colorForeground, 0), foregroundHsv); + Color.colorToHSV(attributes.getColor(colorBackground, 0), backgroundHsv); + boolean isDarkBg = foregroundHsv[2] > backgroundHsv[2]; + theme = isDarkBg ? R.style.SuwNavBarThemeDark : R.style.SuwNavBarThemeLight; } - - private static int getNavbarTheme(Context context) { - // Normally we can automatically guess the theme by comparing the foreground color against - // the background color. But we also allow specifying explicitly using suwNavBarTheme. - TypedArray attributes = context.obtainStyledAttributes( - new int[] { - R.attr.suwNavBarTheme, - android.R.attr.colorForeground, - android.R.attr.colorBackground }); - @StyleableRes int suwNavBarTheme = 0; - @StyleableRes int colorForeground = 1; - @StyleableRes int colorBackground = 2; - int theme = attributes.getResourceId(suwNavBarTheme, 0); - if (theme == 0) { - // Compare the value of the foreground against the background color to see if current - // theme is light-on-dark or dark-on-light. - float[] foregroundHsv = new float[3]; - float[] backgroundHsv = new float[3]; - Color.colorToHSV(attributes.getColor(colorForeground, 0), foregroundHsv); - Color.colorToHSV(attributes.getColor(colorBackground, 0), backgroundHsv); - boolean isDarkBg = foregroundHsv[2] > backgroundHsv[2]; - theme = isDarkBg ? R.style.SuwNavBarThemeDark : R.style.SuwNavBarThemeLight; - } - attributes.recycle(); - return theme; - } - - private static Context getThemedContext(Context context) { - final int theme = getNavbarTheme(context); - return new ContextThemeWrapper(context, theme); + attributes.recycle(); + return theme; + } + + private static Context getThemedContext(Context context) { + final int theme = getNavbarTheme(context); + return new ContextThemeWrapper(context, theme); + } + + private Button nextButton; + private Button backButton; + private Button moreButton; + private NavigationBarListener listener; + + public NavigationBar(Context context) { + super(getThemedContext(context)); + init(); + } + + public NavigationBar(Context context, AttributeSet attrs) { + super(getThemedContext(context), attrs); + init(); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public NavigationBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(getThemedContext(context), attrs, defStyleAttr); + init(); + } + + // All the constructors delegate to this init method. The 3-argument constructor is not + // available in LinearLayout before v11, so call super with the exact same arguments. + private void init() { + View.inflate(getContext(), R.layout.suw_navbar_view, this); + nextButton = (Button) findViewById(R.id.suw_navbar_next); + backButton = (Button) findViewById(R.id.suw_navbar_back); + moreButton = (Button) findViewById(R.id.suw_navbar_more); + } + + public Button getBackButton() { + return backButton; + } + + public Button getNextButton() { + return nextButton; + } + + public Button getMoreButton() { + return moreButton; + } + + public void setNavigationBarListener(NavigationBarListener listener) { + this.listener = listener; + if (this.listener != null) { + getBackButton().setOnClickListener(this); + getNextButton().setOnClickListener(this); } - - private Button mNextButton; - private Button mBackButton; - private Button mMoreButton; - private NavigationBarListener mListener; - - public NavigationBar(Context context) { - super(getThemedContext(context)); - init(); - } - - public NavigationBar(Context context, AttributeSet attrs) { - super(getThemedContext(context), attrs); - init(); - } - - @TargetApi(VERSION_CODES.HONEYCOMB) - public NavigationBar(Context context, AttributeSet attrs, int defStyleAttr) { - super(getThemedContext(context), attrs, defStyleAttr); - init(); - } - - // All the constructors delegate to this init method. The 3-argument constructor is not - // available in LinearLayout before v11, so call super with the exact same arguments. - private void init() { - View.inflate(getContext(), R.layout.suw_navbar_view, this); - mNextButton = (Button) findViewById(R.id.suw_navbar_next); - mBackButton = (Button) findViewById(R.id.suw_navbar_back); - mMoreButton = (Button) findViewById(R.id.suw_navbar_more); - } - - public Button getBackButton() { - return mBackButton; - } - - public Button getNextButton() { - return mNextButton; - } - - public Button getMoreButton() { - return mMoreButton; - } - - public void setNavigationBarListener(NavigationBarListener listener) { - mListener = listener; - if (mListener != null) { - getBackButton().setOnClickListener(this); - getNextButton().setOnClickListener(this); - } - } - - @Override - public void onClick(View view) { - if (mListener != null) { - if (view == getBackButton()) { - mListener.onNavigateBack(); - } else if (view == getNextButton()) { - mListener.onNavigateNext(); - } - } + } + + @Override + public void onClick(View view) { + if (listener != null) { + if (view == getBackButton()) { + listener.onNavigateBack(); + } else if (view == getNextButton()) { + listener.onNavigateNext(); + } } + } } diff --git a/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java b/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java index fe0bc8f..dd8c27f 100644 --- a/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java @@ -26,7 +26,6 @@ import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.view.WindowInsets; import android.widget.FrameLayout; - import com.android.setupwizardlib.R; /** @@ -40,74 +39,75 @@ import com.android.setupwizardlib.R; */ public class StatusBarBackgroundLayout extends FrameLayout { - private Drawable mStatusBarBackground; - private Object mLastInsets; // Use generic Object type for compatibility + private Drawable statusBarBackground; + private Object lastInsets; // Use generic Object type for compatibility - public StatusBarBackgroundLayout(Context context) { - super(context); - init(context, null, 0); - } + public StatusBarBackgroundLayout(Context context) { + super(context); + init(context, null, 0); + } - public StatusBarBackgroundLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } + public StatusBarBackgroundLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } - @TargetApi(VERSION_CODES.HONEYCOMB) - public StatusBarBackgroundLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } + @TargetApi(VERSION_CODES.HONEYCOMB) + public StatusBarBackgroundLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SuwStatusBarBackgroundLayout, defStyleAttr, 0); - final Drawable statusBarBackground = - a.getDrawable(R.styleable.SuwStatusBarBackgroundLayout_suwStatusBarBackground); - setStatusBarBackground(statusBarBackground); - a.recycle(); - } + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = + context.obtainStyledAttributes( + attrs, R.styleable.SuwStatusBarBackgroundLayout, defStyleAttr, 0); + final Drawable statusBarBackground = + a.getDrawable(R.styleable.SuwStatusBarBackgroundLayout_suwStatusBarBackground); + setStatusBarBackground(statusBarBackground); + a.recycle(); + } - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - if (mLastInsets == null) { - requestApplyInsets(); - } - } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (lastInsets == null) { + requestApplyInsets(); + } } + } - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - if (mLastInsets != null) { - final int insetTop = ((WindowInsets) mLastInsets).getSystemWindowInsetTop(); - if (insetTop > 0) { - mStatusBarBackground.setBounds(0, 0, getWidth(), insetTop); - mStatusBarBackground.draw(canvas); - } - } + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (lastInsets != null) { + final int insetTop = ((WindowInsets) lastInsets).getSystemWindowInsetTop(); + if (insetTop > 0) { + statusBarBackground.setBounds(0, 0, getWidth(), insetTop); + statusBarBackground.draw(canvas); } + } } + } - public void setStatusBarBackground(Drawable background) { - mStatusBarBackground = background; - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - setWillNotDraw(background == null); - setFitsSystemWindows(background != null); - invalidate(); - } + public void setStatusBarBackground(Drawable background) { + statusBarBackground = background; + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + setWillNotDraw(background == null); + setFitsSystemWindows(background != null); + invalidate(); } + } - public Drawable getStatusBarBackground() { - return mStatusBarBackground; - } + public Drawable getStatusBarBackground() { + return statusBarBackground; + } - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mLastInsets = insets; - return super.onApplyWindowInsets(insets); - } + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + lastInsets = insets; + return super.onApplyWindowInsets(insets); + } } diff --git a/library/main/src/com/android/setupwizardlib/view/StickyHeaderListView.java b/library/main/src/com/android/setupwizardlib/view/StickyHeaderListView.java index 58274f6..c226f29 100644 --- a/library/main/src/com/android/setupwizardlib/view/StickyHeaderListView.java +++ b/library/main/src/com/android/setupwizardlib/view/StickyHeaderListView.java @@ -29,7 +29,6 @@ import android.view.View; import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.widget.ListView; - import com.android.setupwizardlib.R; /** @@ -39,125 +38,129 @@ import com.android.setupwizardlib.R; * when the sticky element hits the top of the view. * *

There are a few things to note: + * *

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

There are a few things to note: + * *

    *
  1. The two supported scenarios are StickyHeaderScrollView -> subview (stickyContainer) -> - * sticky, and StickyHeaderScrollView -> container -> subview (sticky). - * The arrow (->) represents parent/child relationship and must be immediate child. + * sticky, and StickyHeaderScrollView -> container -> subview (sticky). The arrow (->) + * represents parent/child relationship and must be immediate child. *
  2. If fitsSystemWindows is true, then this will offset the sticking position by the height of - * the system decorations at the top of the screen. + * the system decorations at the top of the screen. *
  3. For versions before Honeycomb, this will behave like a regular ScrollView. *
* @@ -43,75 +44,75 @@ import android.view.WindowInsets; */ public class StickyHeaderScrollView extends BottomScrollView { - private View mSticky; - private View mStickyContainer; - private int mStatusBarInset = 0; + private View sticky; + private View stickyContainer; + private int statusBarInset = 0; - public StickyHeaderScrollView(Context context) { - super(context); - } + public StickyHeaderScrollView(Context context) { + super(context); + } - public StickyHeaderScrollView(Context context, AttributeSet attrs) { - super(context, attrs); - } + public StickyHeaderScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } - public StickyHeaderScrollView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public StickyHeaderScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - if (mSticky == null) { - updateStickyView(); - } - updateStickyHeaderPosition(); + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (sticky == null) { + updateStickyView(); } + updateStickyHeaderPosition(); + } - public void updateStickyView() { - mSticky = findViewWithTag("sticky"); - mStickyContainer = findViewWithTag("stickyContainer"); - } + public void updateStickyView() { + sticky = findViewWithTag("sticky"); + stickyContainer = findViewWithTag("stickyContainer"); + } - private void updateStickyHeaderPosition() { - // Note: for pre-Honeycomb the header will not be moved, so this ScrollView essentially - // behaves like a normal BottomScrollView. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - if (mSticky != null) { - // The view to draw when sticking to the top - final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky; - // The offset to draw the view at when sticky - final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0; - // Position of the draw target, relative to the outside of the scrollView - final int drawTop = drawTarget.getTop() - getScrollY(); - if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) { - // ScrollView translates the whole canvas so we have to compensate for that - drawTarget.setTranslationY(getScrollY() - drawOffset); - } else { - drawTarget.setTranslationY(0); - } - } + private void updateStickyHeaderPosition() { + // Note: for pre-Honeycomb the header will not be moved, so this ScrollView essentially + // behaves like a normal BottomScrollView. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + if (sticky != null) { + // The view to draw when sticking to the top + final View drawTarget = stickyContainer != null ? stickyContainer : sticky; + // The offset to draw the view at when sticky + final int drawOffset = stickyContainer != null ? sticky.getTop() : 0; + // Position of the draw target, relative to the outside of the scrollView + final int drawTop = drawTarget.getTop() - getScrollY(); + if (drawTop + drawOffset < statusBarInset || !drawTarget.isShown()) { + // ScrollView translates the whole canvas so we have to compensate for that + drawTarget.setTranslationY(getScrollY() - drawOffset); + } else { + drawTarget.setTranslationY(0); } + } } + } - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - updateStickyHeaderPosition(); - } + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + updateStickyHeaderPosition(); + } - @Override - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - if (getFitsSystemWindows()) { - mStatusBarInset = insets.getSystemWindowInsetTop(); - insets = insets.replaceSystemWindowInsets( - insets.getSystemWindowInsetLeft(), - 0, /* top */ - insets.getSystemWindowInsetRight(), - insets.getSystemWindowInsetBottom() - ); - } - return insets; + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if (getFitsSystemWindows()) { + statusBarInset = insets.getSystemWindowInsetTop(); + insets = + insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + 0, /* top */ + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); } + return insets; + } } diff --git a/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java b/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java index 10e91f4..526883c 100644 --- a/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java +++ b/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java @@ -24,60 +24,56 @@ import android.view.MotionEvent; import android.widget.TextView; /** - * A movement method that tracks the last result of whether touch events are handled. This is - * used to patch the return value of {@link TextView#onTouchEvent} so that it consumes the touch - * events only when the movement method says the event is consumed. + * A movement method that tracks the last result of whether touch events are handled. This is used + * to patch the return value of {@link TextView#onTouchEvent} so that it consumes the touch events + * only when the movement method says the event is consumed. */ public interface TouchableMovementMethod { - /** - * @return The last touch event received in {@link MovementMethod#onTouchEvent} - */ - MotionEvent getLastTouchEvent(); + /** @return The last touch event received in {@link MovementMethod#onTouchEvent} */ + MotionEvent getLastTouchEvent(); - /** - * @return The return value of the last {@link MovementMethod#onTouchEvent}, or whether the - * last touch event should be considered handled by the text view - */ - boolean isLastTouchEventHandled(); + /** + * @return The return value of the last {@link MovementMethod#onTouchEvent}, or whether the last + * touch event should be considered handled by the text view + */ + boolean isLastTouchEventHandled(); - /** - * An extension of LinkMovementMethod that tracks whether the event is handled when it is - * touched. - */ - class TouchableLinkMovementMethod extends LinkMovementMethod - implements TouchableMovementMethod { + /** + * An extension of LinkMovementMethod that tracks whether the event is handled when it is touched. + */ + class TouchableLinkMovementMethod extends LinkMovementMethod implements TouchableMovementMethod { - public static TouchableLinkMovementMethod getInstance() { - return new TouchableLinkMovementMethod(); - } + public static TouchableLinkMovementMethod getInstance() { + return new TouchableLinkMovementMethod(); + } - boolean mLastEventResult = false; - MotionEvent mLastEvent; + boolean lastEventResult = false; + MotionEvent lastEvent; - @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { - mLastEvent = event; - boolean result = super.onTouchEvent(widget, buffer, event); - if (event.getAction() == MotionEvent.ACTION_DOWN) { - // Unfortunately, LinkMovementMethod extends ScrollMovementMethod, and it always - // consume the down event. So here we use the selection instead as a hint of whether - // the down event landed on a link. - mLastEventResult = Selection.getSelectionStart(buffer) != -1; - } else { - mLastEventResult = result; - } - return result; - } + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + lastEvent = event; + boolean result = super.onTouchEvent(widget, buffer, event); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + // Unfortunately, LinkMovementMethod extends ScrollMovementMethod, and it always + // consume the down event. So here we use the selection instead as a hint of whether + // the down event landed on a link. + lastEventResult = Selection.getSelectionStart(buffer) != -1; + } else { + lastEventResult = result; + } + return result; + } - @Override - public MotionEvent getLastTouchEvent() { - return mLastEvent; - } + @Override + public MotionEvent getLastTouchEvent() { + return lastEvent; + } - @Override - public boolean isLastTouchEventHandled() { - return mLastEventResult; - } + @Override + public boolean isLastTouchEventHandled() { + return lastEventResult; } + } } diff --git a/library/platform/src/com/android/setupwizardlib/view/NavigationBarButton.java b/library/platform/src/com/android/setupwizardlib/view/NavigationBarButton.java index 45d3737..5d14aa7 100644 --- a/library/platform/src/com/android/setupwizardlib/view/NavigationBarButton.java +++ b/library/platform/src/com/android/setupwizardlib/view/NavigationBarButton.java @@ -22,11 +22,11 @@ import android.widget.Button; public class NavigationBarButton extends Button { - public NavigationBarButton(Context context) { - super(context); - } + public NavigationBarButton(Context context) { + super(context); + } - public NavigationBarButton(Context context, AttributeSet attrs) { - super(context, attrs); - } + public NavigationBarButton(Context context, AttributeSet attrs) { + super(context, attrs); + } } diff --git a/library/platform/src/com/android/setupwizardlib/view/RichTextView.java b/library/platform/src/com/android/setupwizardlib/view/RichTextView.java index fa68a68..7eb29c6 100644 --- a/library/platform/src/com/android/setupwizardlib/view/RichTextView.java +++ b/library/platform/src/com/android/setupwizardlib/view/RichTextView.java @@ -27,15 +27,14 @@ import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.TextView; - import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; import com.android.setupwizardlib.span.SpanHelper; import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod; /** - * An extension of TextView that automatically replaces the annotation tags as specified in - * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)} + * An extension of TextView that automatically replaces the annotation tags as specified in {@link + * SpanHelper#replaceSpan(android.text.Spannable, Object, Object)} * *

Note: The accessibility interaction for ClickableSpans (and therefore LinkSpans) are built * into platform in O, although the interaction paradigm is different. (See b/17726921). In this @@ -44,133 +43,133 @@ import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMove */ public class RichTextView extends TextView implements OnLinkClickListener { - /* static section */ - - private static final String TAG = "RichTextView"; - - private static final String ANNOTATION_LINK = "link"; - private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance"; - - /** - * Replace <annotation> tags in strings to become their respective types. Currently 2 - * types are supported: - *

    - *
  1. <annotation link="foobar"> will create a - * {@link com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key - * "foobar"
  2. - *
  3. <annotation textAppearance="TextAppearance.FooBar"> will create a - * {@link android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar
  4. - *
- */ - public static CharSequence getRichText(Context context, CharSequence text) { - if (text instanceof Spanned) { - final SpannableString spannable = new SpannableString(text); - final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class); - for (Annotation span : spans) { - final String key = span.getKey(); - if (ANNOTATION_TEXT_APPEARANCE.equals(key)) { - String textAppearance = span.getValue(); - final int style = context.getResources() - .getIdentifier(textAppearance, "style", context.getPackageName()); - if (style == 0) { - Log.w(TAG, "Cannot find resource: " + style); - } - final TextAppearanceSpan textAppearanceSpan = - new TextAppearanceSpan(context, style); - SpanHelper.replaceSpan(spannable, span, textAppearanceSpan); - } else if (ANNOTATION_LINK.equals(key)) { - LinkSpan link = new LinkSpan(span.getValue()); - SpanHelper.replaceSpan(spannable, span, link); - } - } - return spannable; + /* static section */ + + private static final String TAG = "RichTextView"; + + private static final String ANNOTATION_LINK = "link"; + private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance"; + + /** + * Replace <annotation> tags in strings to become their respective types. Currently 2 types + * are supported: + * + *
    + *
  1. <annotation link="foobar"> will create a {@link + * com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key "foobar" + *
  2. <annotation textAppearance="TextAppearance.FooBar"> will create a {@link + * android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar + *
+ */ + public static CharSequence getRichText(Context context, CharSequence text) { + if (text instanceof Spanned) { + final SpannableString spannable = new SpannableString(text); + final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class); + for (Annotation span : spans) { + final String key = span.getKey(); + if (ANNOTATION_TEXT_APPEARANCE.equals(key)) { + String textAppearance = span.getValue(); + final int style = + context + .getResources() + .getIdentifier(textAppearance, "style", context.getPackageName()); + if (style == 0) { + Log.w(TAG, "Cannot find resource: " + style); + } + final TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(context, style); + SpanHelper.replaceSpan(spannable, span, textAppearanceSpan); + } else if (ANNOTATION_LINK.equals(key)) { + LinkSpan link = new LinkSpan(span.getValue()); + SpanHelper.replaceSpan(spannable, span, link); } - return text; - } - - /* non-static section */ - - private OnLinkClickListener mOnLinkClickListener; - - public RichTextView(Context context) { - super(context); - } - - public RichTextView(Context context, AttributeSet attrs) { - super(context, attrs); + } + return spannable; } - - @Override - public void setText(CharSequence text, BufferType type) { - text = getRichText(getContext(), text); - // Set text first before doing anything else because setMovementMethod internally calls - // setText. This in turn ends up calling this method with mText as the first parameter - super.setText(text, type); - boolean hasLinks = hasLinks(text); - - if (hasLinks) { - // When a TextView has a movement method, it will set the view to clickable. This makes - // View.onTouchEvent always return true and consumes the touch event, essentially - // nullifying any return values of MovementMethod.onTouchEvent. - // To still allow propagating touch events to the parent when this view doesn't have - // links, we only set the movement method here if the text contains links. - setMovementMethod(TouchableLinkMovementMethod.getInstance()); - } else { - setMovementMethod(null); - } - // ExploreByTouchHelper automatically enables focus for RichTextView - // even though it may not have any links. Causes problems during talkback - // as individual TextViews consume touch events and thereby reducing the focus window - // shown by Talkback. Disable focus if there are no links - setFocusable(hasLinks); - // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is - // focusable in touch mode, we may be focused when the screen is first shown, and starting - // a screen halfway scrolled down is confusing to the user. - setRevealOnFocusHint(false); - setFocusableInTouchMode(hasLinks); + return text; + } + + /* non-static section */ + + private OnLinkClickListener mOnLinkClickListener; + + public RichTextView(Context context) { + super(context); + } + + public RichTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setText(CharSequence text, BufferType type) { + text = getRichText(getContext(), text); + // Set text first before doing anything else because setMovementMethod internally calls + // setText. This in turn ends up calling this method with mText as the first parameter + super.setText(text, type); + boolean hasLinks = hasLinks(text); + + if (hasLinks) { + // When a TextView has a movement method, it will set the view to clickable. This makes + // View.onTouchEvent always return true and consumes the touch event, essentially + // nullifying any return values of MovementMethod.onTouchEvent. + // To still allow propagating touch events to the parent when this view doesn't have + // links, we only set the movement method here if the text contains links. + setMovementMethod(TouchableLinkMovementMethod.getInstance()); + } else { + setMovementMethod(null); } - - private boolean hasLinks(CharSequence text) { - if (text instanceof Spanned) { - final ClickableSpan[] spans = - ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); - return spans.length > 0; - } - return false; + // ExploreByTouchHelper automatically enables focus for RichTextView + // even though it may not have any links. Causes problems during talkback + // as individual TextViews consume touch events and thereby reducing the focus window + // shown by Talkback. Disable focus if there are no links + setFocusable(hasLinks); + // Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is + // focusable in touch mode, we may be focused when the screen is first shown, and starting + // a screen halfway scrolled down is confusing to the user. + setRevealOnFocusHint(false); + setFocusableInTouchMode(hasLinks); + } + + private boolean hasLinks(CharSequence text) { + if (text instanceof Spanned) { + final ClickableSpan[] spans = + ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); + return spans.length > 0; } - - @Override - @SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called - public boolean onTouchEvent(MotionEvent event) { - // Since View#onTouchEvent always return true if the view is clickable (which is the case - // when a TextView has a movement method), override the implementation to allow the movement - // method, if it implements TouchableMovementMethod, to say that the touch is not handled, - // allowing the event to bubble up to the parent view. - boolean superResult = super.onTouchEvent(event); - MovementMethod movementMethod = getMovementMethod(); - if (movementMethod instanceof TouchableMovementMethod) { - TouchableMovementMethod touchableMovementMethod = - (TouchableMovementMethod) movementMethod; - if (touchableMovementMethod.getLastTouchEvent() == event) { - return touchableMovementMethod.isLastTouchEventHandled(); - } - } - return superResult; + return false; + } + + @Override + @SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called + public boolean onTouchEvent(MotionEvent event) { + // Since View#onTouchEvent always return true if the view is clickable (which is the case + // when a TextView has a movement method), override the implementation to allow the movement + // method, if it implements TouchableMovementMethod, to say that the touch is not handled, + // allowing the event to bubble up to the parent view. + boolean superResult = super.onTouchEvent(event); + MovementMethod movementMethod = getMovementMethod(); + if (movementMethod instanceof TouchableMovementMethod) { + TouchableMovementMethod touchableMovementMethod = (TouchableMovementMethod) movementMethod; + if (touchableMovementMethod.getLastTouchEvent() == event) { + return touchableMovementMethod.isLastTouchEventHandled(); + } } + return superResult; + } - public void setOnLinkClickListener(OnLinkClickListener listener) { - mOnLinkClickListener = listener; - } + public void setOnLinkClickListener(OnLinkClickListener listener) { + mOnLinkClickListener = listener; + } - public OnLinkClickListener getOnLinkClickListener() { - return mOnLinkClickListener; - } + public OnLinkClickListener getOnLinkClickListener() { + return mOnLinkClickListener; + } - @Override - public boolean onLinkClick(LinkSpan span) { - if (mOnLinkClickListener != null) { - return mOnLinkClickListener.onLinkClick(span); - } - return false; + @Override + public boolean onLinkClick(LinkSpan span) { + if (mOnLinkClickListener != null) { + return mOnLinkClickListener.onLinkClick(span); } + return false; + } } diff --git a/library/platform/test/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/library/platform/test/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java index 3d11e12..212b52c 100644 --- a/library/platform/test/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java +++ b/library/platform/test/src/com/android/setupwizardlib/test/util/DrawingTestActivity.java @@ -25,5 +25,4 @@ import android.app.Activity; * * @see DrawingTestHelper */ -public class DrawingTestActivity extends Activity { -} +public class DrawingTestActivity extends Activity {} diff --git a/library/recyclerview/src/com/android/setupwizardlib/DividerItemDecoration.java b/library/recyclerview/src/com/android/setupwizardlib/DividerItemDecoration.java index 2db17f8..128ed6b 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/DividerItemDecoration.java +++ b/library/recyclerview/src/com/android/setupwizardlib/DividerItemDecoration.java @@ -21,223 +21,207 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.view.View; - import androidx.annotation.IntDef; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.RecyclerView; - +import android.view.View; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * An {@link androidx.recyclerview.widget.RecyclerView.ItemDecoration} for RecyclerView to draw - * dividers between items. This ItemDecoration will draw the drawable specified by - * {@link #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by - * default, and the behavior of whether the divider is shown can be customized by subclassing - * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. + * dividers between items. This ItemDecoration will draw the drawable specified by {@link + * #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by default, + * and the behavior of whether the divider is shown can be customized by subclassing {@link + * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. * *

Modified from v14 PreferenceFragment.DividerDecoration. */ public class DividerItemDecoration extends RecyclerView.ItemDecoration { - /* static section */ + /* static section */ + + /** + * An interface to be implemented by a {@link RecyclerView.ViewHolder} which controls whether + * dividers should be shown above and below that item. + */ + public interface DividedViewHolder { /** - * An interface to be implemented by a {@link RecyclerView.ViewHolder} which controls whether - * dividers should be shown above and below that item. + * Returns whether divider is allowed above this item. A divider will be shown only if both + * items immediately above and below it allows this divider. */ - public interface DividedViewHolder { - - /** - * Returns whether divider is allowed above this item. A divider will be shown only if both - * items immediately above and below it allows this divider. - */ - boolean isDividerAllowedAbove(); - - /** - * Returns whether divider is allowed below this item. A divider will be shown only if both - * items immediately above and below it allows this divider. - */ - boolean isDividerAllowedBelow(); - } - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - DIVIDER_CONDITION_EITHER, - DIVIDER_CONDITION_BOTH}) - public @interface DividerCondition {} - - public static final int DIVIDER_CONDITION_EITHER = 0; - public static final int DIVIDER_CONDITION_BOTH = 1; + boolean isDividerAllowedAbove(); /** - * @deprecated Use {@link #DividerItemDecoration(android.content.Context)} + * Returns whether divider is allowed below this item. A divider will be shown only if both + * items immediately above and below it allows this divider. */ - @Deprecated - public static DividerItemDecoration getDefault(Context context) { - return new DividerItemDecoration(context); - } - - /* non-static section */ - - private Drawable mDivider; - private int mDividerHeight; - private int mDividerIntrinsicHeight; - @DividerCondition - private int mDividerCondition; + boolean isDividerAllowedBelow(); + } - public DividerItemDecoration() { - } + @Retention(RetentionPolicy.SOURCE) + @IntDef({DIVIDER_CONDITION_EITHER, DIVIDER_CONDITION_BOTH}) + public @interface DividerCondition {} - public DividerItemDecoration(Context context) { - final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration); - final Drawable divider = a.getDrawable( - R.styleable.SuwDividerItemDecoration_android_listDivider); - final int dividerHeight = a.getDimensionPixelSize( - R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0); - @DividerCondition final int dividerCondition = a.getInt( - R.styleable.SuwDividerItemDecoration_suwDividerCondition, - DIVIDER_CONDITION_EITHER); - a.recycle(); - - setDivider(divider); - setDividerHeight(dividerHeight); - setDividerCondition(dividerCondition); - } + public static final int DIVIDER_CONDITION_EITHER = 0; + public static final int DIVIDER_CONDITION_BOTH = 1; - @Override - public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { - if (mDivider == null) { - return; - } - final int childCount = parent.getChildCount(); - final int width = parent.getWidth(); - final int dividerHeight = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight; - for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { - final View view = parent.getChildAt(childViewIndex); - if (shouldDrawDividerBelow(view, parent)) { - final int top = (int) ViewCompat.getY(view) + view.getHeight(); - mDivider.setBounds(0, top, width, top + dividerHeight); - mDivider.draw(c); - } - } - } + /** @deprecated Use {@link #DividerItemDecoration(android.content.Context)} */ + @Deprecated + public static DividerItemDecoration getDefault(Context context) { + return new DividerItemDecoration(context); + } - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, - RecyclerView.State state) { - if (shouldDrawDividerBelow(view, parent)) { - outRect.bottom = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight; - } - } + /* non-static section */ - private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { - final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); - final int index = holder.getLayoutPosition(); - final int lastItemIndex = parent.getAdapter().getItemCount() - 1; - if (isDividerAllowedBelow(holder)) { - if (mDividerCondition == DIVIDER_CONDITION_EITHER) { - // Draw the divider without consulting the next item if we only - // need permission for either above or below. - return true; - } - } else if (mDividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) { - // Don't draw if the current view holder doesn't allow drawing below - // and the current theme requires permission for both the item below and above. - // Also, if this is the last item, there is no item below to ask permission - // for whether to draw a divider above, so don't draw it. - return false; - } - // Require permission from index below to draw the divider. - if (index < lastItemIndex) { - final RecyclerView.ViewHolder nextHolder = - parent.findViewHolderForLayoutPosition(index + 1); - if (!isDividerAllowedAbove(nextHolder)) { - // Don't draw if the next view holder doesn't allow drawing above - return false; - } - } - return true; - } - - /** - * Whether a divider is allowed above the view holder. The allowed values will be combined - * according to {@link #getDividerCondition()}. The default implementation delegates to - * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows - * the divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can - * override this to give more information to decide whether a divider should be drawn. - * - * @return True if divider is allowed above this view holder. - */ - protected boolean isDividerAllowedAbove(RecyclerView.ViewHolder viewHolder) { - return !(viewHolder instanceof DividedViewHolder) - || ((DividedViewHolder) viewHolder).isDividerAllowedAbove(); - } + private Drawable divider; + private int dividerHeight; + private int dividerIntrinsicHeight; + @DividerCondition private int dividerCondition; - /** - * Whether a divider is allowed below the view holder. The allowed values will be combined - * according to {@link #getDividerCondition()}. The default implementation delegates to - * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows - * the divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can - * override this to give more information to decide whether a divider should be drawn. - * - * @return True if divider is allowed below this view holder. - */ - protected boolean isDividerAllowedBelow(RecyclerView.ViewHolder viewHolder) { - return !(viewHolder instanceof DividedViewHolder) - || ((DividedViewHolder) viewHolder).isDividerAllowedBelow(); - } + public DividerItemDecoration() {} - /** - * Sets the drawable to be used as the divider. - */ - public void setDivider(Drawable divider) { - if (divider != null) { - mDividerIntrinsicHeight = divider.getIntrinsicHeight(); - } else { - mDividerIntrinsicHeight = 0; - } - mDivider = divider; + public DividerItemDecoration(Context context) { + final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration); + final Drawable divider = + a.getDrawable(R.styleable.SuwDividerItemDecoration_android_listDivider); + final int dividerHeight = + a.getDimensionPixelSize(R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0); + @DividerCondition + final int dividerCondition = + a.getInt( + R.styleable.SuwDividerItemDecoration_suwDividerCondition, DIVIDER_CONDITION_EITHER); + a.recycle(); + + setDivider(divider); + setDividerHeight(dividerHeight); + setDividerCondition(dividerCondition); + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (divider == null) { + return; } - - /** - * Gets the drawable currently used as the divider. - */ - public Drawable getDivider() { - return mDivider; + final int childCount = parent.getChildCount(); + final int width = parent.getWidth(); + final int dividerHeight = this.dividerHeight != 0 ? this.dividerHeight : dividerIntrinsicHeight; + for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { + final View view = parent.getChildAt(childViewIndex); + if (shouldDrawDividerBelow(view, parent)) { + final int top = (int) ViewCompat.getY(view) + view.getHeight(); + divider.setBounds(0, top, width, top + dividerHeight); + divider.draw(c); + } } + } - /** - * Sets the divider height, in pixels. - */ - public void setDividerHeight(int dividerHeight) { - mDividerHeight = dividerHeight; + @Override + public void getItemOffsets( + Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + if (shouldDrawDividerBelow(view, parent)) { + outRect.bottom = dividerHeight != 0 ? dividerHeight : dividerIntrinsicHeight; } - - /** - * Gets the divider height, in pixels. - */ - public int getDividerHeight() { - return mDividerHeight; + } + + private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { + final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); + final int index = holder.getLayoutPosition(); + final int lastItemIndex = parent.getAdapter().getItemCount() - 1; + if (isDividerAllowedBelow(holder)) { + if (dividerCondition == DIVIDER_CONDITION_EITHER) { + // Draw the divider without consulting the next item if we only + // need permission for either above or below. + return true; + } + } else if (dividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) { + // Don't draw if the current view holder doesn't allow drawing below + // and the current theme requires permission for both the item below and above. + // Also, if this is the last item, there is no item below to ask permission + // for whether to draw a divider above, so don't draw it. + return false; } - - /** - * Sets whether the divider needs permission from both the item view holder below - * and above from where the divider would draw itself or just needs permission from - * one or the other before drawing itself. - */ - public void setDividerCondition(@DividerCondition int dividerCondition) { - mDividerCondition = dividerCondition; + // Require permission from index below to draw the divider. + if (index < lastItemIndex) { + final RecyclerView.ViewHolder nextHolder = parent.findViewHolderForLayoutPosition(index + 1); + if (!isDividerAllowedAbove(nextHolder)) { + // Don't draw if the next view holder doesn't allow drawing above + return false; + } } - - /** - * Gets whether the divider needs permission from both the item view holder below - * and above from where the divider would draw itself or just needs permission from - * one or the other before drawing itself. - */ - @DividerCondition - public int getDividerCondition() { - return mDividerCondition; + return true; + } + + /** + * Whether a divider is allowed above the view holder. The allowed values will be combined + * according to {@link #getDividerCondition()}. The default implementation delegates to {@link + * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows the + * divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can override + * this to give more information to decide whether a divider should be drawn. + * + * @return True if divider is allowed above this view holder. + */ + protected boolean isDividerAllowedAbove(RecyclerView.ViewHolder viewHolder) { + return !(viewHolder instanceof DividedViewHolder) + || ((DividedViewHolder) viewHolder).isDividerAllowedAbove(); + } + + /** + * Whether a divider is allowed below the view holder. The allowed values will be combined + * according to {@link #getDividerCondition()}. The default implementation delegates to {@link + * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows the + * divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can override + * this to give more information to decide whether a divider should be drawn. + * + * @return True if divider is allowed below this view holder. + */ + protected boolean isDividerAllowedBelow(RecyclerView.ViewHolder viewHolder) { + return !(viewHolder instanceof DividedViewHolder) + || ((DividedViewHolder) viewHolder).isDividerAllowedBelow(); + } + + /** Sets the drawable to be used as the divider. */ + public void setDivider(Drawable divider) { + if (divider != null) { + dividerIntrinsicHeight = divider.getIntrinsicHeight(); + } else { + dividerIntrinsicHeight = 0; } + this.divider = divider; + } + + /** Gets the drawable currently used as the divider. */ + public Drawable getDivider() { + return divider; + } + + /** Sets the divider height, in pixels. */ + public void setDividerHeight(int dividerHeight) { + this.dividerHeight = dividerHeight; + } + + /** Gets the divider height, in pixels. */ + public int getDividerHeight() { + return dividerHeight; + } + + /** + * Sets whether the divider needs permission from both the item view holder below and above from + * where the divider would draw itself or just needs permission from one or the other before + * drawing itself. + */ + public void setDividerCondition(@DividerCondition int dividerCondition) { + this.dividerCondition = dividerCondition; + } + + /** + * Gets whether the divider needs permission from both the item view holder below and above from + * where the divider would draw itself or just needs permission from one or the other before + * drawing itself. + */ + @DividerCondition + public int getDividerCondition() { + return dividerCondition; + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/GlifPreferenceLayout.java b/library/recyclerview/src/com/android/setupwizardlib/GlifPreferenceLayout.java index af1a739..795fdad 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/GlifPreferenceLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/GlifPreferenceLayout.java @@ -18,21 +18,20 @@ package com.android.setupwizardlib; import android.content.Context; import android.os.Bundle; +import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.template.RecyclerMixin; /** * A layout to be used with {@code PreferenceFragment} in v14 support library. This can be specified - * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in - * {@code app:preferenceTheme}. + * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in {@code + * app:preferenceTheme}. + * + *

Example: * - *

Example: *

{@code
  * <style android:name="MyActivityTheme">
  *     <item android:name="preferenceTheme">@style/MyPreferenceTheme</item>
@@ -47,10 +46,11 @@ import com.android.setupwizardlib.template.RecyclerMixin;
  * </style>
  * }
* - * where {@code my_preference_layout} is a layout that contains - * {@link com.android.setupwizardlib.GlifPreferenceLayout}. + * where {@code my_preference_layout} is a layout that contains {@link + * com.android.setupwizardlib.GlifPreferenceLayout}. + * + *

Example: * - *

Example: *

{@code
  * <com.android.setupwizardlib.GlifPreferenceLayout
  *     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -59,60 +59,57 @@ import com.android.setupwizardlib.template.RecyclerMixin;
  *     android:layout_height="match_parent" />
  * }
* - *

Fragments using this layout must delegate {@code onCreateRecyclerView} to the - * implementation in this class: - * {@link #onCreateRecyclerView(android.view.LayoutInflater, android.view.ViewGroup, - * android.os.Bundle)} + *

Fragments using this layout must delegate {@code onCreateRecyclerView} to the + * implementation in this class: {@link #onCreateRecyclerView(android.view.LayoutInflater, + * android.view.ViewGroup, android.os.Bundle)} */ public class GlifPreferenceLayout extends GlifRecyclerLayout { - public GlifPreferenceLayout(Context context) { - super(context); - } + public GlifPreferenceLayout(Context context) { + super(context); + } - public GlifPreferenceLayout(Context context, int template, int containerId) { - super(context, template, containerId); - } + public GlifPreferenceLayout(Context context, int template, int containerId) { + super(context, template, containerId); + } - public GlifPreferenceLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } + public GlifPreferenceLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } - public GlifPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public GlifPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_layout_content; - } - return super.findContainer(containerId); + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_layout_content; } + return super.findContainer(containerId); + } - /** - * This method must be called in {@code PreferenceFragment#onCreateRecyclerView}. - */ - public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, - Bundle savedInstanceState) { - return mRecyclerMixin.getRecyclerView(); - } + /** This method must be called in {@code PreferenceFragment#onCreateRecyclerView}. */ + public RecyclerView onCreateRecyclerView( + LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + return mRecyclerMixin.getRecyclerView(); + } - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_glif_preference_template; - } - return super.onInflateTemplate(inflater, template); + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_glif_preference_template; } + return super.onInflateTemplate(inflater, template); + } - @Override - protected void onTemplateInflated() { - // Inflate the recycler view here, so attributes on the decoration views can be applied - // immediately. - final LayoutInflater inflater = LayoutInflater.from(getContext()); - RecyclerView recyclerView = (RecyclerView) inflater.inflate( - R.layout.suw_glif_preference_recycler_view, this, false); - mRecyclerMixin = new RecyclerMixin(this, recyclerView); - } + @Override + protected void onTemplateInflated() { + // Inflate the recycler view here, so attributes on the decoration views can be applied + // immediately. + final LayoutInflater inflater = LayoutInflater.from(getContext()); + RecyclerView recyclerView = + (RecyclerView) inflater.inflate(R.layout.suw_glif_preference_recycler_view, this, false); + mRecyclerMixin = new RecyclerMixin(this, recyclerView); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java b/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java index 7e0b1b7..2595b79 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java @@ -20,15 +20,13 @@ import android.annotation.TargetApi; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Build.VERSION_CODES; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - import com.android.setupwizardlib.template.RecyclerMixin; import com.android.setupwizardlib.template.RecyclerViewScrollHandlingDelegate; import com.android.setupwizardlib.template.RequireScrollMixin; @@ -39,157 +37,137 @@ import com.android.setupwizardlib.template.RequireScrollMixin; */ public class GlifRecyclerLayout extends GlifLayout { - protected RecyclerMixin mRecyclerMixin; - - public GlifRecyclerLayout(Context context) { - this(context, 0, 0); - } - - public GlifRecyclerLayout(Context context, int template) { - this(context, template, 0); - } - - public GlifRecyclerLayout(Context context, int template, int containerId) { - super(context, template, containerId); - init(context, null, 0); - } - - public GlifRecyclerLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - @TargetApi(VERSION_CODES.HONEYCOMB) - public GlifRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - mRecyclerMixin.parseAttributes(attrs, defStyleAttr); - registerMixin(RecyclerMixin.class, mRecyclerMixin); - - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - requireScrollMixin.setScrollHandlingDelegate( - new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mRecyclerMixin.onLayout(); - } - - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_glif_recycler_template; - } - return super.onInflateTemplate(inflater, template); - } - - @Override - protected void onTemplateInflated() { - final View recyclerView = findViewById(R.id.suw_recycler_view); - if (recyclerView instanceof RecyclerView) { - mRecyclerMixin = new RecyclerMixin(this, (RecyclerView) recyclerView); - } else { - throw new IllegalStateException( - "GlifRecyclerLayout should use a template with recycler view"); - } - } - - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_recycler_view; - } - return super.findContainer(containerId); - } - - @Override - // Returning generic type is the common pattern used for findViewBy* methods - @SuppressWarnings("TypeParameterUnusedInFormals") - public T findManagedViewById(int id) { - final View header = mRecyclerMixin.getHeader(); - if (header != null) { - final T view = header.findViewById(id); - if (view != null) { - return view; - } - } - return super.findViewById(id); - } - - /** - * @see RecyclerMixin#setDividerItemDecoration(DividerItemDecoration) - */ - public void setDividerItemDecoration(DividerItemDecoration decoration) { - mRecyclerMixin.setDividerItemDecoration(decoration); - } - - /** - * @see RecyclerMixin#getRecyclerView() - */ - public RecyclerView getRecyclerView() { - return mRecyclerMixin.getRecyclerView(); - } - - /** - * @see RecyclerMixin#setAdapter(Adapter) - */ - public void setAdapter(Adapter adapter) { - mRecyclerMixin.setAdapter(adapter); - } - - /** - * @see RecyclerMixin#getAdapter() - */ - public Adapter getAdapter() { - return mRecyclerMixin.getAdapter(); - } - - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - mRecyclerMixin.setDividerInset(inset); - } - - /** - * @see RecyclerMixin#setDividerInset(int) - */ - public void setDividerInsets(int start, int end) { - mRecyclerMixin.setDividerInsets(start, end); - } - - /** - * @deprecated Use {@link #getDividerInsetStart()} instead. - */ - @Deprecated - public int getDividerInset() { - return mRecyclerMixin.getDividerInset(); - } - - /** - * @see RecyclerMixin#getDividerInsetStart() - */ - public int getDividerInsetStart() { - return mRecyclerMixin.getDividerInsetStart(); - } - - /** - * @see RecyclerMixin#getDividerInsetEnd() - */ - public int getDividerInsetEnd() { - return mRecyclerMixin.getDividerInsetEnd(); - } - - /** - * @see RecyclerMixin#getDivider() - */ - public Drawable getDivider() { - return mRecyclerMixin.getDivider(); - } + protected RecyclerMixin mRecyclerMixin; + + public GlifRecyclerLayout(Context context) { + this(context, 0, 0); + } + + public GlifRecyclerLayout(Context context, int template) { + this(context, template, 0); + } + + public GlifRecyclerLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, 0); + } + + public GlifRecyclerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public GlifRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + mRecyclerMixin.parseAttributes(attrs, defStyleAttr); + registerMixin(RecyclerMixin.class, mRecyclerMixin); + + final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); + requireScrollMixin.setScrollHandlingDelegate( + new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mRecyclerMixin.onLayout(); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_glif_recycler_template; + } + return super.onInflateTemplate(inflater, template); + } + + @Override + protected void onTemplateInflated() { + final View recyclerView = findViewById(R.id.suw_recycler_view); + if (recyclerView instanceof RecyclerView) { + mRecyclerMixin = new RecyclerMixin(this, (RecyclerView) recyclerView); + } else { + throw new IllegalStateException( + "GlifRecyclerLayout should use a template with recycler view"); + } + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_recycler_view; + } + return super.findContainer(containerId); + } + + @Override + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public T findManagedViewById(int id) { + final View header = mRecyclerMixin.getHeader(); + if (header != null) { + final T view = header.findViewById(id); + if (view != null) { + return view; + } + } + return super.findViewById(id); + } + + /** @see RecyclerMixin#setDividerItemDecoration(DividerItemDecoration) */ + public void setDividerItemDecoration(DividerItemDecoration decoration) { + mRecyclerMixin.setDividerItemDecoration(decoration); + } + + /** @see RecyclerMixin#getRecyclerView() */ + public RecyclerView getRecyclerView() { + return mRecyclerMixin.getRecyclerView(); + } + + /** @see RecyclerMixin#setAdapter(Adapter) */ + public void setAdapter(Adapter adapter) { + mRecyclerMixin.setAdapter(adapter); + } + + /** @see RecyclerMixin#getAdapter() */ + public Adapter getAdapter() { + return mRecyclerMixin.getAdapter(); + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + mRecyclerMixin.setDividerInset(inset); + } + + /** @see RecyclerMixin#setDividerInset(int) */ + public void setDividerInsets(int start, int end) { + mRecyclerMixin.setDividerInsets(start, end); + } + + /** @deprecated Use {@link #getDividerInsetStart()} instead. */ + @Deprecated + public int getDividerInset() { + return mRecyclerMixin.getDividerInset(); + } + + /** @see RecyclerMixin#getDividerInsetStart() */ + public int getDividerInsetStart() { + return mRecyclerMixin.getDividerInsetStart(); + } + + /** @see RecyclerMixin#getDividerInsetEnd() */ + public int getDividerInsetEnd() { + return mRecyclerMixin.getDividerInsetEnd(); + } + + /** @see RecyclerMixin#getDivider() */ + public Drawable getDivider() { + return mRecyclerMixin.getDivider(); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java index 670c309..e9aa329 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardPreferenceLayout.java @@ -18,21 +18,20 @@ package com.android.setupwizardlib; import android.content.Context; import android.os.Bundle; +import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.template.RecyclerMixin; /** * A layout to be used with {@code PreferenceFragment} in v14 support library. This can be specified - * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in - * {@code app:preferenceTheme}. + * as the {@code android:layout} in the {@code app:preferenceFragmentStyle} in {@code + * app:preferenceTheme}. + * + *

Example: * - *

Example: *

{@code
  * <style android:name="MyActivityTheme">
  *     <item android:name="preferenceTheme">@style/MyPreferenceTheme</item>
@@ -47,10 +46,11 @@ import com.android.setupwizardlib.template.RecyclerMixin;
  * </style>
  * }
* - * where {@code my_preference_layout} is a layout that contains - * {@link com.android.setupwizardlib.SetupWizardPreferenceLayout}. + * where {@code my_preference_layout} is a layout that contains {@link + * com.android.setupwizardlib.SetupWizardPreferenceLayout}. + * + *

Example: * - *

Example: *

{@code
  * <com.android.setupwizardlib.SetupWizardPreferenceLayout
  *     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -59,58 +59,56 @@ import com.android.setupwizardlib.template.RecyclerMixin;
  *     android:layout_height="match_parent" />
  * }
* - *

Fragments using this layout must delegate {@code onCreateRecyclerView} to the + *

Fragments using this layout must delegate {@code onCreateRecyclerView} to the * implementation in this class: {@link #onCreateRecyclerView} */ public class SetupWizardPreferenceLayout extends SetupWizardRecyclerLayout { - public SetupWizardPreferenceLayout(Context context) { - super(context); - } + public SetupWizardPreferenceLayout(Context context) { + super(context); + } - public SetupWizardPreferenceLayout(Context context, int template, int containerId) { - super(context, template, containerId); - } + public SetupWizardPreferenceLayout(Context context, int template, int containerId) { + super(context, template, containerId); + } - public SetupWizardPreferenceLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } + public SetupWizardPreferenceLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } - public SetupWizardPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public SetupWizardPreferenceLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_layout_content; - } - return super.findContainer(containerId); + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_layout_content; } + return super.findContainer(containerId); + } - /** - * This method must be called in {@code PreferenceFragment#onCreateRecyclerView}. - */ - public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, - Bundle savedInstanceState) { - return mRecyclerMixin.getRecyclerView(); - } + /** This method must be called in {@code PreferenceFragment#onCreateRecyclerView}. */ + public RecyclerView onCreateRecyclerView( + LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + return mRecyclerMixin.getRecyclerView(); + } - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_preference_template; - } - return super.onInflateTemplate(inflater, template); + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_preference_template; } + return super.onInflateTemplate(inflater, template); + } - @Override - protected void onTemplateInflated() { - // Inflate the recycler view here, so attributes on the decoration views can be applied - // immediately. - final LayoutInflater inflater = LayoutInflater.from(getContext()); - RecyclerView recyclerView = (RecyclerView) inflater.inflate( - R.layout.suw_preference_recycler_view, this, false); - mRecyclerMixin = new RecyclerMixin(this, recyclerView); - } + @Override + protected void onTemplateInflated() { + // Inflate the recycler view here, so attributes on the decoration views can be applied + // immediately. + final LayoutInflater inflater = LayoutInflater.from(getContext()); + RecyclerView recyclerView = + (RecyclerView) inflater.inflate(R.layout.suw_preference_recycler_view, this, false); + mRecyclerMixin = new RecyclerMixin(this, recyclerView); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java index 5d3f1a5..ba0b598 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java @@ -18,178 +18,156 @@ package com.android.setupwizardlib; import android.content.Context; import android.graphics.drawable.Drawable; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - import com.android.setupwizardlib.template.RecyclerMixin; import com.android.setupwizardlib.template.RecyclerViewScrollHandlingDelegate; import com.android.setupwizardlib.template.RequireScrollMixin; /** - * A setup wizard layout for use with {@link androidx.recyclerview.widget.RecyclerView}. - * {@code android:entries} can also be used to specify an - * {@link com.android.setupwizardlib.items.ItemHierarchy} to be used with this layout in XML. + * A setup wizard layout for use with {@link androidx.recyclerview.widget.RecyclerView}. {@code + * android:entries} can also be used to specify an {@link + * com.android.setupwizardlib.items.ItemHierarchy} to be used with this layout in XML. * * @see SetupWizardListLayout */ public class SetupWizardRecyclerLayout extends SetupWizardLayout { - private static final String TAG = "RecyclerLayout"; - - protected RecyclerMixin mRecyclerMixin; - - public SetupWizardRecyclerLayout(Context context) { - this(context, 0, 0); - } - - public SetupWizardRecyclerLayout(Context context, int template, int containerId) { - super(context, template, containerId); - init(context, null, 0); - } - - public SetupWizardRecyclerLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - public SetupWizardRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - mRecyclerMixin.parseAttributes(attrs, defStyleAttr); - registerMixin(RecyclerMixin.class, mRecyclerMixin); - - - final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); - requireScrollMixin.setScrollHandlingDelegate( - new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mRecyclerMixin.onLayout(); - } - - /** - * @see RecyclerMixin#getAdapter() - */ - public Adapter getAdapter() { - return mRecyclerMixin.getAdapter(); - } - - /** - * @see RecyclerMixin#setAdapter(Adapter) - */ - public void setAdapter(Adapter adapter) { - mRecyclerMixin.setAdapter(adapter); - } - - /** - * @see RecyclerMixin#getRecyclerView() - */ - public RecyclerView getRecyclerView() { - return mRecyclerMixin.getRecyclerView(); - } - - @Override - protected ViewGroup findContainer(int containerId) { - if (containerId == 0) { - containerId = R.id.suw_recycler_view; - } - return super.findContainer(containerId); - } - - @Override - protected View onInflateTemplate(LayoutInflater inflater, int template) { - if (template == 0) { - template = R.layout.suw_recycler_template; - } - return super.onInflateTemplate(inflater, template); - } - - @Override - protected void onTemplateInflated() { - final View recyclerView = findViewById(R.id.suw_recycler_view); - if (recyclerView instanceof RecyclerView) { - mRecyclerMixin = new RecyclerMixin(this, (RecyclerView) recyclerView); - } else { - throw new IllegalStateException( - "SetupWizardRecyclerLayout should use a template with recycler view"); - } - } - - @Override - // Returning generic type is the common pattern used for findViewBy* methods - @SuppressWarnings("TypeParameterUnusedInFormals") - public T findManagedViewById(int id) { - final View header = mRecyclerMixin.getHeader(); - if (header != null) { - final T view = header.findViewById(id); - if (view != null) { - return view; - } - } - return super.findViewById(id); - } - - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - mRecyclerMixin.setDividerInset(inset); - } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start The number of pixels to inset on the "start" side of the list divider. Typically - * this will be either {@code @dimen/suw_items_icon_divider_inset} or - * {@code @dimen/suw_items_text_divider_inset}. - * @param end The number of pixels to inset on the "end" side of the list divider. - * - * @see RecyclerMixin#setDividerInsets(int, int) - */ - public void setDividerInsets(int start, int end) { - mRecyclerMixin.setDividerInsets(start, end); - } - - /** - * @deprecated Use {@link #getDividerInsetStart()} instead. - */ - @Deprecated - public int getDividerInset() { - return mRecyclerMixin.getDividerInset(); - } - - /** - * @see RecyclerMixin#getDividerInsetStart() - */ - public int getDividerInsetStart() { - return mRecyclerMixin.getDividerInsetStart(); - } - - /** - * @see RecyclerMixin#getDividerInsetEnd() - */ - public int getDividerInsetEnd() { - return mRecyclerMixin.getDividerInsetEnd(); - } - - /** - * @see RecyclerMixin#getDivider() - */ - public Drawable getDivider() { - return mRecyclerMixin.getDivider(); - } + protected RecyclerMixin mRecyclerMixin; + + public SetupWizardRecyclerLayout(Context context) { + this(context, 0, 0); + } + + public SetupWizardRecyclerLayout(Context context, int template, int containerId) { + super(context, template, containerId); + init(null, 0); + } + + public SetupWizardRecyclerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + public SetupWizardRecyclerLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + mRecyclerMixin.parseAttributes(attrs, defStyleAttr); + registerMixin(RecyclerMixin.class, mRecyclerMixin); + + final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class); + requireScrollMixin.setScrollHandlingDelegate( + new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView())); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mRecyclerMixin.onLayout(); + } + + /** @see RecyclerMixin#getAdapter() */ + public Adapter getAdapter() { + return mRecyclerMixin.getAdapter(); + } + + /** @see RecyclerMixin#setAdapter(Adapter) */ + public void setAdapter(Adapter adapter) { + mRecyclerMixin.setAdapter(adapter); + } + + /** @see RecyclerMixin#getRecyclerView() */ + public RecyclerView getRecyclerView() { + return mRecyclerMixin.getRecyclerView(); + } + + @Override + protected ViewGroup findContainer(int containerId) { + if (containerId == 0) { + containerId = R.id.suw_recycler_view; + } + return super.findContainer(containerId); + } + + @Override + protected View onInflateTemplate(LayoutInflater inflater, int template) { + if (template == 0) { + template = R.layout.suw_recycler_template; + } + return super.onInflateTemplate(inflater, template); + } + + @Override + protected void onTemplateInflated() { + final View recyclerView = findViewById(R.id.suw_recycler_view); + if (recyclerView instanceof RecyclerView) { + mRecyclerMixin = new RecyclerMixin(this, (RecyclerView) recyclerView); + } else { + throw new IllegalStateException( + "SetupWizardRecyclerLayout should use a template with recycler view"); + } + } + + @Override + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public T findManagedViewById(int id) { + final View header = mRecyclerMixin.getHeader(); + if (header != null) { + final T view = header.findViewById(id); + if (view != null) { + return view; + } + } + return super.findViewById(id); + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + mRecyclerMixin.setDividerInset(inset); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start The number of pixels to inset on the "start" side of the list divider. Typically + * this will be either {@code @dimen/suw_items_icon_divider_inset} or + * {@code @dimen/suw_items_text_divider_inset}. + * @param end The number of pixels to inset on the "end" side of the list divider. + * @see RecyclerMixin#setDividerInsets(int, int) + */ + public void setDividerInsets(int start, int end) { + mRecyclerMixin.setDividerInsets(start, end); + } + + /** @deprecated Use {@link #getDividerInsetStart()} instead. */ + @Deprecated + public int getDividerInset() { + return mRecyclerMixin.getDividerInset(); + } + + /** @see RecyclerMixin#getDividerInsetStart() */ + public int getDividerInsetStart() { + return mRecyclerMixin.getDividerInsetStart(); + } + + /** @see RecyclerMixin#getDividerInsetEnd() */ + public int getDividerInsetEnd() { + return mRecyclerMixin.getDividerInsetEnd(); + } + + /** @see RecyclerMixin#getDivider() */ + public Drawable getDivider() { + return mRecyclerMixin.getDivider(); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/items/ItemViewHolder.java b/library/recyclerview/src/com/android/setupwizardlib/items/ItemViewHolder.java index aeaba68..419e2aa 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/items/ItemViewHolder.java +++ b/library/recyclerview/src/com/android/setupwizardlib/items/ItemViewHolder.java @@ -16,44 +16,42 @@ package com.android.setupwizardlib.items; -import android.view.View; - import androidx.recyclerview.widget.RecyclerView; - +import android.view.View; import com.android.setupwizardlib.DividerItemDecoration; class ItemViewHolder extends RecyclerView.ViewHolder - implements DividerItemDecoration.DividedViewHolder { - - private boolean mIsEnabled; - private IItem mItem; - - ItemViewHolder(View itemView) { - super(itemView); - } - - @Override - public boolean isDividerAllowedAbove() { - return mIsEnabled; - } - - @Override - public boolean isDividerAllowedBelow() { - return mIsEnabled; - } - - public void setEnabled(boolean isEnabled) { - mIsEnabled = isEnabled; - itemView.setClickable(isEnabled); - itemView.setEnabled(isEnabled); - itemView.setFocusable(isEnabled); - } - - public void setItem(IItem item) { - mItem = item; - } - - public IItem getItem() { - return mItem; - } + implements DividerItemDecoration.DividedViewHolder { + + private boolean isEnabled; + private IItem item; + + ItemViewHolder(View itemView) { + super(itemView); + } + + @Override + public boolean isDividerAllowedAbove() { + return isEnabled; + } + + @Override + public boolean isDividerAllowedBelow() { + return isEnabled; + } + + public void setEnabled(boolean isEnabled) { + this.isEnabled = isEnabled; + itemView.setClickable(isEnabled); + itemView.setEnabled(isEnabled); + itemView.setFocusable(isEnabled); + } + + public void setItem(IItem item) { + this.item = item; + } + + public IItem getItem() { + return item; + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java b/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java index 56c60e7..ee753b8 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java +++ b/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java @@ -20,14 +20,12 @@ import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.R; /** @@ -36,217 +34,214 @@ import com.android.setupwizardlib.R; * XML. */ public class RecyclerItemAdapter extends RecyclerView.Adapter - implements ItemHierarchy.Observer { + implements ItemHierarchy.Observer { - private static final String TAG = "RecyclerItemAdapter"; + private static final String TAG = "RecyclerItemAdapter"; - /** - * A view tag set by {@link View#setTag(Object)}. If set on the root view of a layout, it will - * not create the default background for the list item. This means the item will not have ripple - * touch feedback by default. - */ - public static final String TAG_NO_BACKGROUND = "noBackground"; + /** + * A view tag set by {@link View#setTag(Object)}. If set on the root view of a layout, it will not + * create the default background for the list item. This means the item will not have ripple touch + * feedback by default. + */ + public static final String TAG_NO_BACKGROUND = "noBackground"; - /** - * Listener for item selection in this adapter. - */ - public interface OnItemSelectedListener { - - /** - * Called when an item in this adapter is clicked. - * - * @param item The Item corresponding to the position being clicked. - */ - void onItemSelected(IItem item); - } - - private final ItemHierarchy mItemHierarchy; - private OnItemSelectedListener mListener; - - public RecyclerItemAdapter(ItemHierarchy hierarchy) { - mItemHierarchy = hierarchy; - mItemHierarchy.registerObserver(this); - } + /** Listener for item selection in this adapter. */ + public interface OnItemSelectedListener { /** - * Gets the item at the given position. + * Called when an item in this adapter is clicked. * - * @see ItemHierarchy#getItemAt(int) + * @param item The Item corresponding to the position being clicked. */ - public IItem getItem(int position) { - return mItemHierarchy.getItemAt(position); - } - - @Override - public long getItemId(int position) { - IItem mItem = getItem(position); - if (mItem instanceof AbstractItem) { - final int id = ((AbstractItem) mItem).getId(); - return id > 0 ? id : RecyclerView.NO_ID; - } else { - return RecyclerView.NO_ID; - } - } - - @Override - public int getItemCount() { - return mItemHierarchy.getCount(); - } - - @Override - public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - final View view = inflater.inflate(viewType, parent, false); - final ItemViewHolder viewHolder = new ItemViewHolder(view); - - final Object viewTag = view.getTag(); - if (!TAG_NO_BACKGROUND.equals(viewTag)) { - final TypedArray typedArray = parent.getContext() - .obtainStyledAttributes(R.styleable.SuwRecyclerItemAdapter); - Drawable selectableItemBackground = typedArray.getDrawable( - R.styleable.SuwRecyclerItemAdapter_android_selectableItemBackground); - if (selectableItemBackground == null) { - selectableItemBackground = typedArray.getDrawable( - R.styleable.SuwRecyclerItemAdapter_selectableItemBackground); - } - - Drawable background = view.getBackground(); - if (background == null) { - background = typedArray.getDrawable( - R.styleable.SuwRecyclerItemAdapter_android_colorBackground); - } - - if (selectableItemBackground == null || background == null) { - Log.e(TAG, "Cannot resolve required attributes." - + " selectableItemBackground=" + selectableItemBackground - + " background=" + background); - } else { - final Drawable[] layers = {background, selectableItemBackground}; - view.setBackgroundDrawable(new PatchedLayerDrawable(layers)); - } - - typedArray.recycle(); - } - - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - final IItem item = viewHolder.getItem(); - if (mListener != null && item != null && item.isEnabled()) { - mListener.onItemSelected(item); - } + void onItemSelected(IItem item); + } + + private final ItemHierarchy itemHierarchy; + private OnItemSelectedListener listener; + + public RecyclerItemAdapter(ItemHierarchy hierarchy) { + itemHierarchy = hierarchy; + itemHierarchy.registerObserver(this); + } + + /** + * Gets the item at the given position. + * + * @see ItemHierarchy#getItemAt(int) + */ + public IItem getItem(int position) { + return itemHierarchy.getItemAt(position); + } + + @Override + public long getItemId(int position) { + IItem mItem = getItem(position); + if (mItem instanceof AbstractItem) { + final int id = ((AbstractItem) mItem).getId(); + return id > 0 ? id : RecyclerView.NO_ID; + } else { + return RecyclerView.NO_ID; + } + } + + @Override + public int getItemCount() { + return itemHierarchy.getCount(); + } + + @Override + public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + final View view = inflater.inflate(viewType, parent, false); + final ItemViewHolder viewHolder = new ItemViewHolder(view); + + final Object viewTag = view.getTag(); + if (!TAG_NO_BACKGROUND.equals(viewTag)) { + final TypedArray typedArray = + parent.getContext().obtainStyledAttributes(R.styleable.SuwRecyclerItemAdapter); + Drawable selectableItemBackground = + typedArray.getDrawable( + R.styleable.SuwRecyclerItemAdapter_android_selectableItemBackground); + if (selectableItemBackground == null) { + selectableItemBackground = + typedArray.getDrawable(R.styleable.SuwRecyclerItemAdapter_selectableItemBackground); + } + + Drawable background = view.getBackground(); + if (background == null) { + background = + typedArray.getDrawable(R.styleable.SuwRecyclerItemAdapter_android_colorBackground); + } + + if (selectableItemBackground == null || background == null) { + Log.e( + TAG, + "Cannot resolve required attributes." + + " selectableItemBackground=" + + selectableItemBackground + + " background=" + + background); + } else { + final Drawable[] layers = {background, selectableItemBackground}; + view.setBackgroundDrawable(new PatchedLayerDrawable(layers)); + } + + typedArray.recycle(); + } + + view.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + final IItem item = viewHolder.getItem(); + if (listener != null && item != null && item.isEnabled()) { + listener.onItemSelected(item); } + } }); - return viewHolder; + return viewHolder; + } + + @Override + public void onBindViewHolder(ItemViewHolder holder, int position) { + final IItem item = getItem(position); + holder.setEnabled(item.isEnabled()); + holder.setItem(item); + item.onBindView(holder.itemView); + } + + @Override + public int getItemViewType(int position) { + // Use layout resource as item view type. RecyclerView item type does not have to be + // contiguous. + IItem item = getItem(position); + return item.getLayoutResource(); + } + + @Override + public void onChanged(ItemHierarchy hierarchy) { + notifyDataSetChanged(); + } + + @Override + public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + notifyItemRangeChanged(positionStart, itemCount); + } + + @Override + public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + notifyItemRangeInserted(positionStart, itemCount); + } + + @Override + public void onItemRangeMoved( + ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount) { + // There is no notifyItemRangeMoved + // https://code.google.com/p/android/issues/detail?id=125984 + if (itemCount == 1) { + notifyItemMoved(fromPosition, toPosition); + } else { + // If more than one, degenerate into the catch-all data set changed callback, since I'm + // not sure how recycler view handles multiple calls to notifyItemMoved (if the result + // is committed after every notification then naively calling + // notifyItemMoved(from + i, to + i) is wrong). + // Logging this in case this is a more common occurrence than expected. + Log.i(TAG, "onItemRangeMoved with more than one item"); + notifyDataSetChanged(); + } + } + + @Override + public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { + notifyItemRangeRemoved(positionStart, itemCount); + } + + /** + * Find an item hierarchy within the root hierarchy. + * + * @see ItemHierarchy#findItemById(int) + */ + public ItemHierarchy findItemById(int id) { + return itemHierarchy.findItemById(id); + } + + /** Gets the root item hierarchy in this adapter. */ + public ItemHierarchy getRootItemHierarchy() { + return itemHierarchy; + } + + /** + * Sets the listener to listen for when user clicks on a item. + * + * @see OnItemSelectedListener + */ + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + this.listener = listener; + } + + /** + * Before Lollipop, LayerDrawable always return true in getPadding, even if the children layers do + * not have any padding. Patch the implementation so that getPadding returns false if the padding + * is empty. + * + *

When getPadding is true, the padding of the view will be replaced by the padding of the + * drawable when {@link View#setBackgroundDrawable(Drawable)} is called. This patched class makes + * sure layer drawables without padding does not clear out original padding on the view. + */ + @VisibleForTesting + static class PatchedLayerDrawable extends LayerDrawable { + + /** {@inheritDoc} */ + PatchedLayerDrawable(Drawable[] layers) { + super(layers); } @Override - public void onBindViewHolder(ItemViewHolder holder, int position) { - final IItem item = getItem(position); - holder.setEnabled(item.isEnabled()); - holder.setItem(item); - item.onBindView(holder.itemView); - } - - @Override - public int getItemViewType(int position) { - // Use layout resource as item view type. RecyclerView item type does not have to be - // contiguous. - IItem item = getItem(position); - return item.getLayoutResource(); - } - - @Override - public void onChanged(ItemHierarchy hierarchy) { - notifyDataSetChanged(); - } - - @Override - public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - notifyItemRangeChanged(positionStart, itemCount); - } - - @Override - public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - notifyItemRangeInserted(positionStart, itemCount); - } - - @Override - public void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition, - int itemCount) { - // There is no notifyItemRangeMoved - // https://code.google.com/p/android/issues/detail?id=125984 - if (itemCount == 1) { - notifyItemMoved(fromPosition, toPosition); - } else { - // If more than one, degenerate into the catch-all data set changed callback, since I'm - // not sure how recycler view handles multiple calls to notifyItemMoved (if the result - // is committed after every notification then naively calling - // notifyItemMoved(from + i, to + i) is wrong). - // Logging this in case this is a more common occurrence than expected. - Log.i(TAG, "onItemRangeMoved with more than one item"); - notifyDataSetChanged(); - } - } - - @Override - public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { - notifyItemRangeRemoved(positionStart, itemCount); - } - - /** - * Find an item hierarchy within the root hierarchy. - * - * @see ItemHierarchy#findItemById(int) - */ - public ItemHierarchy findItemById(int id) { - return mItemHierarchy.findItemById(id); - } - - /** - * Gets the root item hierarchy in this adapter. - */ - public ItemHierarchy getRootItemHierarchy() { - return mItemHierarchy; - } - - /** - * Sets the listener to listen for when user clicks on a item. - * - * @see OnItemSelectedListener - */ - public void setOnItemSelectedListener(OnItemSelectedListener listener) { - mListener = listener; - } - - /** - * Before Lollipop, LayerDrawable always return true in getPadding, even if the children layers - * do not have any padding. Patch the implementation so that getPadding returns false if the - * padding is empty. - * - * When getPadding is true, the padding of the view will be replaced by the padding of the - * drawable when {@link View#setBackgroundDrawable(Drawable)} is called. This patched class - * makes sure layer drawables without padding does not clear out original padding on the view. - */ - @VisibleForTesting - static class PatchedLayerDrawable extends LayerDrawable { - - /** - * {@inheritDoc} - */ - PatchedLayerDrawable(Drawable[] layers) { - super(layers); - } - - @Override - public boolean getPadding(Rect padding) { - final boolean superHasPadding = super.getPadding(padding); - return superHasPadding - && !(padding.left == 0 - && padding.top == 0 - && padding.right == 0 - && padding.bottom == 0); - } + public boolean getPadding(Rect padding) { + final boolean superHasPadding = super.getPadding(padding); + return superHasPadding + && !(padding.left == 0 && padding.top == 0 && padding.right == 0 && padding.bottom == 0); } + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java index 32e7bd8..a6c6526 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java +++ b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java @@ -21,16 +21,14 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION_CODES; -import android.util.AttributeSet; -import android.view.View; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.ViewHolder; - +import android.util.AttributeSet; +import android.view.View; import com.android.setupwizardlib.DividerItemDecoration; import com.android.setupwizardlib.R; import com.android.setupwizardlib.TemplateLayout; @@ -51,223 +49,208 @@ import com.android.setupwizardlib.view.HeaderRecyclerView.HeaderAdapter; */ public class RecyclerMixin implements Mixin { - private TemplateLayout mTemplateLayout; + private final TemplateLayout templateLayout; - @NonNull - private final RecyclerView mRecyclerView; + @NonNull private final RecyclerView recyclerView; - @Nullable - private View mHeader; + @Nullable private View header; - @NonNull - private DividerItemDecoration mDividerDecoration; + @NonNull private DividerItemDecoration dividerDecoration; - private Drawable mDefaultDivider; - private Drawable mDivider; + private Drawable defaultDivider; + private Drawable divider; - private int mDividerInsetStart; - private int mDividerInsetEnd; + private int dividerInsetStart; + private int dividerInsetEnd; - /** - * Creates the RecyclerMixin. Unlike typical mixins which are created in the constructor, this - * mixin should be called in {@link TemplateLayout#onTemplateInflated()}, which is called by - * the super constructor, because the recycler view and the header needs to be made available - * before other mixins from the super class. - * - * @param layout The layout this mixin belongs to. - */ - public RecyclerMixin(@NonNull TemplateLayout layout, @NonNull RecyclerView recyclerView) { - mTemplateLayout = layout; + /** + * Creates the RecyclerMixin. Unlike typical mixins which are created in the constructor, this + * mixin should be called in {@link TemplateLayout#onTemplateInflated()}, which is called by the + * super constructor, because the recycler view and the header needs to be made available before + * other mixins from the super class. + * + * @param layout The layout this mixin belongs to. + */ + public RecyclerMixin(@NonNull TemplateLayout layout, @NonNull RecyclerView recyclerView) { + templateLayout = layout; - mDividerDecoration = new DividerItemDecoration(mTemplateLayout.getContext()); + dividerDecoration = new DividerItemDecoration(templateLayout.getContext()); - // The recycler view needs to be available - mRecyclerView = recyclerView; - mRecyclerView.setLayoutManager(new LinearLayoutManager(mTemplateLayout.getContext())); + // The recycler view needs to be available + this.recyclerView = recyclerView; + this.recyclerView.setLayoutManager(new LinearLayoutManager(templateLayout.getContext())); - if (recyclerView instanceof HeaderRecyclerView) { - mHeader = ((HeaderRecyclerView) recyclerView).getHeader(); - } - - mRecyclerView.addItemDecoration(mDividerDecoration); + if (recyclerView instanceof HeaderRecyclerView) { + header = ((HeaderRecyclerView) recyclerView).getHeader(); } - /** - * Parse XML attributes and configures this mixin and the recycler view accordingly. This should - * be called from the constructor of the layout. - * - * @param attrs The {@link AttributeSet} as passed into the constructor. Can be null if the - * layout was not created from XML. - * @param defStyleAttr The default style attribute as passed into the layout constructor. Can be - * 0 if it is not needed. - */ - public void parseAttributes(@Nullable AttributeSet attrs, int defStyleAttr) { - final Context context = mTemplateLayout.getContext(); - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.SuwRecyclerMixin, defStyleAttr, 0); - - final int entries = a.getResourceId(R.styleable.SuwRecyclerMixin_android_entries, 0); - if (entries != 0) { - final ItemHierarchy inflated = new ItemInflater(context).inflate(entries); - final RecyclerItemAdapter adapter = new RecyclerItemAdapter(inflated); - adapter.setHasStableIds(a.getBoolean( - R.styleable.SuwRecyclerMixin_suwHasStableIds, false)); - setAdapter(adapter); - } - int dividerInset = - a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInset, -1); - if (dividerInset != -1) { - setDividerInset(dividerInset); - } else { - int dividerInsetStart = - a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetStart, 0); - int dividerInsetEnd = - a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetEnd, 0); - setDividerInsets(dividerInsetStart, dividerInsetEnd); - } - - a.recycle(); + this.recyclerView.addItemDecoration(dividerDecoration); + } + + /** + * Parse XML attributes and configures this mixin and the recycler view accordingly. This should + * be called from the constructor of the layout. + * + * @param attrs The {@link AttributeSet} as passed into the constructor. Can be null if the layout + * was not created from XML. + * @param defStyleAttr The default style attribute as passed into the layout constructor. Can be 0 + * if it is not needed. + */ + public void parseAttributes(@Nullable AttributeSet attrs, int defStyleAttr) { + final Context context = templateLayout.getContext(); + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.SuwRecyclerMixin, defStyleAttr, 0); + + final int entries = a.getResourceId(R.styleable.SuwRecyclerMixin_android_entries, 0); + if (entries != 0) { + final ItemHierarchy inflated = new ItemInflater(context).inflate(entries); + final RecyclerItemAdapter adapter = new RecyclerItemAdapter(inflated); + adapter.setHasStableIds(a.getBoolean(R.styleable.SuwRecyclerMixin_suwHasStableIds, false)); + setAdapter(adapter); } - - /** - * @return The recycler view contained in the layout, as marked by - * {@code @id/suw_recycler_view}. This will return {@code null} if the recycler view - * doesn't exist in the layout. - */ - @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a recycler - // view, and call this after the template is inflated, - // this will not return null. - public RecyclerView getRecyclerView() { - return mRecyclerView; + int dividerInset = a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInset, -1); + if (dividerInset != -1) { + setDividerInset(dividerInset); + } else { + int dividerInsetStart = + a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetStart, 0); + int dividerInsetEnd = + a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetEnd, 0); + setDividerInsets(dividerInsetStart, dividerInsetEnd); } - /** - * Gets the header view of the recycler layout. This is useful for other mixins if they need to - * access views within the header, usually via {@link TemplateLayout#findManagedViewById(int)}. - */ - @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a header, - // this call will not return null. - public View getHeader() { - return mHeader; + a.recycle(); + } + + /** + * @return The recycler view contained in the layout, as marked by {@code @id/suw_recycler_view}. + * This will return {@code null} if the recycler view doesn't exist in the layout. + */ + @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a recycler + // view, and call this after the template is inflated, + // this will not return null. + public RecyclerView getRecyclerView() { + return recyclerView; + } + + /** + * Gets the header view of the recycler layout. This is useful for other mixins if they need to + * access views within the header, usually via {@link TemplateLayout#findManagedViewById(int)}. + */ + @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a header, + // this call will not return null. + public View getHeader() { + return header; + } + + /** + * Recycler mixin needs to update the dividers if the layout direction has changed. This method + * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template is + * called. + */ + public void onLayout() { + if (divider == null) { + // Update divider in case layout direction has just been resolved + updateDivider(); } - - /** - * Recycler mixin needs to update the dividers if the layout direction has changed. This method - * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template - * is called. - */ - public void onLayout() { - if (mDivider == null) { - // Update divider in case layout direction has just been resolved - updateDivider(); - } + } + + /** + * Gets the adapter of the recycler view in this layout. If the adapter includes a header, this + * method will unwrap it and return the underlying adapter. + * + * @return The adapter, or {@code null} if the recycler view has no adapter. + */ + public Adapter getAdapter() { + @SuppressWarnings("unchecked") // RecyclerView.getAdapter returns raw type :( + final RecyclerView.Adapter adapter = recyclerView.getAdapter(); + if (adapter instanceof HeaderAdapter) { + return ((HeaderAdapter) adapter).getWrappedAdapter(); } - - /** - * Gets the adapter of the recycler view in this layout. If the adapter includes a header, - * this method will unwrap it and return the underlying adapter. - * - * @return The adapter, or {@code null} if the recycler view has no adapter. - */ - public Adapter getAdapter() { - @SuppressWarnings("unchecked") // RecyclerView.getAdapter returns raw type :( - final RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); - if (adapter instanceof HeaderAdapter) { - return ((HeaderAdapter) adapter).getWrappedAdapter(); - } - return adapter; + return adapter; + } + + /** Sets the adapter on the recycler view in this layout. */ + public void setAdapter(Adapter adapter) { + recyclerView.setAdapter(adapter); + } + + /** @deprecated Use {@link #setDividerInsets(int, int)} instead. */ + @Deprecated + public void setDividerInset(int inset) { + setDividerInsets(inset, 0); + } + + /** + * Sets the start inset of the divider. This will use the default divider drawable set in the + * theme and apply insets to it. + * + * @param start The number of pixels to inset on the "start" side of the list divider. Typically + * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or + * {@code @dimen/suw_items_glif_text_divider_inset}. + * @param end The number of pixels to inset on the "end" side of the list divider. + */ + public void setDividerInsets(int start, int end) { + dividerInsetStart = start; + dividerInsetEnd = end; + updateDivider(); + } + + /** + * @return The number of pixels inset on the start side of the divider. + * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. + */ + @Deprecated + public int getDividerInset() { + return getDividerInsetStart(); + } + + /** @return The number of pixels inset on the start side of the divider. */ + public int getDividerInsetStart() { + return dividerInsetStart; + } + + /** @return The number of pixels inset on the end side of the divider. */ + public int getDividerInsetEnd() { + return dividerInsetEnd; + } + + private void updateDivider() { + boolean shouldUpdate = true; + if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + shouldUpdate = templateLayout.isLayoutDirectionResolved(); } - - /** - * Sets the adapter on the recycler view in this layout. - */ - public void setAdapter(Adapter adapter) { - mRecyclerView.setAdapter(adapter); - } - - /** - * @deprecated Use {@link #setDividerInsets(int, int)} instead. - */ - @Deprecated - public void setDividerInset(int inset) { - setDividerInsets(inset, 0); - } - - /** - * Sets the start inset of the divider. This will use the default divider drawable set in the - * theme and apply insets to it. - * - * @param start The number of pixels to inset on the "start" side of the list divider. Typically - * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or - * {@code @dimen/suw_items_glif_text_divider_inset}. - * @param end The number of pixels to inset on the "end" side of the list divider. - */ - public void setDividerInsets(int start, int end) { - mDividerInsetStart = start; - mDividerInsetEnd = end; - updateDivider(); - } - - /** - * @return The number of pixels inset on the start side of the divider. - * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. - */ - @Deprecated - public int getDividerInset() { - return getDividerInsetStart(); - } - - /** - * @return The number of pixels inset on the start side of the divider. - */ - public int getDividerInsetStart() { - return mDividerInsetStart; - } - - /** - * @return The number of pixels inset on the end side of the divider. - */ - public int getDividerInsetEnd() { - return mDividerInsetEnd; - } - - private void updateDivider() { - boolean shouldUpdate = true; - if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - shouldUpdate = mTemplateLayout.isLayoutDirectionResolved(); - } - if (shouldUpdate) { - if (mDefaultDivider == null) { - mDefaultDivider = mDividerDecoration.getDivider(); - } - mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable( - mDefaultDivider, - mDividerInsetStart /* start */, - 0 /* top */, - mDividerInsetEnd /* end */, - 0 /* bottom */, - mTemplateLayout); - mDividerDecoration.setDivider(mDivider); - } - } - - /** - * @return The drawable used as the divider. - */ - public Drawable getDivider() { - return mDivider; - } - - /** - * Sets the divider item decoration directly. This is a low level method which should be used - * only if custom divider behavior is needed, for example if the divider should be shown / - * hidden in some specific cases for view holders that cannot implement - * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. - */ - public void setDividerItemDecoration(@NonNull DividerItemDecoration decoration) { - mRecyclerView.removeItemDecoration(mDividerDecoration); - mDividerDecoration = decoration; - mRecyclerView.addItemDecoration(mDividerDecoration); - updateDivider(); + if (shouldUpdate) { + if (defaultDivider == null) { + defaultDivider = dividerDecoration.getDivider(); + } + divider = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + defaultDivider, + dividerInsetStart /* start */, + 0 /* top */, + dividerInsetEnd /* end */, + 0 /* bottom */, + templateLayout); + dividerDecoration.setDivider(divider); } + } + + /** @return The drawable used as the divider. */ + public Drawable getDivider() { + return divider; + } + + /** + * Sets the divider item decoration directly. This is a low level method which should be used only + * if custom divider behavior is needed, for example if the divider should be shown / hidden in + * some specific cases for view holders that cannot implement {@link + * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. + */ + public void setDividerItemDecoration(@NonNull DividerItemDecoration decoration) { + recyclerView.removeItemDecoration(dividerDecoration); + dividerDecoration = decoration; + recyclerView.addItemDecoration(dividerDecoration); + updateDivider(); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java index bfe8df2..8838c44 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java +++ b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java @@ -16,12 +16,10 @@ package com.android.setupwizardlib.template; -import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; - +import android.util.Log; import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate; /** @@ -30,55 +28,53 @@ import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDele */ public class RecyclerViewScrollHandlingDelegate implements ScrollHandlingDelegate { - private static final String TAG = "RVRequireScrollMixin"; + private static final String TAG = "RVRequireScrollMixin"; - @Nullable - private final RecyclerView mRecyclerView; + @Nullable private final RecyclerView recyclerView; - @NonNull - private final RequireScrollMixin mRequireScrollMixin; + @NonNull private final RequireScrollMixin requireScrollMixin; - public RecyclerViewScrollHandlingDelegate( - @NonNull RequireScrollMixin requireScrollMixin, - @Nullable RecyclerView recyclerView) { - mRequireScrollMixin = requireScrollMixin; - mRecyclerView = recyclerView; - } + public RecyclerViewScrollHandlingDelegate( + @NonNull RequireScrollMixin requireScrollMixin, @Nullable RecyclerView recyclerView) { + this.requireScrollMixin = requireScrollMixin; + this.recyclerView = recyclerView; + } - private boolean canScrollDown() { - if (mRecyclerView != null) { - // Compatibility implementation of View#canScrollVertically - final int offset = mRecyclerView.computeVerticalScrollOffset(); - final int range = mRecyclerView.computeVerticalScrollRange() - - mRecyclerView.computeVerticalScrollExtent(); - return range != 0 && offset < range - 1; - } - return false; + private boolean canScrollDown() { + if (recyclerView != null) { + // Compatibility implementation of View#canScrollVertically + final int offset = recyclerView.computeVerticalScrollOffset(); + final int range = + recyclerView.computeVerticalScrollRange() - recyclerView.computeVerticalScrollExtent(); + return range != 0 && offset < range - 1; } + return false; + } - @Override - public void startListening() { - if (mRecyclerView != null) { - mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - mRequireScrollMixin.notifyScrollabilityChange(canScrollDown()); - } - }); - - if (canScrollDown()) { - mRequireScrollMixin.notifyScrollabilityChange(true); + @Override + public void startListening() { + if (this.recyclerView != null) { + this.recyclerView.addOnScrollListener( + new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + requireScrollMixin.notifyScrollabilityChange(canScrollDown()); } - } else { - Log.w(TAG, "Cannot require scroll. Recycler view is null."); - } + }); + + if (canScrollDown()) { + requireScrollMixin.notifyScrollabilityChange(true); + } + } else { + Log.w(TAG, "Cannot require scroll. Recycler view is null."); } + } - @Override - public void pageScrollDown() { - if (mRecyclerView != null) { - final int height = mRecyclerView.getHeight(); - mRecyclerView.smoothScrollBy(0, height); - } + @Override + public void pageScrollDown() { + if (recyclerView != null) { + final int height = recyclerView.getHeight(); + recyclerView.smoothScrollBy(0, height); } + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/view/HeaderRecyclerView.java b/library/recyclerview/src/com/android/setupwizardlib/view/HeaderRecyclerView.java index 0304b65..3808e11 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/view/HeaderRecyclerView.java +++ b/library/recyclerview/src/com/android/setupwizardlib/view/HeaderRecyclerView.java @@ -19,259 +19,257 @@ package com.android.setupwizardlib.view; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; +import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; - -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.DividerItemDecoration; import com.android.setupwizardlib.R; /** * A RecyclerView that can display a header item at the start of the list. The header can be set by - * {@code app:suwHeader} in XML. Note that the header will not be inflated until a layout manager - * is set. + * {@code app:suwHeader} in XML. Note that the header will not be inflated until a layout manager is + * set. */ public class HeaderRecyclerView extends RecyclerView { - private static class HeaderViewHolder extends ViewHolder - implements DividerItemDecoration.DividedViewHolder { - - HeaderViewHolder(View itemView) { - super(itemView); - } - - @Override - public boolean isDividerAllowedAbove() { - return false; - } + private static class HeaderViewHolder extends ViewHolder + implements DividerItemDecoration.DividedViewHolder { - @Override - public boolean isDividerAllowedBelow() { - return false; - } + HeaderViewHolder(View itemView) { + super(itemView); } - /** - * An adapter that can optionally add one header item to the RecyclerView. - * - * @param Type of the content view holder. i.e. view holder type of the wrapped adapter. - */ - public static class HeaderAdapter - extends RecyclerView.Adapter { - - private static final int HEADER_VIEW_TYPE = Integer.MAX_VALUE; + @Override + public boolean isDividerAllowedAbove() { + return false; + } - private RecyclerView.Adapter mAdapter; - private View mHeader; + @Override + public boolean isDividerAllowedBelow() { + return false; + } + } - private final AdapterDataObserver mObserver = new AdapterDataObserver() { + /** + * An adapter that can optionally add one header item to the RecyclerView. + * + * @param Type of the content view holder. i.e. view holder type of the wrapped adapter. + */ + public static class HeaderAdapter + extends RecyclerView.Adapter { - @Override - public void onChanged() { - notifyDataSetChanged(); - } + private static final int HEADER_VIEW_TYPE = Integer.MAX_VALUE; - @Override - public void onItemRangeChanged(int positionStart, int itemCount) { - if (mHeader != null) { - positionStart++; - } - notifyItemRangeChanged(positionStart, itemCount); - } + private final RecyclerView.Adapter adapter; + private View header; - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - if (mHeader != null) { - positionStart++; - } - notifyItemRangeInserted(positionStart, itemCount); - } + private final AdapterDataObserver observer = + new AdapterDataObserver() { - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - if (mHeader != null) { - fromPosition++; - toPosition++; - } - // Why is there no notifyItemRangeMoved? - for (int i = 0; i < itemCount; i++) { - notifyItemMoved(fromPosition + i, toPosition + i); - } - } + @Override + public void onChanged() { + notifyDataSetChanged(); + } - @Override - public void onItemRangeRemoved(int positionStart, int itemCount) { - if (mHeader != null) { - positionStart++; - } - notifyItemRangeRemoved(positionStart, itemCount); + @Override + public void onItemRangeChanged(int positionStart, int itemCount) { + if (header != null) { + positionStart++; } - }; - - public HeaderAdapter(RecyclerView.Adapter adapter) { - mAdapter = adapter; - mAdapter.registerAdapterDataObserver(mObserver); - setHasStableIds(mAdapter.hasStableIds()); - } + notifyItemRangeChanged(positionStart, itemCount); + } - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - // Returning the same view (mHeader) results in crash ".. but view is not a real child." - // The framework creates more than one instance of header because of "disappear" - // animations applied on the header and this necessitates creation of another header - // view to use after the animation. We work around this restriction by returning an - // empty FrameLayout to which the header is attached using #onBindViewHolder method. - if (viewType == HEADER_VIEW_TYPE) { - FrameLayout frameLayout = new FrameLayout(parent.getContext()); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT); - frameLayout.setLayoutParams(params); - return new HeaderViewHolder(frameLayout); - } else { - return mAdapter.onCreateViewHolder(parent, viewType); + @Override + public void onItemRangeInserted(int positionStart, int itemCount) { + if (header != null) { + positionStart++; } - } - - @Override - @SuppressWarnings("unchecked") // Non-header position always return type CVH - public void onBindViewHolder(ViewHolder holder, int position) { - if (mHeader != null) { - position--; + notifyItemRangeInserted(positionStart, itemCount); + } + + @Override + public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + if (header != null) { + fromPosition++; + toPosition++; } - - if (holder instanceof HeaderViewHolder) { - if (mHeader == null) { - throw new IllegalStateException("HeaderViewHolder cannot find mHeader"); - } - if (mHeader.getParent() != null) { - ((ViewGroup) mHeader.getParent()).removeView(mHeader); - } - FrameLayout mHeaderParent = (FrameLayout) holder.itemView; - mHeaderParent.addView(mHeader); - } else { - mAdapter.onBindViewHolder((CVH) holder, position); + // Why is there no notifyItemRangeMoved? + for (int i = 0; i < itemCount; i++) { + notifyItemMoved(fromPosition + i, toPosition + i); } - } + } - @Override - public int getItemViewType(int position) { - if (mHeader != null) { - position--; + @Override + public void onItemRangeRemoved(int positionStart, int itemCount) { + if (header != null) { + positionStart++; } - if (position < 0) { - return HEADER_VIEW_TYPE; - } - return mAdapter.getItemViewType(position); - } - - @Override - public int getItemCount() { - int count = mAdapter.getItemCount(); - if (mHeader != null) { - count++; - } - return count; - } - - @Override - public long getItemId(int position) { - if (mHeader != null) { - position--; - } - if (position < 0) { - return Long.MAX_VALUE; - } - return mAdapter.getItemId(position); - } - - public void setHeader(View header) { - mHeader = header; - } - - public RecyclerView.Adapter getWrappedAdapter() { - return mAdapter; - } - } - - private View mHeader; - private int mHeaderRes; + notifyItemRangeRemoved(positionStart, itemCount); + } + }; - public HeaderRecyclerView(Context context) { - super(context); - init(null, 0); + public HeaderAdapter(RecyclerView.Adapter adapter) { + this.adapter = adapter; + this.adapter.registerAdapterDataObserver(observer); + setHasStableIds(this.adapter.hasStableIds()); } - public HeaderRecyclerView(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs, 0); + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + // Returning the same view (header) results in crash ".. but view is not a real child." + // The framework creates more than one instance of header because of "disappear" + // animations applied on the header and this necessitates creation of another header + // view to use after the animation. We work around this restriction by returning an + // empty FrameLayout to which the header is attached using #onBindViewHolder method. + if (viewType == HEADER_VIEW_TYPE) { + FrameLayout frameLayout = new FrameLayout(parent.getContext()); + FrameLayout.LayoutParams params = + new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + frameLayout.setLayoutParams(params); + return new HeaderViewHolder(frameLayout); + } else { + return adapter.onCreateViewHolder(parent, viewType); + } } - public HeaderRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs, defStyleAttr); + @Override + @SuppressWarnings("unchecked") // Non-header position always return type CVH + public void onBindViewHolder(ViewHolder holder, int position) { + if (header != null) { + position--; + } + + if (holder instanceof HeaderViewHolder) { + if (header == null) { + throw new IllegalStateException("HeaderViewHolder cannot find mHeader"); + } + if (header.getParent() != null) { + ((ViewGroup) header.getParent()).removeView(header); + } + FrameLayout mHeaderParent = (FrameLayout) holder.itemView; + mHeaderParent.addView(header); + } else { + adapter.onBindViewHolder((CVH) holder, position); + } } - private void init(AttributeSet attrs, int defStyleAttr) { - final TypedArray a = getContext().obtainStyledAttributes(attrs, - R.styleable.SuwHeaderRecyclerView, defStyleAttr, 0); - mHeaderRes = a.getResourceId(R.styleable.SuwHeaderRecyclerView_suwHeader, 0); - a.recycle(); + @Override + public int getItemViewType(int position) { + if (header != null) { + position--; + } + if (position < 0) { + return HEADER_VIEW_TYPE; + } + return adapter.getItemViewType(position); } @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - - // Decoration-only headers should not count as an item for accessibility, adjust the - // accessibility event to account for that. - final int numberOfHeaders = mHeader != null ? 1 : 0; - event.setItemCount(event.getItemCount() - numberOfHeaders); - event.setFromIndex(Math.max(event.getFromIndex() - numberOfHeaders, 0)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - event.setToIndex(Math.max(event.getToIndex() - numberOfHeaders, 0)); - } + public int getItemCount() { + int count = adapter.getItemCount(); + if (header != null) { + count++; + } + return count; } - /** - * Gets the header view of this RecyclerView, or {@code null} if there are no headers. - */ - public View getHeader() { - return mHeader; + @Override + public long getItemId(int position) { + if (header != null) { + position--; + } + if (position < 0) { + return Long.MAX_VALUE; + } + return adapter.getItemId(position); } - /** - * Set the view to use as the header of this recycler view. - * Note: This must be called before setAdapter. - */ public void setHeader(View header) { - mHeader = header; + this.header = header; } - @Override - public void setLayoutManager(LayoutManager layout) { - super.setLayoutManager(layout); - if (layout != null && mHeader == null && mHeaderRes != 0) { - // Inflating a child view requires the layout manager to be set. Check here to see if - // any header item is specified in XML and inflate them. - final LayoutInflater inflater = LayoutInflater.from(getContext()); - mHeader = inflater.inflate(mHeaderRes, this, false); - } + public RecyclerView.Adapter getWrappedAdapter() { + return adapter; } - - @Override - @SuppressWarnings("rawtypes,unchecked") // RecyclerView.setAdapter uses raw type :( - public void setAdapter(Adapter adapter) { - if (mHeader != null && adapter != null) { - final HeaderAdapter headerAdapter = new HeaderAdapter(adapter); - headerAdapter.setHeader(mHeader); - adapter = headerAdapter; - } - super.setAdapter(adapter); + } + + private View header; + private int headerRes; + + public HeaderRecyclerView(Context context) { + super(context); + init(null, 0); + } + + public HeaderRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + public HeaderRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + final TypedArray a = + getContext() + .obtainStyledAttributes(attrs, R.styleable.SuwHeaderRecyclerView, defStyleAttr, 0); + headerRes = a.getResourceId(R.styleable.SuwHeaderRecyclerView_suwHeader, 0); + a.recycle(); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + + // Decoration-only headers should not count as an item for accessibility, adjust the + // accessibility event to account for that. + final int numberOfHeaders = header != null ? 1 : 0; + event.setItemCount(event.getItemCount() - numberOfHeaders); + event.setFromIndex(Math.max(event.getFromIndex() - numberOfHeaders, 0)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + event.setToIndex(Math.max(event.getToIndex() - numberOfHeaders, 0)); + } + } + + /** Gets the header view of this RecyclerView, or {@code null} if there are no headers. */ + public View getHeader() { + return header; + } + + /** + * Set the view to use as the header of this recycler view. Note: This must be called before + * setAdapter. + */ + public void setHeader(View header) { + this.header = header; + } + + @Override + public void setLayoutManager(LayoutManager layout) { + super.setLayoutManager(layout); + if (layout != null && header == null && headerRes != 0) { + // Inflating a child view requires the layout manager to be set. Check here to see if + // any header item is specified in XML and inflate them. + final LayoutInflater inflater = LayoutInflater.from(getContext()); + header = inflater.inflate(headerRes, this, false); + } + } + + @Override + @SuppressWarnings("rawtypes,unchecked") // RecyclerView.setAdapter uses raw type :( + public void setAdapter(Adapter adapter) { + if (header != null && adapter != null) { + final HeaderAdapter headerAdapter = new HeaderAdapter(adapter); + headerAdapter.setHeader(header); + adapter = headerAdapter; } + super.setAdapter(adapter); + } } diff --git a/library/recyclerview/src/com/android/setupwizardlib/view/StickyHeaderRecyclerView.java b/library/recyclerview/src/com/android/setupwizardlib/view/StickyHeaderRecyclerView.java index d51ea56..a5fa69c 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/view/StickyHeaderRecyclerView.java +++ b/library/recyclerview/src/com/android/setupwizardlib/view/StickyHeaderRecyclerView.java @@ -32,112 +32,114 @@ import android.view.WindowInsets; * to be drawn when the sticky element hits the top of the view. * *

There are a few things to note: + * *

    *
  1. The view does not work well with padding. b/16190933 *
  2. If fitsSystemWindows is true, then this will offset the sticking position by the height of - * the system decorations at the top of the screen. + * the system decorations at the top of the screen. *
*/ public class StickyHeaderRecyclerView extends HeaderRecyclerView { - private View mSticky; - private int mStatusBarInset = 0; - private RectF mStickyRect = new RectF(); + private View sticky; + private int statusBarInset = 0; + private final RectF stickyRect = new RectF(); - public StickyHeaderRecyclerView(Context context) { - super(context); - } + public StickyHeaderRecyclerView(Context context) { + super(context); + } - public StickyHeaderRecyclerView(Context context, AttributeSet attrs) { - super(context, attrs); - } + public StickyHeaderRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + } - public StickyHeaderRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public StickyHeaderRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - if (mSticky == null) { - updateStickyView(); - } - if (mSticky != null) { - final View headerView = getHeader(); - if (headerView != null && headerView.getHeight() == 0) { - headerView.layout(0, -headerView.getMeasuredHeight(), - headerView.getMeasuredWidth(), 0); - } - } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (sticky == null) { + updateStickyView(); + } + if (sticky != null) { + final View headerView = getHeader(); + if (headerView != null && headerView.getHeight() == 0) { + headerView.layout(0, -headerView.getMeasuredHeight(), headerView.getMeasuredWidth(), 0); + } } + } - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - super.onMeasure(widthSpec, heightSpec); - if (mSticky != null) { - measureChild(getHeader(), widthSpec, heightSpec); - } + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + super.onMeasure(widthSpec, heightSpec); + if (sticky != null) { + measureChild(getHeader(), widthSpec, heightSpec); } + } - /** - * Call this method when the "sticky" view has changed, so this view can update its internal - * states as well. - */ - public void updateStickyView() { - final View header = getHeader(); - if (header != null) { - mSticky = header.findViewWithTag("sticky"); - } + /** + * Call this method when the "sticky" view has changed, so this view can update its internal + * states as well. + */ + public void updateStickyView() { + final View header = getHeader(); + if (header != null) { + sticky = header.findViewWithTag("sticky"); } + } - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - if (mSticky != null) { - final View headerView = getHeader(); - final int saveCount = canvas.save(); - // The view to draw when sticking to the top - final View drawTarget = headerView != null ? headerView : mSticky; - // The offset to draw the view at when sticky - final int drawOffset = headerView != null ? mSticky.getTop() : 0; - // Position of the draw target, relative to the outside of the scrollView - final int drawTop = drawTarget.getTop(); - if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) { - // RecyclerView does not translate the canvas, so we can simply draw at the top - mStickyRect.set(0, -drawOffset + mStatusBarInset, drawTarget.getWidth(), - drawTarget.getHeight() - drawOffset + mStatusBarInset); - canvas.translate(0, mStickyRect.top); - canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight()); - drawTarget.draw(canvas); - } else { - mStickyRect.setEmpty(); - } - canvas.restoreToCount(saveCount); - } + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (sticky != null) { + final View headerView = getHeader(); + final int saveCount = canvas.save(); + // The view to draw when sticking to the top + final View drawTarget = headerView != null ? headerView : sticky; + // The offset to draw the view at when sticky + final int drawOffset = headerView != null ? sticky.getTop() : 0; + // Position of the draw target, relative to the outside of the scrollView + final int drawTop = drawTarget.getTop(); + if (drawTop + drawOffset < statusBarInset || !drawTarget.isShown()) { + // RecyclerView does not translate the canvas, so we can simply draw at the top + stickyRect.set( + 0, + -drawOffset + statusBarInset, + drawTarget.getWidth(), + drawTarget.getHeight() - drawOffset + statusBarInset); + canvas.translate(0, stickyRect.top); + canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight()); + drawTarget.draw(canvas); + } else { + stickyRect.setEmpty(); + } + canvas.restoreToCount(saveCount); } + } - @Override - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - if (getFitsSystemWindows()) { - mStatusBarInset = insets.getSystemWindowInsetTop(); - insets.replaceSystemWindowInsets( - insets.getSystemWindowInsetLeft(), - 0, /* top */ - insets.getSystemWindowInsetRight(), - insets.getSystemWindowInsetBottom() - ); - } - return insets; + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if (getFitsSystemWindows()) { + statusBarInset = insets.getSystemWindowInsetTop(); + insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + 0, /* top */ + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); } + return insets; + } - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (mStickyRect.contains(ev.getX(), ev.getY())) { - ev.offsetLocation(-mStickyRect.left, -mStickyRect.top); - return getHeader().dispatchTouchEvent(ev); - } else { - return super.dispatchTouchEvent(ev); - } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (stickyRect.contains(ev.getX(), ev.getY())) { + ev.offsetLocation(-stickyRect.left, -stickyRect.top); + return getHeader().dispatchTouchEvent(ev); + } else { + return super.dispatchTouchEvent(ev); } + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/items/RecyclerItemAdapterTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/items/RecyclerItemAdapterTest.java index 6f42e84..bed736e 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/items/RecyclerItemAdapterTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/items/RecyclerItemAdapterTest.java @@ -32,16 +32,13 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RectShape; +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; +import android.widget.FrameLayout; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.widget.FrameLayout; - -import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; - import com.android.setupwizardlib.items.RecyclerItemAdapter.PatchedLayerDrawable; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,111 +47,110 @@ import org.junit.runner.RunWith; @SmallTest public class RecyclerItemAdapterTest { - private Item[] mItems = new Item[5]; - private ItemGroup mItemGroup = new ItemGroup(); - - @Before - public void setUp() throws Exception { - for (int i = 0; i < 5; i++) { - Item item = new Item(); - item.setTitle("TestTitle" + i); - item.setId(i); - // Layout resource: 0 -> 1, 1 -> 11, 2 -> 21, 3 -> 1, 4 -> 11. - // (Resource IDs cannot be 0) - item.setLayoutResource((i % 3) * 10 + 1); - mItems[i] = item; - mItemGroup.addChild(item); - } - } - - @Test - public void testAdapter() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - assertEquals("Adapter should have 5 items", 5, adapter.getItemCount()); - assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); - assertEquals("ID should be same as position", 2, adapter.getItemId(2)); - - // ViewType is same as layout resource for RecyclerItemAdapter - assertEquals("Second item should have view type 21", 21, adapter.getItemViewType(2)); - } - - @Test - public void testGetRootItemHierarchy() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - ItemHierarchy root = adapter.getRootItemHierarchy(); - assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); - } - - @Test - public void testPatchedLayerDrawableNoPadding() { - ShapeDrawable child = new ShapeDrawable(new RectShape()); - child.setPadding(0, 0, 0, 0); - PatchedLayerDrawable drawable = new PatchedLayerDrawable(new Drawable[] { child }); - - Rect padding = new Rect(); - assertFalse("Patched layer drawable should not have padding", drawable.getPadding(padding)); - assertEquals(new Rect(0, 0, 0, 0), padding); - } - - @Test - public void testPatchedLayerDrawableWithPadding() { - ShapeDrawable child = new ShapeDrawable(new RectShape()); - child.setPadding(10, 10, 10, 10); - PatchedLayerDrawable drawable = new PatchedLayerDrawable(new Drawable[] { child }); - - Rect padding = new Rect(); - assertTrue("Patched layer drawable should have padding", drawable.getPadding(padding)); - assertEquals(new Rect(10, 10, 10, 10), padding); - } - - @Test - public void testAdapterNotifications() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - final AdapterDataObserver observer = mock(AdapterDataObserver.class); - adapter.registerAdapterDataObserver(observer); - - mItems[0].setTitle("Child 1"); - verify(observer).onItemRangeChanged(eq(0), eq(1), anyObject()); - - mItemGroup.removeChild(mItems[1]); - verify(observer).onItemRangeRemoved(eq(1), eq(1)); - - mItemGroup.addChild(mItems[1]); - verify(observer).onItemRangeInserted(eq(4), eq(1)); - } - - @Test - public void testCreateViewHolder() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); - - final ItemViewHolder viewHolder = - adapter.onCreateViewHolder(parent, R.layout.test_list_item); - assertNotNull("Background should be set", viewHolder.itemView.getBackground()); - assertEquals("foobar", viewHolder.itemView.getTag()); - } - - @Test - public void testCreateViewHolderNoBackground() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); - - final ItemViewHolder viewHolder = - adapter.onCreateViewHolder(parent, R.layout.test_list_item_no_background); - assertNull("Background should be null", viewHolder.itemView.getBackground()); - } - - @Test - public void testCreateViewHolderWithExistingBackground() { - RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); - FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); - - final ItemViewHolder viewHolder = - adapter.onCreateViewHolder(parent, R.layout.test_existing_background); - Drawable background = viewHolder.itemView.getBackground(); - assertTrue(background instanceof PatchedLayerDrawable); - - PatchedLayerDrawable layerDrawable = (PatchedLayerDrawable) background; - assertTrue(layerDrawable.getDrawable(0) instanceof GradientDrawable); + private Item[] mItems = new Item[5]; + private ItemGroup mItemGroup = new ItemGroup(); + + @Before + public void setUp() throws Exception { + for (int i = 0; i < 5; i++) { + Item item = new Item(); + item.setTitle("TestTitle" + i); + item.setId(i); + // Layout resource: 0 -> 1, 1 -> 11, 2 -> 21, 3 -> 1, 4 -> 11. + // (Resource IDs cannot be 0) + item.setLayoutResource((i % 3) * 10 + 1); + mItems[i] = item; + mItemGroup.addChild(item); } + } + + @Test + public void testAdapter() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + assertEquals("Adapter should have 5 items", 5, adapter.getItemCount()); + assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); + assertEquals("ID should be same as position", 2, adapter.getItemId(2)); + + // ViewType is same as layout resource for RecyclerItemAdapter + assertEquals("Second item should have view type 21", 21, adapter.getItemViewType(2)); + } + + @Test + public void testGetRootItemHierarchy() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + ItemHierarchy root = adapter.getRootItemHierarchy(); + assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); + } + + @Test + public void testPatchedLayerDrawableNoPadding() { + ShapeDrawable child = new ShapeDrawable(new RectShape()); + child.setPadding(0, 0, 0, 0); + PatchedLayerDrawable drawable = new PatchedLayerDrawable(new Drawable[] {child}); + + Rect padding = new Rect(); + assertFalse("Patched layer drawable should not have padding", drawable.getPadding(padding)); + assertEquals(new Rect(0, 0, 0, 0), padding); + } + + @Test + public void testPatchedLayerDrawableWithPadding() { + ShapeDrawable child = new ShapeDrawable(new RectShape()); + child.setPadding(10, 10, 10, 10); + PatchedLayerDrawable drawable = new PatchedLayerDrawable(new Drawable[] {child}); + + Rect padding = new Rect(); + assertTrue("Patched layer drawable should have padding", drawable.getPadding(padding)); + assertEquals(new Rect(10, 10, 10, 10), padding); + } + + @Test + public void testAdapterNotifications() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + final AdapterDataObserver observer = mock(AdapterDataObserver.class); + adapter.registerAdapterDataObserver(observer); + + mItems[0].setTitle("Child 1"); + verify(observer).onItemRangeChanged(eq(0), eq(1), anyObject()); + + mItemGroup.removeChild(mItems[1]); + verify(observer).onItemRangeRemoved(eq(1), eq(1)); + + mItemGroup.addChild(mItems[1]); + verify(observer).onItemRangeInserted(eq(4), eq(1)); + } + + @Test + public void testCreateViewHolder() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); + + final ItemViewHolder viewHolder = adapter.onCreateViewHolder(parent, R.layout.test_list_item); + assertNotNull("Background should be set", viewHolder.itemView.getBackground()); + assertEquals("foobar", viewHolder.itemView.getTag()); + } + + @Test + public void testCreateViewHolderNoBackground() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); + + final ItemViewHolder viewHolder = + adapter.onCreateViewHolder(parent, R.layout.test_list_item_no_background); + assertNull("Background should be null", viewHolder.itemView.getBackground()); + } + + @Test + public void testCreateViewHolderWithExistingBackground() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getContext()); + + final ItemViewHolder viewHolder = + adapter.onCreateViewHolder(parent, R.layout.test_existing_background); + Drawable background = viewHolder.itemView.getBackground(); + assertTrue(background instanceof PatchedLayerDrawable); + + PatchedLayerDrawable layerDrawable = (PatchedLayerDrawable) background; + assertTrue(layerDrawable.getDrawable(0) instanceof GradientDrawable); + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/template/RecyclerMixinTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/template/RecyclerMixinTest.java index ece4bf9..f295b91 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/template/RecyclerMixinTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/template/RecyclerMixinTest.java @@ -30,17 +30,14 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import android.view.View; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,120 +48,119 @@ import org.mockito.MockitoAnnotations; @SmallTest public class RecyclerMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; + private Context mContext; + private TemplateLayout mTemplateLayout; - private RecyclerView mRecyclerView; + private RecyclerView mRecyclerView; - @Mock - private Adapter mAdapter; + @Mock private Adapter mAdapter; - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); - mRecyclerView = mock(RecyclerView.class, delegatesTo(new RecyclerView(mContext))); + mRecyclerView = mock(RecyclerView.class, delegatesTo(new RecyclerView(mContext))); - doReturn(true).when(mTemplateLayout).isLayoutDirectionResolved(); - } + doReturn(true).when(mTemplateLayout).isLayoutDirectionResolved(); + } - @Test - public void testGetRecyclerView() { - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - assertSame(mRecyclerView, mixin.getRecyclerView()); - } + @Test + public void testGetRecyclerView() { + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + assertSame(mRecyclerView, mixin.getRecyclerView()); + } - @Test - public void testGetAdapter() { - mRecyclerView.setAdapter(mAdapter); + @Test + public void testGetAdapter() { + mRecyclerView.setAdapter(mAdapter); - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - assertSame(mAdapter, mixin.getAdapter()); - } + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + assertSame(mAdapter, mixin.getAdapter()); + } - @Test - public void testSetAdapter() { - assertNull(mRecyclerView.getAdapter()); + @Test + public void testSetAdapter() { + assertNull(mRecyclerView.getAdapter()); - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setAdapter(mAdapter); + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setAdapter(mAdapter); - assertSame(mAdapter, mRecyclerView.getAdapter()); - } + assertSame(mAdapter, mRecyclerView.getAdapter()); + } - @Test - public void testDividerLegacyInset() { - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setDividerInset(123); + @Test + public void testDividerLegacyInset() { + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setDividerInset(123); - assertEquals(123, mixin.getDividerInset()); + assertEquals(123, mixin.getDividerInset()); - final Drawable divider = mixin.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mixin.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(123, 0, 0, 0), rect); - } + assertEquals(new Rect(123, 0, 0, 0), rect); + } - @Test - public void testDividerInsets() { - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setDividerInsets(123, 456); + @Test + public void testDividerInsets() { + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setDividerInsets(123, 456); - assertEquals(123, mixin.getDividerInsetStart()); - assertEquals(456, mixin.getDividerInsetEnd()); + assertEquals(123, mixin.getDividerInsetStart()); + assertEquals(456, mixin.getDividerInsetEnd()); - final Drawable divider = mixin.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mixin.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(123, 0, 456, 0), rect); - } + assertEquals(new Rect(123, 0, 456, 0), rect); + } - @Test - public void testDividerInsetLegacyRtl() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); + @Test + public void testDividerInsetLegacyRtl() { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setDividerInset(123); + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setDividerInset(123); - assertEquals(123, mixin.getDividerInset()); + assertEquals(123, mixin.getDividerInset()); - final Drawable divider = mixin.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mixin.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(0, 0, 123, 0), rect); - } - // else the test passes + assertEquals(new Rect(0, 0, 123, 0), rect); } + // else the test passes + } - @Test - public void testDividerInsetsRtl() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); + @Test + public void testDividerInsetsRtl() { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); - RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); - mixin.setDividerInsets(123, 456); + RecyclerMixin mixin = new RecyclerMixin(mTemplateLayout, mRecyclerView); + mixin.setDividerInsets(123, 456); - assertEquals(123, mixin.getDividerInsetStart()); - assertEquals(456, mixin.getDividerInsetEnd()); + assertEquals(123, mixin.getDividerInsetStart()); + assertEquals(456, mixin.getDividerInsetEnd()); - final Drawable divider = mixin.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mixin.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(456, 0, 123, 0), rect); - } - // else the test passes + assertEquals(new Rect(456, 0, 123, 0), rect); } + // else the test passes + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java index 9cf33b9..a3a0cfe 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/DividerItemDecorationTest.java @@ -28,17 +28,14 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; -import android.view.ViewGroup; - -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.DividerItemDecoration; - import org.junit.Test; import org.junit.runner.RunWith; @@ -46,176 +43,179 @@ import org.junit.runner.RunWith; @SmallTest public class DividerItemDecorationTest { - @Test - public void testDivider() { - final DividerItemDecoration decoration = new DividerItemDecoration(); - Drawable divider = new ColorDrawable(); - decoration.setDivider(divider); - assertSame("Divider should be same as set", divider, decoration.getDivider()); - } + @Test + public void testDivider() { + final DividerItemDecoration decoration = new DividerItemDecoration(); + Drawable divider = new ColorDrawable(); + decoration.setDivider(divider); + assertSame("Divider should be same as set", divider, decoration.getDivider()); + } + + @Test + public void testDividerHeight() { + final DividerItemDecoration decoration = new DividerItemDecoration(); + decoration.setDividerHeight(123); + assertEquals("Divider height should be 123", 123, decoration.getDividerHeight()); + } + + @Test + public void testShouldDrawDividerBelowWithEitherCondition() { + // Set up the item decoration, with 1px red divider line + final DividerItemDecoration decoration = new DividerItemDecoration(); + Drawable divider = new ColorDrawable(Color.RED); + decoration.setDivider(divider); + decoration.setDividerHeight(1); + + Bitmap bitmap = drawDecoration(decoration, true, true); + + // Draw the expected result on a bitmap + Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); + Canvas expectedCanvas = new Canvas(expectedBitmap); + Paint paint = new Paint(); + paint.setColor(Color.RED); + expectedCanvas.drawRect(0, 5, 20, 6, paint); + expectedCanvas.drawRect(0, 10, 20, 11, paint); + expectedCanvas.drawRect(0, 15, 20, 16, paint); + // Compare the two bitmaps + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, false, true); + // should still be the same. + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, true, false); + // last item should not have a divider below it now + paint.setColor(Color.TRANSPARENT); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + expectedCanvas.drawRect(0, 15, 20, 16, paint); + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, false, false); + // everything should be transparent now + expectedCanvas.drawRect(0, 5, 20, 6, paint); + expectedCanvas.drawRect(0, 10, 20, 11, paint); + assertBitmapEquals(expectedBitmap, bitmap); + } + + @Test + public void testShouldDrawDividerBelowWithBothCondition() { + // Set up the item decoration, with 1px green divider line + final DividerItemDecoration decoration = new DividerItemDecoration(); + Drawable divider = new ColorDrawable(Color.GREEN); + decoration.setDivider(divider); + decoration.setDividerHeight(1); + decoration.setDividerCondition(DividerItemDecoration.DIVIDER_CONDITION_BOTH); + + Bitmap bitmap = drawDecoration(decoration, true, true); + Paint paint = new Paint(); + paint.setColor(Color.GREEN); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); + Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); + Canvas expectedCanvas = new Canvas(expectedBitmap); + expectedCanvas.drawRect(0, 5, 20, 6, paint); + expectedCanvas.drawRect(0, 10, 20, 11, paint); + expectedCanvas.drawRect(0, 15, 20, 16, paint); + // Should have all the dividers + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, false, true); + paint.setColor(Color.TRANSPARENT); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + expectedCanvas.drawRect(0, 5, 20, 6, paint); + expectedCanvas.drawRect(0, 10, 20, 11, paint); + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, true, false); + // nothing should be drawn now. + expectedCanvas.drawRect(0, 15, 20, 16, paint); + assertBitmapEquals(expectedBitmap, bitmap); + + bitmap.recycle(); + bitmap = drawDecoration(decoration, false, false); + assertBitmapEquals(expectedBitmap, bitmap); + } + + private Bitmap drawDecoration( + DividerItemDecoration decoration, + final boolean allowDividerAbove, + final boolean allowDividerBelow) { + // Set up the canvas to be drawn + Bitmap bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); + Canvas canvas = new Canvas(bitmap); + + final Context context = InstrumentationRegistry.getContext(); + // Set up recycler view with vertical linear layout manager + RecyclerView testRecyclerView = new RecyclerView(context); + testRecyclerView.setLayoutManager(new LinearLayoutManager(context)); + + // Set up adapter with 3 items, each 5px tall + testRecyclerView.setAdapter( + new RecyclerView.Adapter() { + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + final View itemView = new View(context); + itemView.setMinimumWidth(20); + itemView.setMinimumHeight(5); + return ViewHolder.createInstance(itemView, allowDividerAbove, allowDividerBelow); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {} + + @Override + public int getItemCount() { + return 3; + } + }); - @Test - public void testDividerHeight() { - final DividerItemDecoration decoration = new DividerItemDecoration(); - decoration.setDividerHeight(123); - assertEquals("Divider height should be 123", 123, decoration.getDividerHeight()); + testRecyclerView.layout(0, 0, 20, 20); + decoration.onDraw(canvas, testRecyclerView, null); + return bitmap; + } + + private void assertBitmapEquals(Bitmap expected, Bitmap actual) { + assertEquals("Width should be the same", expected.getWidth(), actual.getWidth()); + assertEquals("Height should be the same", expected.getHeight(), actual.getHeight()); + for (int x = 0; x < expected.getWidth(); x++) { + for (int y = 0; y < expected.getHeight(); y++) { + assertEquals( + "Pixel at (" + x + ", " + y + ") should be the same", + expected.getPixel(x, y), + actual.getPixel(x, y)); + } } + } - @Test - public void testShouldDrawDividerBelowWithEitherCondition() { - // Set up the item decoration, with 1px red divider line - final DividerItemDecoration decoration = new DividerItemDecoration(); - Drawable divider = new ColorDrawable(Color.RED); - decoration.setDivider(divider); - decoration.setDividerHeight(1); - - Bitmap bitmap = drawDecoration(decoration, true, true); - - // Draw the expected result on a bitmap - Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); - Canvas expectedCanvas = new Canvas(expectedBitmap); - Paint paint = new Paint(); - paint.setColor(Color.RED); - expectedCanvas.drawRect(0, 5, 20, 6, paint); - expectedCanvas.drawRect(0, 10, 20, 11, paint); - expectedCanvas.drawRect(0, 15, 20, 16, paint); - // Compare the two bitmaps - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, false, true); - // should still be the same. - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, true, false); - // last item should not have a divider below it now - paint.setColor(Color.TRANSPARENT); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - expectedCanvas.drawRect(0, 15, 20, 16, paint); - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, false, false); - // everything should be transparent now - expectedCanvas.drawRect(0, 5, 20, 6, paint); - expectedCanvas.drawRect(0, 10, 20, 11, paint); - assertBitmapEquals(expectedBitmap, bitmap); + private static class ViewHolder extends RecyclerView.ViewHolder + implements DividerItemDecoration.DividedViewHolder { - } + private boolean mAllowDividerAbove; + private boolean mAllowDividerBelow; - @Test - public void testShouldDrawDividerBelowWithBothCondition() { - // Set up the item decoration, with 1px green divider line - final DividerItemDecoration decoration = new DividerItemDecoration(); - Drawable divider = new ColorDrawable(Color.GREEN); - decoration.setDivider(divider); - decoration.setDividerHeight(1); - decoration.setDividerCondition(DividerItemDecoration.DIVIDER_CONDITION_BOTH); - - Bitmap bitmap = drawDecoration(decoration, true, true); - Paint paint = new Paint(); - paint.setColor(Color.GREEN); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); - Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); - Canvas expectedCanvas = new Canvas(expectedBitmap); - expectedCanvas.drawRect(0, 5, 20, 6, paint); - expectedCanvas.drawRect(0, 10, 20, 11, paint); - expectedCanvas.drawRect(0, 15, 20, 16, paint); - // Should have all the dividers - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, false, true); - paint.setColor(Color.TRANSPARENT); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - expectedCanvas.drawRect(0, 5, 20, 6, paint); - expectedCanvas.drawRect(0, 10, 20, 11, paint); - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, true, false); - // nothing should be drawn now. - expectedCanvas.drawRect(0, 15, 20, 16, paint); - assertBitmapEquals(expectedBitmap, bitmap); - - bitmap.recycle(); - bitmap = drawDecoration(decoration, false, false); - assertBitmapEquals(expectedBitmap, bitmap); + public static ViewHolder createInstance( + View itemView, boolean allowDividerAbove, boolean allowDividerBelow) { + return new ViewHolder(itemView, allowDividerAbove, allowDividerBelow); } - private Bitmap drawDecoration(DividerItemDecoration decoration, final boolean allowDividerAbove, - final boolean allowDividerBelow) { - // Set up the canvas to be drawn - Bitmap bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444); - Canvas canvas = new Canvas(bitmap); - - final Context context = InstrumentationRegistry.getContext(); - // Set up recycler view with vertical linear layout manager - RecyclerView testRecyclerView = new RecyclerView(context); - testRecyclerView.setLayoutManager(new LinearLayoutManager(context)); - - // Set up adapter with 3 items, each 5px tall - testRecyclerView.setAdapter(new RecyclerView.Adapter() { - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { - final View itemView = new View(context); - itemView.setMinimumWidth(20); - itemView.setMinimumHeight(5); - return ViewHolder.createInstance(itemView, allowDividerAbove, allowDividerBelow); - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { - } - - @Override - public int getItemCount() { - return 3; - } - }); - - testRecyclerView.layout(0, 0, 20, 20); - decoration.onDraw(canvas, testRecyclerView, null); - return bitmap; + private ViewHolder(View itemView, boolean allowDividerAbove, boolean allowDividerBelow) { + super(itemView); + mAllowDividerAbove = allowDividerAbove; + mAllowDividerBelow = allowDividerBelow; } - private void assertBitmapEquals(Bitmap expected, Bitmap actual) { - assertEquals("Width should be the same", expected.getWidth(), actual.getWidth()); - assertEquals("Height should be the same", expected.getHeight(), actual.getHeight()); - for (int x = 0; x < expected.getWidth(); x++) { - for (int y = 0; y < expected.getHeight(); y++) { - assertEquals("Pixel at (" + x + ", " + y + ") should be the same", - expected.getPixel(x, y), actual.getPixel(x, y)); - } - } + @Override + public boolean isDividerAllowedAbove() { + return mAllowDividerAbove; } - private static class ViewHolder extends RecyclerView.ViewHolder - implements DividerItemDecoration.DividedViewHolder { - - private boolean mAllowDividerAbove; - private boolean mAllowDividerBelow; - - public static ViewHolder createInstance(View itemView, boolean allowDividerAbove, - boolean allowDividerBelow) { - return new ViewHolder(itemView, allowDividerAbove, allowDividerBelow); - } - - private ViewHolder(View itemView, boolean allowDividerAbove, boolean allowDividerBelow) { - super(itemView); - mAllowDividerAbove = allowDividerAbove; - mAllowDividerBelow = allowDividerBelow; - } - - @Override - public boolean isDividerAllowedAbove() { - return mAllowDividerAbove; - } - - @Override - public boolean isDividerAllowedBelow() { - return mAllowDividerBelow; - } + @Override + public boolean isDividerAllowedBelow() { + return mAllowDividerBelow; } + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java index 4d2876d..d55ba23 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifPreferenceLayoutTest.java @@ -24,18 +24,15 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.widget.RecyclerView; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.GlifPreferenceLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,62 +41,63 @@ import org.junit.runner.RunWith; @SmallTest public class GlifPreferenceLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); - } - - @Test - public void testDefaultTemplate() { - GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - } - - @Test - public void testGetRecyclerView() { - GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + } + + @Test + public void testDefaultTemplate() { + GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + } + + @Test + public void testGetRecyclerView() { + GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + } + + @Test + public void testOnCreateRecyclerView() { + GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + final RecyclerView recyclerView = + layout.onCreateRecyclerView( + LayoutInflater.from(mContext), layout, null /* savedInstanceState */); + assertNotNull("RecyclerView created should not be null", recyclerView); + } + + @Test + public void testDividerInset() { + GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertPreferenceTemplateInflated(layout); - @Test - public void testOnCreateRecyclerView() { - GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - final RecyclerView recyclerView = layout.onCreateRecyclerView(LayoutInflater.from(mContext), - layout, null /* savedInstanceState */); - assertNotNull("RecyclerView created should not be null", recyclerView); - } - - @Test - public void testDividerInset() { - GlifPreferenceLayout layout = new GlifPreferenceLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertPreferenceTemplateInflated(layout); + layout.addView( + layout.onCreateRecyclerView( + LayoutInflater.from(mContext), layout, null /* savedInstanceState */)); - layout.addView(layout.onCreateRecyclerView(LayoutInflater.from(mContext), layout, - null /* savedInstanceState */)); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } + private void assertPreferenceTemplateInflated(GlifPreferenceLayout layout) { + View contentContainer = layout.findViewById(R.id.suw_layout_content); + assertTrue( + "@id/suw_layout_content should be a ViewGroup", contentContainer instanceof ViewGroup); - private void assertPreferenceTemplateInflated(GlifPreferenceLayout layout) { - View contentContainer = layout.findViewById(R.id.suw_layout_content); - assertTrue("@id/suw_layout_content should be a ViewGroup", - contentContainer instanceof ViewGroup); - - assertNotNull("Header text view should not be null", - layout.findManagedViewById(R.id.suw_layout_title)); - assertNotNull("Icon view should not be null", - layout.findManagedViewById(R.id.suw_layout_icon)); - } + assertNotNull( + "Header text view should not be null", layout.findManagedViewById(R.id.suw_layout_title)); + assertNotNull("Icon view should not be null", layout.findManagedViewById(R.id.suw_layout_icon)); + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java index a68faf0..5db7db5 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/GlifRecyclerLayoutTest.java @@ -26,20 +26,17 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.GlifRecyclerLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,130 +45,127 @@ import org.junit.runner.RunWith; @SmallTest public class GlifRecyclerLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); - } - - @Test - public void testDefaultTemplate() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - GlifRecyclerLayout layout = (GlifRecyclerLayout) - inflater.inflate(R.layout.test_glif_recycler_layout, null); - assertRecyclerTemplateInflated(layout); - } - - @Test - public void testGetRecyclerView() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); - } - - @Test - public void testAdapter() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - - final RecyclerView.Adapter adapter = createTestAdapter(1); - layout.setAdapter(adapter); - - final RecyclerView.Adapter gotAdapter = layout.getAdapter(); - // Note: The wrapped adapter should be returned, not the HeaderAdapter. - assertSame("Adapter got from GlifRecyclerLayout should be same as set", - adapter, gotAdapter); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + } + + @Test + public void testDefaultTemplate() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + GlifRecyclerLayout layout = + (GlifRecyclerLayout) inflater.inflate(R.layout.test_glif_recycler_layout, null); + assertRecyclerTemplateInflated(layout); + } + + @Test + public void testGetRecyclerView() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + } + + @Test + public void testAdapter() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + + final RecyclerView.Adapter adapter = createTestAdapter(1); + layout.setAdapter(adapter); + + final RecyclerView.Adapter gotAdapter = layout.getAdapter(); + // Note: The wrapped adapter should be returned, not the HeaderAdapter. + assertSame("Adapter got from GlifRecyclerLayout should be same as set", adapter, gotAdapter); + } + + @Test + public void testLayout() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + + layout.setAdapter(createTestAdapter(3)); + + layout.measure( + MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY)); + layout.layout(0, 0, 500, 500); + // Test that the layout code doesn't crash. + } + + @Test + public void testDividerInsetLegacy() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertRecyclerTemplateInflated(layout); - @Test - public void testLayout() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - layout.setAdapter(createTestAdapter(3)); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - layout.measure( - MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY)); - layout.layout(0, 0, 500, 500); - // Test that the layout code doesn't crash. + @Test + public void testDividerInsets() { + GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } - - @Test - public void testDividerInsetLegacy() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertRecyclerTemplateInflated(layout); - - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testDividerInsets() { - GlifRecyclerLayout layout = new GlifRecyclerLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertRecyclerTemplateInflated(layout); - - layout.setDividerInsets(10, 15); - assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); - assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testTemplateWithNoRecyclerView() { - try { - new GlifRecyclerLayout(mContext, R.layout.suw_glif_template); - fail("Creating GlifRecyclerLayout with no recycler view should throw exception"); - } catch (Exception e) { - // pass - } - } - - private void assertRecyclerTemplateInflated(GlifRecyclerLayout layout) { - View recyclerView = layout.findViewById(R.id.suw_recycler_view); - assertTrue("@id/suw_recycler_view should be a RecyclerView", - recyclerView instanceof RecyclerView); - - assertNotNull("Header text view should not be null", - layout.findManagedViewById(R.id.suw_layout_title)); - assertNotNull("Icon view should not be null", - layout.findManagedViewById(R.id.suw_layout_icon)); - } - - private Adapter createTestAdapter(final int itemCount) { - return new RecyclerView.Adapter() { - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int position) { - return new RecyclerView.ViewHolder(new View(parent.getContext())) {}; - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { - } - - @Override - public int getItemCount() { - return itemCount; - } - }; + assertRecyclerTemplateInflated(layout); + + layout.setDividerInsets(10, 15); + assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); + assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); + + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } + + @Test + public void testTemplateWithNoRecyclerView() { + try { + new GlifRecyclerLayout(mContext, R.layout.suw_glif_template); + fail("Creating GlifRecyclerLayout with no recycler view should throw exception"); + } catch (Exception e) { + // pass } + } + + private void assertRecyclerTemplateInflated(GlifRecyclerLayout layout) { + View recyclerView = layout.findViewById(R.id.suw_recycler_view); + assertTrue( + "@id/suw_recycler_view should be a RecyclerView", recyclerView instanceof RecyclerView); + + assertNotNull( + "Header text view should not be null", layout.findManagedViewById(R.id.suw_layout_title)); + assertNotNull("Icon view should not be null", layout.findManagedViewById(R.id.suw_layout_icon)); + } + + private Adapter createTestAdapter(final int itemCount) { + return new RecyclerView.Adapter() { + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int position) { + return new RecyclerView.ViewHolder(new View(parent.getContext())) {}; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {} + + @Override + public int getItemCount() { + return itemCount; + } + }; + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/HeaderRecyclerViewTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/HeaderRecyclerViewTest.java index 9af68a7..3d0f9da 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/HeaderRecyclerViewTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/HeaderRecyclerViewTest.java @@ -19,160 +19,143 @@ package com.android.setupwizardlib.test; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; -import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - import com.android.setupwizardlib.view.HeaderRecyclerView.HeaderAdapter; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * Test for {@link com.android.setupwizardlib.view.HeaderRecyclerView} - */ +/** Test for {@link com.android.setupwizardlib.view.HeaderRecyclerView} */ @RunWith(AndroidJUnit4.class) @SmallTest public class HeaderRecyclerViewTest { - private TestAdapter mWrappedAdapter; - private HeaderAdapter mHeaderAdapter; - - @Mock - private RecyclerView.AdapterDataObserver mObserver; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mWrappedAdapter = new TestAdapter(); - - mHeaderAdapter = new HeaderAdapter(mWrappedAdapter); - mHeaderAdapter.registerAdapterDataObserver(mObserver); - } - - /** - * Test that notifyDataSetChanged gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyChanged() { - mWrappedAdapter.notifyDataSetChanged(); - - verify(mObserver).onChanged(); + private TestAdapter mWrappedAdapter; + private HeaderAdapter mHeaderAdapter; + + @Mock private RecyclerView.AdapterDataObserver mObserver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mWrappedAdapter = new TestAdapter(); + + mHeaderAdapter = new HeaderAdapter(mWrappedAdapter); + mHeaderAdapter.registerAdapterDataObserver(mObserver); + } + + /** Test that notifyDataSetChanged gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyChanged() { + mWrappedAdapter.notifyDataSetChanged(); + + verify(mObserver).onChanged(); + } + + /** Test that notifyItemChanged gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyItemChangedNoHeader() { + mWrappedAdapter.notifyItemChanged(12); + + verify(mObserver).onItemRangeChanged(eq(12), eq(1), eq(null)); + } + + /** + * Test that notifyItemChanged gets propagated by HeaderRecyclerView's adapter and adds 1 to the + * position for the extra header items. + */ + @Test + public void testNotifyItemChangedWithHeader() { + mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); + mWrappedAdapter.notifyItemChanged(12); + + verify(mObserver).onItemRangeChanged(eq(13), eq(1), eq(null)); + } + + /** Test that notifyItemInserted gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyItemInsertedNoHeader() { + mWrappedAdapter.notifyItemInserted(12); + + verify(mObserver).onItemRangeInserted(eq(12), eq(1)); + } + + /** + * Test that notifyItemInserted gets propagated by HeaderRecyclerView's adapter and adds 1 to the + * position for the extra header item. + */ + @Test + public void testNotifyItemInsertedWithHeader() { + mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); + mWrappedAdapter.notifyItemInserted(12); + + verify(mObserver).onItemRangeInserted(eq(13), eq(1)); + } + + /** Test that notifyItemRemoved gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyItemRemovedNoHeader() { + mWrappedAdapter.notifyItemRemoved(12); + + verify(mObserver).onItemRangeRemoved(eq(12), eq(1)); + } + + /** + * Test that notifyItemRemoved gets propagated by HeaderRecyclerView's adapter and adds 1 to the + * position for the extra header item. + */ + @Test + public void testNotifyItemRemovedWithHeader() { + mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); + mWrappedAdapter.notifyItemRemoved(12); + + verify(mObserver).onItemRangeRemoved(eq(13), eq(1)); + } + + /** Test that notifyItemMoved gets propagated by HeaderRecyclerView's adapter. */ + @Test + public void testNotifyItemMovedNoHeader() { + mWrappedAdapter.notifyItemMoved(12, 18); + + verify(mObserver).onItemRangeMoved(eq(12), eq(18), eq(1)); + } + + /** + * Test that notifyItemMoved gets propagated by HeaderRecyclerView's adapter and adds 1 to the + * position for the extra header item. + */ + @Test + public void testNotifyItemMovedWithHeader() { + mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); + mWrappedAdapter.notifyItemMoved(12, 18); + + verify(mObserver).onItemRangeMoved(eq(13), eq(19), eq(1)); + } + + /** + * Test adapter to be wrapped inside {@link HeaderAdapter} to to send item change notifications. + */ + public static class TestAdapter extends RecyclerView.Adapter { + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + return null; } - /** - * Test that notifyItemChanged gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyItemChangedNoHeader() { - mWrappedAdapter.notifyItemChanged(12); - - verify(mObserver).onItemRangeChanged(eq(12), eq(1), eq(null)); - } - - /** - * Test that notifyItemChanged gets propagated by HeaderRecyclerView's adapter and adds 1 to the - * position for the extra header items. - */ - @Test - public void testNotifyItemChangedWithHeader() { - mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); - mWrappedAdapter.notifyItemChanged(12); - - verify(mObserver).onItemRangeChanged(eq(13), eq(1), eq(null)); - } - - /** - * Test that notifyItemInserted gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyItemInsertedNoHeader() { - mWrappedAdapter.notifyItemInserted(12); - - verify(mObserver).onItemRangeInserted(eq(12), eq(1)); - } - - /** - * Test that notifyItemInserted gets propagated by HeaderRecyclerView's adapter and adds 1 to - * the position for the extra header item. - */ - @Test - public void testNotifyItemInsertedWithHeader() { - mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); - mWrappedAdapter.notifyItemInserted(12); - - verify(mObserver).onItemRangeInserted(eq(13), eq(1)); - } - - /** - * Test that notifyItemRemoved gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyItemRemovedNoHeader() { - mWrappedAdapter.notifyItemRemoved(12); - - verify(mObserver).onItemRangeRemoved(eq(12), eq(1)); - } - - /** - * Test that notifyItemRemoved gets propagated by HeaderRecyclerView's adapter and adds 1 to - * the position for the extra header item. - */ - @Test - public void testNotifyItemRemovedWithHeader() { - mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); - mWrappedAdapter.notifyItemRemoved(12); - - verify(mObserver).onItemRangeRemoved(eq(13), eq(1)); - } - - /** - * Test that notifyItemMoved gets propagated by HeaderRecyclerView's adapter. - */ - @Test - public void testNotifyItemMovedNoHeader() { - mWrappedAdapter.notifyItemMoved(12, 18); - - verify(mObserver).onItemRangeMoved(eq(12), eq(18), eq(1)); - } - - /** - * Test that notifyItemMoved gets propagated by HeaderRecyclerView's adapter and adds 1 to - * the position for the extra header item. - */ - @Test - public void testNotifyItemMovedWithHeader() { - mHeaderAdapter.setHeader(new View(InstrumentationRegistry.getTargetContext())); - mWrappedAdapter.notifyItemMoved(12, 18); - - verify(mObserver).onItemRangeMoved(eq(13), eq(19), eq(1)); - } - - /** - * Test adapter to be wrapped inside {@link HeaderAdapter} to to send item change notifications. - */ - public static class TestAdapter extends RecyclerView.Adapter { - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { - return null; - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { - } + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {} - @Override - public int getItemCount() { - return 0; - } + @Override + public int getItemCount() { + return 0; } + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java index 316793f..39929dc 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardPreferenceLayoutTest.java @@ -24,18 +24,15 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.widget.RecyclerView; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.SetupWizardPreferenceLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,62 +41,65 @@ import org.junit.runner.RunWith; @SmallTest public class SetupWizardPreferenceLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - } - - @Test - public void testDefaultTemplate() { - SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - } - - @Test - public void testGetRecyclerView() { - SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + } + + @Test + public void testDefaultTemplate() { + SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + } + + @Test + public void testGetRecyclerView() { + SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + } + + @Test + public void testOnCreateRecyclerView() { + SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); + assertPreferenceTemplateInflated(layout); + final RecyclerView recyclerView = + layout.onCreateRecyclerView( + LayoutInflater.from(mContext), layout, null /* savedInstanceState */); + assertNotNull("RecyclerView created should not be null", recyclerView); + } + + @Test + public void testDividerInset() { + SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertPreferenceTemplateInflated(layout); - @Test - public void testOnCreateRecyclerView() { - SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); - assertPreferenceTemplateInflated(layout); - final RecyclerView recyclerView = layout.onCreateRecyclerView(LayoutInflater.from(mContext), - layout, null /* savedInstanceState */); - assertNotNull("RecyclerView created should not be null", recyclerView); - } - - @Test - public void testDividerInset() { - SetupWizardPreferenceLayout layout = new SetupWizardPreferenceLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertPreferenceTemplateInflated(layout); + layout.addView( + layout.onCreateRecyclerView( + LayoutInflater.from(mContext), layout, null /* savedInstanceState */)); - layout.addView(layout.onCreateRecyclerView(LayoutInflater.from(mContext), layout, - null /* savedInstanceState */)); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } + private void assertPreferenceTemplateInflated(SetupWizardPreferenceLayout layout) { + View contentContainer = layout.findViewById(R.id.suw_layout_content); + assertTrue( + "@id/suw_layout_content should be a ViewGroup", contentContainer instanceof ViewGroup); - private void assertPreferenceTemplateInflated(SetupWizardPreferenceLayout layout) { - View contentContainer = layout.findViewById(R.id.suw_layout_content); - assertTrue("@id/suw_layout_content should be a ViewGroup", - contentContainer instanceof ViewGroup); - - assertNotNull("Header text view should not be null", - layout.findManagedViewById(R.id.suw_layout_title)); - assertNotNull("Decoration view should not be null", - layout.findManagedViewById(R.id.suw_layout_decor)); - } + assertNotNull( + "Header text view should not be null", layout.findManagedViewById(R.id.suw_layout_title)); + assertNotNull( + "Decoration view should not be null", layout.findManagedViewById(R.id.suw_layout_decor)); + } } diff --git a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java index bbe773b..46a665d 100644 --- a/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java +++ b/library/recyclerview/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardRecyclerLayoutTest.java @@ -26,21 +26,18 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.SetupWizardRecyclerLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,133 +46,129 @@ import org.junit.runner.RunWith; @SmallTest public class SetupWizardRecyclerLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - } - - @Test - public void testDefaultTemplate() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - SetupWizardRecyclerLayout layout = (SetupWizardRecyclerLayout) - inflater.inflate(R.layout.test_recycler_layout, null); - assertRecyclerTemplateInflated(layout); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + } + + @Test + public void testDefaultTemplate() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + SetupWizardRecyclerLayout layout = + (SetupWizardRecyclerLayout) inflater.inflate(R.layout.test_recycler_layout, null); + assertRecyclerTemplateInflated(layout); + } + + @Test + public void testGetRecyclerView() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); + } + + @Test + public void testAdapter() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + + final Adapter adapter = createTestAdapter(1); + layout.setAdapter(adapter); + + final Adapter gotAdapter = layout.getAdapter(); + // Note: The wrapped adapter should be returned, not the HeaderAdapter. + assertSame("Adapter got from SetupWizardLayout should be same as set", adapter, gotAdapter); + } + + @Test + public void testLayout() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + assertRecyclerTemplateInflated(layout); + + layout.setAdapter(createTestAdapter(3)); + + layout.measure( + MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY)); + layout.layout(0, 0, 500, 500); + // Test that the layout code doesn't crash. + } + + @Test + public void testDividerInsetLegacy() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertRecyclerTemplateInflated(layout); - @Test - public void testGetRecyclerView() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - assertNotNull("getRecyclerView should not be null", layout.getRecyclerView()); - } - - @Test - public void testAdapter() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - - final Adapter adapter = createTestAdapter(1); - layout.setAdapter(adapter); - - final Adapter gotAdapter = layout.getAdapter(); - // Note: The wrapped adapter should be returned, not the HeaderAdapter. - assertSame("Adapter got from SetupWizardLayout should be same as set", - adapter, gotAdapter); - } - - @Test - public void testLayout() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - assertRecyclerTemplateInflated(layout); - - layout.setAdapter(createTestAdapter(3)); - - layout.measure( - MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY)); - layout.layout(0, 0, 500, 500); - // Test that the layout code doesn't crash. - } - - @Test - public void testDividerInsetLegacy() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertRecyclerTemplateInflated(layout); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + @Test + public void testDividerInsets() { + SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } - - @Test - public void testDividerInsets() { - SetupWizardRecyclerLayout layout = new SetupWizardRecyclerLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertRecyclerTemplateInflated(layout); - - layout.setDividerInsets(10, 15); - assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); - assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testTemplateWithNoRecyclerView() { - try { - new SetupWizardRecyclerLayout( - mContext, - R.layout.suw_glif_template, - R.id.suw_recycler_view); - fail("Creating SetupWizardRecyclerLayout with no recycler view should throw exception"); - } catch (Exception e) { - // pass - } - } - - private void assertRecyclerTemplateInflated(SetupWizardRecyclerLayout layout) { - View recyclerView = layout.findViewById(R.id.suw_recycler_view); - assertTrue("@id/suw_recycler_view should be a RecyclerView", - recyclerView instanceof RecyclerView); - - assertNotNull("Header text view should not be null", - layout.findManagedViewById(R.id.suw_layout_title)); - assertNotNull("Decoration view should not be null", - layout.findManagedViewById(R.id.suw_layout_decor)); - } - - private Adapter createTestAdapter(final int itemCount) { - return new Adapter() { - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int position) { - return new ViewHolder(new View(parent.getContext())) {}; - } - - @Override - public void onBindViewHolder(ViewHolder viewHolder, int position) { - } - - @Override - public int getItemCount() { - return itemCount; - } - }; + assertRecyclerTemplateInflated(layout); + + layout.setDividerInsets(10, 15); + assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); + assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); + + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } + + @Test + public void testTemplateWithNoRecyclerView() { + try { + new SetupWizardRecyclerLayout(mContext, R.layout.suw_glif_template, R.id.suw_recycler_view); + fail("Creating SetupWizardRecyclerLayout with no recycler view should throw exception"); + } catch (Exception e) { + // pass } + } + + private void assertRecyclerTemplateInflated(SetupWizardRecyclerLayout layout) { + View recyclerView = layout.findViewById(R.id.suw_recycler_view); + assertTrue( + "@id/suw_recycler_view should be a RecyclerView", recyclerView instanceof RecyclerView); + + assertNotNull( + "Header text view should not be null", layout.findManagedViewById(R.id.suw_layout_title)); + assertNotNull( + "Decoration view should not be null", layout.findManagedViewById(R.id.suw_layout_decor)); + } + + private Adapter createTestAdapter(final int itemCount) { + return new Adapter() { + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int position) { + return new ViewHolder(new View(parent.getContext())) {}; + } + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int position) {} + + @Override + public int getItemCount() { + return itemCount; + } + }; + } } diff --git a/library/recyclerview/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java b/library/recyclerview/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java index 6fa4c54..dd262da 100644 --- a/library/recyclerview/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java +++ b/library/recyclerview/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java @@ -26,9 +26,7 @@ import static org.robolectric.RuntimeEnvironment.application; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.OnScrollListener; - import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,52 +35,51 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) @RunWith(SuwLibRobolectricTestRunner.class) public class RecyclerViewScrollHandlingDelegateTest { - @Mock - private RequireScrollMixin mRequireScrollMixin; - - private RecyclerView mRecyclerView; - private RecyclerViewScrollHandlingDelegate mDelegate; - private ArgumentCaptor mListenerCaptor; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mRecyclerView = spy(new RecyclerView(application)); - doReturn(20).when(mRecyclerView).computeVerticalScrollRange(); - doReturn(0).when(mRecyclerView).computeVerticalScrollExtent(); - doReturn(0).when(mRecyclerView).computeVerticalScrollOffset(); - mListenerCaptor = ArgumentCaptor.forClass(OnScrollListener.class); - doNothing().when(mRecyclerView).addOnScrollListener(mListenerCaptor.capture()); - - mDelegate = new RecyclerViewScrollHandlingDelegate(mRequireScrollMixin, mRecyclerView); - mRecyclerView.layout(0, 0, 50, 50); - } - - @Test - public void testRequireScroll() { - mDelegate.startListening(); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); - } - - @Test - public void testScrolledToBottom() { - mDelegate.startListening(); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); - - doReturn(20).when(mRecyclerView).computeVerticalScrollOffset(); - mListenerCaptor.getValue().onScrolled(mRecyclerView, 0, 20); - - verify(mRequireScrollMixin).notifyScrollabilityChange(false); - } - - @Test - public void testClickScrollButton() { - mDelegate.pageScrollDown(); - verify(mRecyclerView).smoothScrollBy(anyInt(), eq(50)); - } + @Mock private RequireScrollMixin mRequireScrollMixin; + + private RecyclerView mRecyclerView; + private RecyclerViewScrollHandlingDelegate mDelegate; + private ArgumentCaptor mListenerCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mRecyclerView = spy(new RecyclerView(application)); + doReturn(20).when(mRecyclerView).computeVerticalScrollRange(); + doReturn(0).when(mRecyclerView).computeVerticalScrollExtent(); + doReturn(0).when(mRecyclerView).computeVerticalScrollOffset(); + mListenerCaptor = ArgumentCaptor.forClass(OnScrollListener.class); + doNothing().when(mRecyclerView).addOnScrollListener(mListenerCaptor.capture()); + + mDelegate = new RecyclerViewScrollHandlingDelegate(mRequireScrollMixin, mRecyclerView); + mRecyclerView.layout(0, 0, 50, 50); + } + + @Test + public void testRequireScroll() { + mDelegate.startListening(); + verify(mRequireScrollMixin).notifyScrollabilityChange(true); + } + + @Test + public void testScrolledToBottom() { + mDelegate.startListening(); + verify(mRequireScrollMixin).notifyScrollabilityChange(true); + + doReturn(20).when(mRecyclerView).computeVerticalScrollOffset(); + mListenerCaptor.getValue().onScrolled(mRecyclerView, 0, 20); + + verify(mRequireScrollMixin).notifyScrollabilityChange(false); + } + + @Test + public void testClickScrollButton() { + mDelegate.pageScrollDown(); + verify(mRecyclerView).smoothScrollBy(anyInt(), eq(50)); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/TemplateLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/TemplateLayoutTest.java index ddce677..a5026ee 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/TemplateLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/TemplateLayoutTest.java @@ -23,16 +23,14 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.template.HeaderMixin; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,65 +39,64 @@ import org.junit.runner.RunWith; @SmallTest public class TemplateLayoutTest { - private Context mContext; + private Context mContext; - @Before - public void setUp() throws Exception { - mContext = InstrumentationRegistry.getContext(); - } + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + } - @Test - public void testAddView() { - TemplateLayout layout = new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content); - TextView tv = new TextView(mContext); - tv.setId(R.id.test_view_id); - layout.addView(tv); - View view = layout.findViewById(R.id.test_view_id); - assertSame("The view added should be the same text view", tv, view); - } + @Test + public void testAddView() { + TemplateLayout layout = + new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content); + TextView tv = new TextView(mContext); + tv.setId(R.id.test_view_id); + layout.addView(tv); + View view = layout.findViewById(R.id.test_view_id); + assertSame("The view added should be the same text view", tv, view); + } - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - TemplateLayout layout = - (TemplateLayout) inflater.inflate(R.layout.test_template_layout, null); - View content = layout.findViewById(R.id.test_content); - assertTrue("@id/test_content should be a TextView", content instanceof TextView); - } + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + TemplateLayout layout = (TemplateLayout) inflater.inflate(R.layout.test_template_layout, null); + View content = layout.findViewById(R.id.test_content); + assertTrue("@id/test_content should be a TextView", content instanceof TextView); + } - @Test - public void testTemplate() { - TemplateLayout layout = new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content); - View templateView = layout.findViewById(R.id.test_template_view); - assertNotNull("@id/test_template_view should exist in template", templateView); + @Test + public void testTemplate() { + TemplateLayout layout = + new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content); + View templateView = layout.findViewById(R.id.test_template_view); + assertNotNull("@id/test_template_view should exist in template", templateView); - TextView tv = new TextView(mContext); - tv.setId(R.id.test_view_id); - layout.addView(tv); + TextView tv = new TextView(mContext); + tv.setId(R.id.test_view_id); + layout.addView(tv); - templateView = layout.findViewById(R.id.test_template_view); - assertNotNull("@id/test_template_view should exist in template", templateView); - View contentView = layout.findViewById(R.id.test_view_id); - assertSame("The view added should be the same text view", tv, contentView); - } + templateView = layout.findViewById(R.id.test_template_view); + assertNotNull("@id/test_template_view should exist in template", templateView); + View contentView = layout.findViewById(R.id.test_view_id); + assertSame("The view added should be the same text view", tv, contentView); + } - @Test - public void testNoTemplate() { - try { - new TemplateLayout(mContext, 0, 0); - fail("Inflating TemplateLayout without template should throw exception"); - } catch (IllegalArgumentException e) { - // Expected IllegalArgumentException - } + @Test + public void testNoTemplate() { + try { + new TemplateLayout(mContext, 0, 0); + fail("Inflating TemplateLayout without template should throw exception"); + } catch (IllegalArgumentException e) { + // Expected IllegalArgumentException } + } - @Test - public void testGetMixin() { - TemplateLayout layout = new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content); - final HeaderMixin mixin = layout.getMixin(HeaderMixin.class); - assertNull("getMixin for a mixin that doesn't exist should return null", mixin); - } + @Test + public void testGetMixin() { + TemplateLayout layout = + new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content); + final HeaderMixin mixin = layout.getMixin(HeaderMixin.class); + assertNull("getMixin for a mixin that doesn't exist should return null", mixin); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/ButtonFooterMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/ButtonFooterMixinTest.java index 08f5958..971211b 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/ButtonFooterMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/ButtonFooterMixinTest.java @@ -24,9 +24,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.annotation.IdRes; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; @@ -34,12 +32,11 @@ import android.view.ViewStub; import android.widget.Button; import android.widget.FrameLayout; import android.widget.LinearLayout; - -import androidx.annotation.IdRes; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,125 +45,128 @@ import org.junit.runner.RunWith; @SmallTest public class ButtonFooterMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - - // The parent view to contain the view stub and views it inflates. - private FrameLayout mStubParent; - private ViewStub mFooterStub; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mFooterStub = new ViewStub(mContext, R.layout.suw_glif_footer_button_bar); - mStubParent = new FrameLayout(mContext); - mStubParent.addView(mFooterStub); - doReturn(mFooterStub).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_footer)); - } - - @Test - public void testAddButton() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button button = mixin.addButton("foobar", R.style.SuwGlifButton_Primary); - - assertNotNull(button); - @IdRes final int id = 12345; - button.setId(id); - assertNotNull(mStubParent.findViewById(id)); - - assertEquals("foobar", button.getText()); - - // Make sure the style is applied by checking the paddings - assertEquals(dp2Px(16), button.getPaddingLeft()); - assertEquals(dp2Px(16), button.getPaddingRight()); - } - - @Test - public void testAddButtonTextRes() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button button = mixin.addButton(R.string.suw_next_button_label, - R.style.SuwGlifButton_Primary); - - assertNotNull(button); - button.setTag("button"); - assertNotNull(mStubParent.findViewWithTag("button")); - - assertEquals("Next", button.getText()); - - // Make sure the style is applied by checking the paddings - assertEquals(dp2Px(16), button.getPaddingLeft()); - assertEquals(dp2Px(16), button.getPaddingRight()); - } - - @Test - public void testAddSpace() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - mixin.addButton("foo", R.style.SuwGlifButton_Secondary); - final View space = mixin.addSpace(); - mixin.addButton("bar", R.style.SuwGlifButton_Primary); - - space.setTag("space"); - assertNotNull(mStubParent.findViewWithTag("space")); - assertEquals("Space should have weight of 1", - 1f, ((LinearLayout.LayoutParams) space.getLayoutParams()).weight, 0.001); - } - - @Test - public void testRemoveButton() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); - final Button barButton = mixin.addButton("bar", R.style.SuwGlifButton_Secondary); - - fooButton.setTag("foo"); - barButton.setTag("bar"); - assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); - assertNotNull("Bar button should exist", mStubParent.findViewWithTag("bar")); - - mixin.removeButton(fooButton); - - assertNull("Foo button should be removed", mStubParent.findViewWithTag("foo")); - assertNotNull("Bar button should not be removed", mStubParent.findViewWithTag("bar")); - } - - @Test - public void testRemoveSpace() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); - final View space = mixin.addSpace(); - - fooButton.setTag("foo"); - space.setTag("space"); - assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); - assertNotNull("space should exist", mStubParent.findViewWithTag("space")); - - mixin.removeSpace(space); - - assertNotNull("Foo button should not be removed", mStubParent.findViewWithTag("foo")); - assertNull("Space should be removed", mStubParent.findViewWithTag("space")); - } - - @Test - public void testRemoveAllViews() { - ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); - final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); - final View space = mixin.addSpace(); - - fooButton.setTag("foo"); - space.setTag("space"); - assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); - assertNotNull("space should exist", mStubParent.findViewWithTag("space")); - - mixin.removeAllViews(); - - assertNull("Foo button should be removed", mStubParent.findViewWithTag("foo")); - assertNull("Space should be removed", mStubParent.findViewWithTag("space")); - } - - private int dp2Px(float dp) { - DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); - } + private Context mContext; + private TemplateLayout mTemplateLayout; + + // The parent view to contain the view stub and views it inflates. + private FrameLayout mStubParent; + private ViewStub mFooterStub; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mFooterStub = new ViewStub(mContext, R.layout.suw_glif_footer_button_bar); + mStubParent = new FrameLayout(mContext); + mStubParent.addView(mFooterStub); + doReturn(mFooterStub).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_footer)); + } + + @Test + public void testAddButton() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button button = mixin.addButton("foobar", R.style.SuwGlifButton_Primary); + + assertNotNull(button); + @IdRes final int id = 12345; + button.setId(id); + assertNotNull(mStubParent.findViewById(id)); + + assertEquals("foobar", button.getText()); + + // Make sure the style is applied by checking the paddings + assertEquals(dp2Px(16), button.getPaddingLeft()); + assertEquals(dp2Px(16), button.getPaddingRight()); + } + + @Test + public void testAddButtonTextRes() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button button = + mixin.addButton(R.string.suw_next_button_label, R.style.SuwGlifButton_Primary); + + assertNotNull(button); + button.setTag("button"); + assertNotNull(mStubParent.findViewWithTag("button")); + + assertEquals("Next", button.getText()); + + // Make sure the style is applied by checking the paddings + assertEquals(dp2Px(16), button.getPaddingLeft()); + assertEquals(dp2Px(16), button.getPaddingRight()); + } + + @Test + public void testAddSpace() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + mixin.addButton("foo", R.style.SuwGlifButton_Secondary); + final View space = mixin.addSpace(); + mixin.addButton("bar", R.style.SuwGlifButton_Primary); + + space.setTag("space"); + assertNotNull(mStubParent.findViewWithTag("space")); + assertEquals( + "Space should have weight of 1", + 1f, + ((LinearLayout.LayoutParams) space.getLayoutParams()).weight, + 0.001); + } + + @Test + public void testRemoveButton() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); + final Button barButton = mixin.addButton("bar", R.style.SuwGlifButton_Secondary); + + fooButton.setTag("foo"); + barButton.setTag("bar"); + assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); + assertNotNull("Bar button should exist", mStubParent.findViewWithTag("bar")); + + mixin.removeButton(fooButton); + + assertNull("Foo button should be removed", mStubParent.findViewWithTag("foo")); + assertNotNull("Bar button should not be removed", mStubParent.findViewWithTag("bar")); + } + + @Test + public void testRemoveSpace() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); + final View space = mixin.addSpace(); + + fooButton.setTag("foo"); + space.setTag("space"); + assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); + assertNotNull("space should exist", mStubParent.findViewWithTag("space")); + + mixin.removeSpace(space); + + assertNotNull("Foo button should not be removed", mStubParent.findViewWithTag("foo")); + assertNull("Space should be removed", mStubParent.findViewWithTag("space")); + } + + @Test + public void testRemoveAllViews() { + ButtonFooterMixin mixin = new ButtonFooterMixin(mTemplateLayout); + final Button fooButton = mixin.addButton("foo", R.style.SuwGlifButton_Secondary); + final View space = mixin.addSpace(); + + fooButton.setTag("foo"); + space.setTag("space"); + assertNotNull("Foo button should exist", mStubParent.findViewWithTag("foo")); + assertNotNull("space should exist", mStubParent.findViewWithTag("space")); + + mixin.removeAllViews(); + + assertNull("Foo button should be removed", mStubParent.findViewWithTag("foo")); + assertNull("Space should be removed", mStubParent.findViewWithTag("space")); + } + + private int dp2Px(float dp) { + DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/ColoredHeaderMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/ColoredHeaderMixinTest.java index 1c86af1..3ea8f6e 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/ColoredHeaderMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/ColoredHeaderMixinTest.java @@ -25,67 +25,62 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.XmlResourceParser; import android.graphics.Color; +import android.util.Xml; +import android.widget.TextView; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.Xml; -import android.widget.TextView; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; - @RunWith(AndroidJUnit4.class) @SmallTest public class ColoredHeaderMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - private TextView mHeaderTextView; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mHeaderTextView = new TextView(mContext); - doReturn(mHeaderTextView).when(mTemplateLayout) - .findManagedViewById(eq(R.id.suw_layout_title)); + private Context mContext; + private TemplateLayout mTemplateLayout; + private TextView mHeaderTextView; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mHeaderTextView = new TextView(mContext); + doReturn(mHeaderTextView).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_title)); + } + + @Test + public void testSetColor() { + ColoredHeaderMixin mixin = new ColoredHeaderMixin(mTemplateLayout, null, 0); + mixin.setColor(ColorStateList.valueOf(Color.MAGENTA)); + + assertEquals(ColorStateList.valueOf(Color.MAGENTA), mHeaderTextView.getTextColors()); + } + + @Test + public void testGetColor() { + ColoredHeaderMixin mixin = new ColoredHeaderMixin(mTemplateLayout, null, 0); + mHeaderTextView.setTextColor(ColorStateList.valueOf(Color.GREEN)); + + assertEquals(ColorStateList.valueOf(Color.GREEN), mixin.getColor()); + } + + @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. + @Test + public void testSetColorFromXml() throws IOException, XmlPullParserException { + final XmlResourceParser parser = mContext.getResources().getXml(R.layout.test_mixin_attributes); + while (!TemplateLayout.class.getName().equals(parser.getName())) { + parser.next(); } + new ColoredHeaderMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - @Test - public void testSetColor() { - ColoredHeaderMixin mixin = new ColoredHeaderMixin(mTemplateLayout, null, 0); - mixin.setColor(ColorStateList.valueOf(Color.MAGENTA)); - - assertEquals(ColorStateList.valueOf(Color.MAGENTA), mHeaderTextView.getTextColors()); - } - - @Test - public void testGetColor() { - ColoredHeaderMixin mixin = new ColoredHeaderMixin(mTemplateLayout, null, 0); - mHeaderTextView.setTextColor(ColorStateList.valueOf(Color.GREEN)); - - assertEquals(ColorStateList.valueOf(Color.GREEN), mixin.getColor()); - } - - @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. - @Test - public void testSetColorFromXml() throws IOException, XmlPullParserException { - final XmlResourceParser parser = - mContext.getResources().getXml(R.layout.test_mixin_attributes); - while (!TemplateLayout.class.getName().equals(parser.getName())) { - parser.next(); - } - new ColoredHeaderMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - - assertEquals(ColorStateList.valueOf(Color.RED), mHeaderTextView.getTextColors()); - } + assertEquals(ColorStateList.valueOf(Color.RED), mHeaderTextView.getTextColors()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/HeaderMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/HeaderMixinTest.java index a1b4b59..211f95f 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/HeaderMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/HeaderMixinTest.java @@ -25,82 +25,77 @@ import static org.mockito.Mockito.spy; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.XmlResourceParser; +import android.util.Xml; +import android.widget.TextView; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.Xml; -import android.widget.TextView; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; - @RunWith(AndroidJUnit4.class) @SmallTest public class HeaderMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - private TextView mHeaderTextView; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mHeaderTextView = new TextView(mContext); - doReturn(mHeaderTextView).when(mTemplateLayout) - .findManagedViewById(eq(R.id.suw_layout_title)); + private Context mContext; + private TemplateLayout mTemplateLayout; + private TextView mHeaderTextView; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mHeaderTextView = new TextView(mContext); + doReturn(mHeaderTextView).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_title)); + } + + @Test + public void testGetTextView() { + HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); + assertSame(mHeaderTextView, mixin.getTextView()); + } + + @Test + public void testSetTextId() { + HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); + mixin.setText(R.string.suw_next_button_label); + + assertEquals("Next", mHeaderTextView.getText()); + } + + @Test + public void testSetText() { + HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); + mixin.setText("Foobar"); + + assertEquals("Foobar", mHeaderTextView.getText()); + } + + @SuppressLint("SetTextI18n") // It's OK, this is a test + @Test + public void testGetText() { + mHeaderTextView.setText("Lorem ipsum"); + + HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); + assertEquals("Lorem ipsum", mixin.getText()); + } + + @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. + @Test + public void testSetTextFromXml() throws IOException, XmlPullParserException { + final XmlResourceParser parser = mContext.getResources().getXml(R.layout.test_mixin_attributes); + while (!TemplateLayout.class.getName().equals(parser.getName())) { + parser.next(); } + new HeaderMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - @Test - public void testGetTextView() { - HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); - assertSame(mHeaderTextView, mixin.getTextView()); - } - - @Test - public void testSetTextId() { - HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); - mixin.setText(R.string.suw_next_button_label); - - assertEquals("Next", mHeaderTextView.getText()); - } - - @Test - public void testSetText() { - HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); - mixin.setText("Foobar"); - - assertEquals("Foobar", mHeaderTextView.getText()); - } - - @SuppressLint("SetTextI18n") // It's OK, this is a test - @Test - public void testGetText() { - mHeaderTextView.setText("Lorem ipsum"); - - HeaderMixin mixin = new HeaderMixin(mTemplateLayout, null, 0); - assertEquals("Lorem ipsum", mixin.getText()); - } - - @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. - @Test - public void testSetTextFromXml() throws IOException, XmlPullParserException { - final XmlResourceParser parser = - mContext.getResources().getXml(R.layout.test_mixin_attributes); - while (!TemplateLayout.class.getName().equals(parser.getName())) { - parser.next(); - } - new HeaderMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - - assertEquals("lorem ipsum", mHeaderTextView.getText()); - } + assertEquals("lorem ipsum", mHeaderTextView.getText()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java index 5a36f4a..001fe33 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.template; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.mockito.Matchers.eq; @@ -30,115 +29,111 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.util.Xml; import android.view.View; import android.widget.ImageView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; - @RunWith(AndroidJUnit4.class) @SmallTest public class IconMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - private ImageView mIconView; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mIconView = new ImageView(mContext); - doReturn(mIconView).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_icon)); - } - - @Test - public void testGetIconView() { - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - assertSame(mIconView, mixin.getView()); - } - - @Test - public void testSetIcon() { - final ColorDrawable drawable = new ColorDrawable(Color.CYAN); - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mixin.setIcon(drawable); - - assertSame(drawable, mIconView.getDrawable()); - assertEquals(View.VISIBLE, mIconView.getVisibility()); - } - - @Test - public void setIcon_resourceId_shouldSetIcon() { - int icon = android.R.drawable.ic_menu_add; - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mixin.setIcon(icon); - - Drawable drawable = mIconView.getDrawable(); - assertThat(drawable).isInstanceOf(BitmapDrawable.class); - assertEquals(View.VISIBLE, mIconView.getVisibility()); - } - - @Test - public void setIcon_shouldSetVisibilityToGone_whenIconIsNull() { - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mixin.setIcon(null); - - assertEquals(View.GONE, mIconView.getVisibility()); - } - - @Test - public void testGetIcon() { - final ColorDrawable drawable = new ColorDrawable(Color.BLUE); - mIconView.setImageDrawable(drawable); - - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - assertSame(drawable, mixin.getIcon()); - } - - @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. - @Test - public void testSetIconFromXml() throws IOException, XmlPullParserException { - final XmlResourceParser parser = - mContext.getResources().getXml(R.layout.test_mixin_attributes); - while (!TemplateLayout.class.getName().equals(parser.getName())) { - parser.next(); - } - new IconMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); - - // Check that the bitmaps themselves are equal because BitmapDrawable does not implement - // equals() - final BitmapDrawable expected = (BitmapDrawable) mContext.getResources() - .getDrawable(android.R.drawable.ic_menu_add); - final BitmapDrawable actual = (BitmapDrawable) mIconView.getDrawable(); - assertEquals(expected.getBitmap(), actual.getBitmap()); - assertEquals(View.VISIBLE, mIconView.getVisibility()); - } - - @Test - public void setContentDescription_shouldSetContentDescriptionOnIconView() { - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mixin.setContentDescription("hello world"); - assertThat(mIconView.getContentDescription()).isEqualTo("hello world"); - } - - @Test - public void getContentDescription_shouldReturnContentDescriptionFromView() { - IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); - mIconView.setContentDescription("aloha"); - assertThat(mixin.getContentDescription()).isEqualTo("aloha"); + private Context mContext; + private TemplateLayout mTemplateLayout; + private ImageView mIconView; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mIconView = new ImageView(mContext); + doReturn(mIconView).when(mTemplateLayout).findManagedViewById(eq(R.id.suw_layout_icon)); + } + + @Test + public void testGetIconView() { + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + assertSame(mIconView, mixin.getView()); + } + + @Test + public void testSetIcon() { + final ColorDrawable drawable = new ColorDrawable(Color.CYAN); + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mixin.setIcon(drawable); + + assertSame(drawable, mIconView.getDrawable()); + assertEquals(View.VISIBLE, mIconView.getVisibility()); + } + + @Test + public void setIcon_resourceId_shouldSetIcon() { + int icon = android.R.drawable.ic_menu_add; + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mixin.setIcon(icon); + + Drawable drawable = mIconView.getDrawable(); + assertThat(drawable).isInstanceOf(BitmapDrawable.class); + assertEquals(View.VISIBLE, mIconView.getVisibility()); + } + + @Test + public void setIcon_shouldSetVisibilityToGone_whenIconIsNull() { + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mixin.setIcon(null); + + assertEquals(View.GONE, mIconView.getVisibility()); + } + + @Test + public void testGetIcon() { + final ColorDrawable drawable = new ColorDrawable(Color.BLUE); + mIconView.setImageDrawable(drawable); + + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + assertSame(drawable, mixin.getIcon()); + } + + @SuppressWarnings("ResourceType") // Needed to create attribute set from layout XML. + @Test + public void testSetIconFromXml() throws IOException, XmlPullParserException { + final XmlResourceParser parser = mContext.getResources().getXml(R.layout.test_mixin_attributes); + while (!TemplateLayout.class.getName().equals(parser.getName())) { + parser.next(); } + new IconMixin(mTemplateLayout, Xml.asAttributeSet(parser), 0); + + // Check that the bitmaps themselves are equal because BitmapDrawable does not implement + // equals() + final BitmapDrawable expected = + (BitmapDrawable) mContext.getResources().getDrawable(android.R.drawable.ic_menu_add); + final BitmapDrawable actual = (BitmapDrawable) mIconView.getDrawable(); + assertEquals(expected.getBitmap(), actual.getBitmap()); + assertEquals(View.VISIBLE, mIconView.getVisibility()); + } + + @Test + public void setContentDescription_shouldSetContentDescriptionOnIconView() { + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mixin.setContentDescription("hello world"); + assertThat(mIconView.getContentDescription()).isEqualTo("hello world"); + } + + @Test + public void getContentDescription_shouldReturnContentDescriptionFromView() { + IconMixin mixin = new IconMixin(mTemplateLayout, null, 0); + mIconView.setContentDescription("aloha"); + assertThat(mixin.getContentDescription()).isEqualTo("aloha"); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/ListMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/ListMixinTest.java index 30d68f1..e73e2bc 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/ListMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/ListMixinTest.java @@ -32,16 +32,14 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.widget.ListAdapter; import android.widget.ListView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,140 +50,138 @@ import org.mockito.MockitoAnnotations; @SmallTest public class ListMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; + private Context mContext; + private TemplateLayout mTemplateLayout; - private ListView mListView; + private ListView mListView; - @Mock - private ListAdapter mAdapter; + @Mock private ListAdapter mAdapter; - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getTargetContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); + mContext = InstrumentationRegistry.getTargetContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); - mListView = mock(ListView.class, delegatesTo(new ListView(mContext))); - doReturn(1).when(mAdapter).getViewTypeCount(); + mListView = mock(ListView.class, delegatesTo(new ListView(mContext))); + doReturn(1).when(mAdapter).getViewTypeCount(); - doReturn(mListView).when(mTemplateLayout) - .findManagedViewById(eq(android.R.id.list)); - doReturn(true).when(mTemplateLayout).isLayoutDirectionResolved(); - } + doReturn(mListView).when(mTemplateLayout).findManagedViewById(eq(android.R.id.list)); + doReturn(true).when(mTemplateLayout).isLayoutDirectionResolved(); + } - @Test - public void testGetListView() { - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - assertSame(mListView, mixin.getListView()); - } + @Test + public void testGetListView() { + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + assertSame(mListView, mixin.getListView()); + } - @Test - public void testGetAdapter() { - mListView.setAdapter(mAdapter); + @Test + public void testGetAdapter() { + mListView.setAdapter(mAdapter); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - assertSame(mAdapter, mixin.getAdapter()); - } + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + assertSame(mAdapter, mixin.getAdapter()); + } - @Test - public void testSetAdapter() { - assertNull(mListView.getAdapter()); + @Test + public void testSetAdapter() { + assertNull(mListView.getAdapter()); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setAdapter(mAdapter); + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setAdapter(mAdapter); - assertSame(mAdapter, mListView.getAdapter()); - } + assertSame(mAdapter, mListView.getAdapter()); + } - @Test - public void testDividerInsetLegacy() { - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setDividerInset(123); + @Test + public void testDividerInsetLegacy() { + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setDividerInset(123); - assertEquals(123, mixin.getDividerInset()); + assertEquals(123, mixin.getDividerInset()); - final Drawable divider = mListView.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mListView.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(123, 0, 0, 0), rect); - } + assertEquals(new Rect(123, 0, 0, 0), rect); + } - @Test - public void testDividerInsets() { - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setDividerInsets(123, 456); + @Test + public void testDividerInsets() { + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setDividerInsets(123, 456); - assertEquals(123, mixin.getDividerInsetStart()); - assertEquals(456, mixin.getDividerInsetEnd()); + assertEquals(123, mixin.getDividerInsetStart()); + assertEquals(456, mixin.getDividerInsetEnd()); - final Drawable divider = mListView.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mListView.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(123, 0, 456, 0), rect); - } + assertEquals(new Rect(123, 0, 456, 0), rect); + } - @Test - public void testDividerInsetLegacyRtl() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); + @Test + public void testDividerInsetLegacyRtl() { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setDividerInset(123); + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setDividerInset(123); - assertEquals(123, mixin.getDividerInset()); + assertEquals(123, mixin.getDividerInset()); - final Drawable divider = mListView.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mListView.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(0, 0, 123, 0), rect); - } - // else the test passes + assertEquals(new Rect(0, 0, 123, 0), rect); } + // else the test passes + } - @Test - public void testDividerInsetsRtl() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); + @Test + public void testDividerInsetsRtl() { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + doReturn(View.LAYOUT_DIRECTION_RTL).when(mTemplateLayout).getLayoutDirection(); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setDividerInsets(123, 456); + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + mixin.setDividerInsets(123, 456); - assertEquals(123, mixin.getDividerInsetStart()); - assertEquals(456, mixin.getDividerInsetEnd()); + assertEquals(123, mixin.getDividerInsetStart()); + assertEquals(456, mixin.getDividerInsetEnd()); - final Drawable divider = mListView.getDivider(); - InsetDrawable insetDrawable = (InsetDrawable) divider; - Rect rect = new Rect(); - insetDrawable.getPadding(rect); + final Drawable divider = mListView.getDivider(); + InsetDrawable insetDrawable = (InsetDrawable) divider; + Rect rect = new Rect(); + insetDrawable.getPadding(rect); - assertEquals(new Rect(456, 0, 123, 0), rect); - } - // else the test passes + assertEquals(new Rect(456, 0, 123, 0), rect); } + // else the test passes + } - @Test - public void testNoList() { - doReturn(null).when(mTemplateLayout).findManagedViewById(eq(android.R.id.list)); + @Test + public void testNoList() { + doReturn(null).when(mTemplateLayout).findManagedViewById(eq(android.R.id.list)); - ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); + ListMixin mixin = new ListMixin(mTemplateLayout, null, 0); - mixin.setAdapter(mAdapter); - mixin.setDividerInset(123); + mixin.setAdapter(mAdapter); + mixin.setDividerInset(123); - assertNull(mixin.getListView()); - assertNull(mixin.getAdapter()); - mixin.getDividerInset(); // Test that it doesn't crash. The return value is not significant. - assertNull(mixin.getDivider()); + assertNull(mixin.getListView()); + assertNull(mixin.getAdapter()); + mixin.getDividerInset(); // Test that it doesn't crash. The return value is not significant. + assertNull(mixin.getDivider()); - verifyNoMoreInteractions(mListView); - } + verifyNoMoreInteractions(mListView); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/NavigationBarMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/NavigationBarMixinTest.java index aca6084..1e2aff3 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/NavigationBarMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/NavigationBarMixinTest.java @@ -29,12 +29,10 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; import com.android.setupwizardlib.view.NavigationBar; import com.android.setupwizardlib.view.NavigationBar.NavigationBarListener; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,56 +41,57 @@ import org.junit.runner.RunWith; @SmallTest public class NavigationBarMixinTest { - private Context mContext; - private TemplateLayout mTemplateLayout; - private NavigationBar mNavigationBar; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getContext(); - mTemplateLayout = spy(new TemplateLayout(mContext, R.layout.test_template, - R.id.suw_layout_content)); - - mNavigationBar = new NavigationBar(mContext); - doReturn(mNavigationBar).when(mTemplateLayout) - .findManagedViewById(eq(R.id.suw_layout_navigation_bar)); - } - - @Test - public void testGetNavigationBar() { - NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); - assertSame(mNavigationBar, mixin.getNavigationBar()); - } - - @Test - public void testSetNextButtonText() { - NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); - mixin.setNextButtonText(R.string.suw_more_button_label); - assertEquals("More", mNavigationBar.getNextButton().getText()); - - mixin.setNextButtonText("Foobar"); - assertEquals("Foobar", mNavigationBar.getNextButton().getText()); - } - - @SuppressLint("SetTextI18n") // It's OK, this is just a test - @Test - public void testGetNextButtonText() { - mNavigationBar.getNextButton().setText("lorem ipsum"); - - NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); - assertSame("lorem ipsum", mixin.getNextButtonText()); - } - - @Test - public void testSetNavigationBarListener() { - final NavigationBarListener listener = mock(NavigationBarListener.class); - NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); - mixin.setNavigationBarListener(listener); - - mNavigationBar.getNextButton().performClick(); - verify(listener).onNavigateNext(); - - mNavigationBar.getBackButton().performClick(); - verify(listener).onNavigateBack(); - } + private Context mContext; + private TemplateLayout mTemplateLayout; + private NavigationBar mNavigationBar; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + mTemplateLayout = + spy(new TemplateLayout(mContext, R.layout.test_template, R.id.suw_layout_content)); + + mNavigationBar = new NavigationBar(mContext); + doReturn(mNavigationBar) + .when(mTemplateLayout) + .findManagedViewById(eq(R.id.suw_layout_navigation_bar)); + } + + @Test + public void testGetNavigationBar() { + NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); + assertSame(mNavigationBar, mixin.getNavigationBar()); + } + + @Test + public void testSetNextButtonText() { + NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); + mixin.setNextButtonText(R.string.suw_more_button_label); + assertEquals("More", mNavigationBar.getNextButton().getText()); + + mixin.setNextButtonText("Foobar"); + assertEquals("Foobar", mNavigationBar.getNextButton().getText()); + } + + @SuppressLint("SetTextI18n") // It's OK, this is just a test + @Test + public void testGetNextButtonText() { + mNavigationBar.getNextButton().setText("lorem ipsum"); + + NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); + assertSame("lorem ipsum", mixin.getNextButtonText()); + } + + @Test + public void testSetNavigationBarListener() { + final NavigationBarListener listener = mock(NavigationBarListener.class); + NavigationBarMixin mixin = new NavigationBarMixin(mTemplateLayout); + mixin.setNavigationBarListener(listener); + + mNavigationBar.getNextButton().performClick(); + verify(listener).onNavigateNext(); + + mNavigationBar.getBackButton().performClick(); + verify(listener).onNavigateBack(); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/ProgressBarMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/ProgressBarMixinTest.java index 5b2fb50..78ebe1e 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/ProgressBarMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/ProgressBarMixinTest.java @@ -29,16 +29,14 @@ import android.graphics.Canvas; import android.graphics.Color; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.ProgressBar; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,111 +45,107 @@ import org.junit.runner.RunWith; @SmallTest public class ProgressBarMixinTest { - private TemplateLayout mTemplateLayout; - - @Before - public void setUp() { - Context context = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - mTemplateLayout = new TemplateLayout( - context, - R.layout.test_progress_bar_template, R.id.suw_layout_content); - } - - @Test - public void testSetShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setShown(true); - - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertNotNull("Progress bar should be available after setting to shown", progressBar); - assertEquals(View.VISIBLE, progressBar.getVisibility()); - } - - @Test - public void testNotShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setShown(true); - mixin.setShown(false); - - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertNotEquals(View.VISIBLE, progressBar.getVisibility()); + private TemplateLayout mTemplateLayout; + + @Before + public void setUp() { + Context context = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + mTemplateLayout = + new TemplateLayout(context, R.layout.test_progress_bar_template, R.id.suw_layout_content); + } + + @Test + public void testSetShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setShown(true); + + ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertNotNull("Progress bar should be available after setting to shown", progressBar); + assertEquals(View.VISIBLE, progressBar.getVisibility()); + } + + @Test + public void testNotShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setShown(true); + mixin.setShown(false); + + ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertNotEquals(View.VISIBLE, progressBar.getVisibility()); + } + + @Test + public void testIsShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + + mixin.setShown(true); + assertTrue(mixin.isShown()); + + mixin.setShown(false); + assertFalse(mixin.isShown()); + } + + @Test + public void testPeekProgressBar() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + assertNull( + "PeekProgressBar should return null when stub not inflated yet", mixin.peekProgressBar()); + + mixin.setShown(true); + assertNotNull( + "PeekProgressBar should be available after setting to shown", mixin.peekProgressBar()); + } + + @Test + public void testSetColorBeforeSetShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setColor(ColorStateList.valueOf(Color.MAGENTA)); + + mixin.setShown(true); + + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = + (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertEquals(ColorStateList.valueOf(Color.MAGENTA), progressBar.getIndeterminateTintList()); + assertEquals( + ColorStateList.valueOf(Color.MAGENTA), progressBar.getProgressBackgroundTintList()); } - - @Test - public void testIsShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - - mixin.setShown(true); - assertTrue(mixin.isShown()); - - mixin.setShown(false); - assertFalse(mixin.isShown()); + // this method is a no-op on versions < lollipop. Just check that it doesn't crash. + } + + @Test + public void testSetColorAfterSetShown() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setShown(true); + + mixin.setColor(ColorStateList.valueOf(Color.YELLOW)); + + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = + (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertEquals(ColorStateList.valueOf(Color.YELLOW), progressBar.getIndeterminateTintList()); + assertEquals( + ColorStateList.valueOf(Color.YELLOW), progressBar.getProgressBackgroundTintList()); } - - @Test - public void testPeekProgressBar() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - assertNull("PeekProgressBar should return null when stub not inflated yet", - mixin.peekProgressBar()); - - mixin.setShown(true); - assertNotNull("PeekProgressBar should be available after setting to shown", - mixin.peekProgressBar()); - } - - @Test - public void testSetColorBeforeSetShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setColor(ColorStateList.valueOf(Color.MAGENTA)); - - mixin.setShown(true); - - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertEquals(ColorStateList.valueOf(Color.MAGENTA), - progressBar.getIndeterminateTintList()); - assertEquals(ColorStateList.valueOf(Color.MAGENTA), - progressBar.getProgressBackgroundTintList()); - } - // this method is a no-op on versions < lollipop. Just check that it doesn't crash. - } - - @Test - public void testSetColorAfterSetShown() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setShown(true); - - mixin.setColor(ColorStateList.valueOf(Color.YELLOW)); - - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertEquals(ColorStateList.valueOf(Color.YELLOW), - progressBar.getIndeterminateTintList()); - assertEquals(ColorStateList.valueOf(Color.YELLOW), - progressBar.getProgressBackgroundTintList()); - } - // this method is a no-op on versions < lollipop. Just check that it doesn't crash. - } - - @Test - public void testDeterminateProgressBarNullTint() { - ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); - mixin.setShown(true); - mixin.peekProgressBar().setIndeterminate(false); - - mixin.setColor(null); - - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) mTemplateLayout.findViewById( - R.id.suw_layout_progress); - assertEquals(null, progressBar.getProgressBackgroundTintList()); - progressBar.draw(new Canvas()); - } - // setColor is a no-op on versions < lollipop. Just check that it doesn't crash. + // this method is a no-op on versions < lollipop. Just check that it doesn't crash. + } + + @Test + public void testDeterminateProgressBarNullTint() { + ProgressBarMixin mixin = new ProgressBarMixin(mTemplateLayout); + mixin.setShown(true); + mixin.peekProgressBar().setIndeterminate(false); + + mixin.setColor(null); + + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = + (ProgressBar) mTemplateLayout.findViewById(R.id.suw_layout_progress); + assertEquals(null, progressBar.getProgressBackgroundTintList()); + progressBar.draw(new Canvas()); } + // setColor is a no-op on versions < lollipop. Just check that it doesn't crash. + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/template/TemplateLayoutMixinTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/template/TemplateLayoutMixinTest.java index 7cc934a..6adebc6 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/TemplateLayoutMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/TemplateLayoutMixinTest.java @@ -24,10 +24,8 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,35 +34,38 @@ import org.junit.runner.RunWith; @SmallTest public class TemplateLayoutMixinTest { - private TestTemplateLayout mLayout; + private TestTemplateLayout mLayout; - @Before - public void setUp() throws Exception { - mLayout = new TestTemplateLayout(InstrumentationRegistry.getContext()); - } + @Before + public void setUp() throws Exception { + mLayout = new TestTemplateLayout(InstrumentationRegistry.getContext()); + } - @Test - public void testGetMixin() { - final TestMixin mixin = mLayout.getMixin(TestMixin.class); - assertNotNull("TestMixin should not be null", mixin); - assertTrue("TestMixin should be an instance of TestMixinSubclass. " - + "Found " + mixin.getClass() + " instead.", - mixin instanceof TestMixinSubclass); + @Test + public void testGetMixin() { + final TestMixin mixin = mLayout.getMixin(TestMixin.class); + assertNotNull("TestMixin should not be null", mixin); + assertTrue( + "TestMixin should be an instance of TestMixinSubclass. " + + "Found " + + mixin.getClass() + + " instead.", + mixin instanceof TestMixinSubclass); - // Mixin must be retrieved using the interface it's registered with, not the concrete class, - // although they are often the same. - assertNull("TestMixinSubclass should be null", mLayout.getMixin(TestMixinSubclass.class)); - } + // Mixin must be retrieved using the interface it's registered with, not the concrete class, + // although they are often the same. + assertNull("TestMixinSubclass should be null", mLayout.getMixin(TestMixinSubclass.class)); + } - private static class TestTemplateLayout extends TemplateLayout { + private static class TestTemplateLayout extends TemplateLayout { - TestTemplateLayout(Context context) { - super(context, R.layout.test_template, R.id.suw_layout_content); - registerMixin(TestMixin.class, new TestMixinSubclass()); - } + TestTemplateLayout(Context context) { + super(context, R.layout.test_template, R.id.suw_layout_content); + registerMixin(TestMixin.class, new TestMixinSubclass()); } + } - private static class TestMixin implements Mixin {} + private static class TestMixin implements Mixin {} - private static class TestMixinSubclass extends TestMixin {} + private static class TestMixinSubclass extends TestMixin {} } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/BottomScrollViewTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/BottomScrollViewTest.java index 1a8eb21..4f9487c 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/BottomScrollViewTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/BottomScrollViewTest.java @@ -21,13 +21,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; +import android.view.View; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; - import com.android.setupwizardlib.view.BottomScrollView; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,101 +34,101 @@ import org.junit.runner.RunWith; @SmallTest public class BottomScrollViewTest { - private TestBottomScrollListener mListener; + private TestBottomScrollListener mListener; - @Before - public void setUp() throws Exception { - mListener = new TestBottomScrollListener(); - } + @Before + public void setUp() throws Exception { + mListener = new TestBottomScrollListener(); + } - @Test - public void testNoNeedScroll() { - createScrollView(20); - assertTrue("Scroll should not be required", mListener.mScrolledToBottom); - } + @Test + public void testNoNeedScroll() { + createScrollView(20); + assertTrue("Scroll should not be required", mListener.mScrolledToBottom); + } - @Test - public void testNeedScroll() { - createScrollView(110); - assertFalse("Scroll should be required", mListener.mScrolledToBottom); - } + @Test + public void testNeedScroll() { + createScrollView(110); + assertFalse("Scroll should be required", mListener.mScrolledToBottom); + } - @Test - public void testScrollToBottom() { - final BottomScrollView bottomScrollView = createScrollView(110); + @Test + public void testScrollToBottom() { + final BottomScrollView bottomScrollView = createScrollView(110); - assertFalse("Scroll should be required", mListener.mScrolledToBottom); + assertFalse("Scroll should be required", mListener.mScrolledToBottom); - bottomScrollView.scrollTo(0, 10); - assertTrue("Should already be scrolled to bottom", mListener.mScrolledToBottom); - } + bottomScrollView.scrollTo(0, 10); + assertTrue("Should already be scrolled to bottom", mListener.mScrolledToBottom); + } - @Test - public void testScrollThreshold() { - final BottomScrollView bottomScrollView = createScrollView(110); - assertEquals("Scroll threshold should be 10", 10, bottomScrollView.getScrollThreshold()); - } + @Test + public void testScrollThreshold() { + final BottomScrollView bottomScrollView = createScrollView(110); + assertEquals("Scroll threshold should be 10", 10, bottomScrollView.getScrollThreshold()); + } - private BottomScrollView createScrollView(final int childHeight) { - final Context context = InstrumentationRegistry.getContext(); - final BottomScrollView bottomScrollView = new TestBottomScrollView(context); - bottomScrollView.setBottomScrollListener(mListener); + private BottomScrollView createScrollView(final int childHeight) { + final Context context = InstrumentationRegistry.getContext(); + final BottomScrollView bottomScrollView = new TestBottomScrollView(context); + bottomScrollView.setBottomScrollListener(mListener); - final View child = new TestChildView(context, childHeight); + final View child = new TestChildView(context, childHeight); - child.measure(0, 0); // TestChildView's measured dimensions doesn't depend on the arguments - bottomScrollView.addView(child); - bottomScrollView.layout(0, 0, 100, 100); + child.measure(0, 0); // TestChildView's measured dimensions doesn't depend on the arguments + bottomScrollView.addView(child); + bottomScrollView.layout(0, 0, 100, 100); - return bottomScrollView; - } + return bottomScrollView; + } - private static class TestChildView extends View { + private static class TestChildView extends View { - private static final int WIDTH = 10; - private int mHeight; + private static final int WIDTH = 10; + private int mHeight; - TestChildView(Context context, int height) { - super(context); - mHeight = height; - } + TestChildView(Context context, int height) { + super(context); + mHeight = height; + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(WIDTH, mHeight); - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(WIDTH, mHeight); + } - public void setHeight(int height) { - mHeight = height; - } + public void setHeight(int height) { + mHeight = height; } + } - private static class TestBottomScrollView extends BottomScrollView { + private static class TestBottomScrollView extends BottomScrollView { - TestBottomScrollView(Context context) { - super(context); - } + TestBottomScrollView(Context context) { + super(context); + } - @Override - public boolean post(Runnable action) { - // Post all runnables synchronously so that tests can check the callbacks. - action.run(); - return true; - } + @Override + public boolean post(Runnable action) { + // Post all runnables synchronously so that tests can check the callbacks. + action.run(); + return true; } + } - private static class TestBottomScrollListener implements BottomScrollView.BottomScrollListener { + private static class TestBottomScrollListener implements BottomScrollView.BottomScrollListener { - boolean mScrolledToBottom = true; + boolean mScrolledToBottom = true; - @Override - public void onScrolledToBottom() { - mScrolledToBottom = true; - } + @Override + public void onScrolledToBottom() { + mScrolledToBottom = true; + } - @Override - public void onRequiresScroll() { - mScrolledToBottom = false; - } + @Override + public void onRequiresScroll() { + mScrolledToBottom = false; } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ButtonBarItemTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ButtonBarItemTest.java index 18c295e..aacffeb 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ButtonBarItemTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ButtonBarItemTest.java @@ -20,18 +20,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.items.ButtonBarItem; import com.android.setupwizardlib.items.ButtonItem; import com.android.setupwizardlib.items.Item; import com.android.setupwizardlib.items.ItemHierarchy; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,87 +38,92 @@ import org.junit.runner.RunWith; @SmallTest public class ButtonBarItemTest { - private ButtonItem mChild1; - private ButtonItem mChild2; - private ButtonItem mChild3; - - @Before - public void setUp() throws Exception { - mChild1 = new ButtonItem(); - mChild2 = new ButtonItem(); - mChild3 = new ButtonItem(); + private ButtonItem mChild1; + private ButtonItem mChild2; + private ButtonItem mChild3; + + @Before + public void setUp() throws Exception { + mChild1 = new ButtonItem(); + mChild2 = new ButtonItem(); + mChild3 = new ButtonItem(); + } + + @Test + public void testFindItemById() { + ButtonBarItem item = new ButtonBarItem(); + item.setId(888); + + mChild1.setId(123); + mChild2.setId(456); + mChild3.setId(789); + item.addChild(mChild1); + item.addChild(mChild2); + item.addChild(mChild3); + + assertEquals("Finding 123 should return child1", mChild1, item.findItemById(123)); + assertEquals("Finding 456 should return child2", mChild2, item.findItemById(456)); + assertEquals("Finding 789 should return child3", mChild3, item.findItemById(789)); + + assertEquals("Finding 888 should return ButtonBarItem itself", item, item.findItemById(888)); + + assertNull("Finding 999 should return null", item.findItemById(999)); + } + + @Test + public void testBindEmpty() { + ButtonBarItem item = new ButtonBarItem(); + final ViewGroup layout = createLayout(); + item.onBindView(layout); + + assertEquals( + "Binding empty ButtonBar should not create any children", 0, layout.getChildCount()); + } + + @Test + public void testBind() { + ButtonBarItem item = new ButtonBarItem(); + + item.addChild(mChild1); + mChild1.setText("child1"); + item.addChild(mChild2); + mChild2.setText("child2"); + item.addChild(mChild3); + mChild3.setText("child3"); + + final ViewGroup layout = createLayout(); + item.onBindView(layout); + + assertEquals("Binding ButtonBar should create 3 children", 3, layout.getChildCount()); + assertEquals( + "First button should have text \"child1\"", + "child1", + ((Button) layout.getChildAt(0)).getText()); + assertEquals( + "Second button should have text \"child2\"", + "child2", + ((Button) layout.getChildAt(1)).getText()); + assertEquals( + "Third button should have text \"child3\"", + "child3", + ((Button) layout.getChildAt(2)).getText()); + } + + @Test + public void testAddInvalidChild() { + ButtonBarItem item = new ButtonBarItem(); + + ItemHierarchy invalidChild = new Item(); + + try { + item.addChild(invalidChild); + fail("Adding non ButtonItem to ButtonBarItem should throw exception"); + } catch (UnsupportedOperationException e) { + // pass } + } - @Test - public void testFindItemById() { - ButtonBarItem item = new ButtonBarItem(); - item.setId(888); - - mChild1.setId(123); - mChild2.setId(456); - mChild3.setId(789); - item.addChild(mChild1); - item.addChild(mChild2); - item.addChild(mChild3); - - assertEquals("Finding 123 should return child1", mChild1, item.findItemById(123)); - assertEquals("Finding 456 should return child2", mChild2, item.findItemById(456)); - assertEquals("Finding 789 should return child3", mChild3, item.findItemById(789)); - - assertEquals("Finding 888 should return ButtonBarItem itself", item, - item.findItemById(888)); - - assertNull("Finding 999 should return null", item.findItemById(999)); - } - - @Test - public void testBindEmpty() { - ButtonBarItem item = new ButtonBarItem(); - final ViewGroup layout = createLayout(); - item.onBindView(layout); - - assertEquals("Binding empty ButtonBar should not create any children", 0, - layout.getChildCount()); - } - - @Test - public void testBind() { - ButtonBarItem item = new ButtonBarItem(); - - item.addChild(mChild1); - mChild1.setText("child1"); - item.addChild(mChild2); - mChild2.setText("child2"); - item.addChild(mChild3); - mChild3.setText("child3"); - - final ViewGroup layout = createLayout(); - item.onBindView(layout); - - assertEquals("Binding ButtonBar should create 3 children", 3, layout.getChildCount()); - assertEquals("First button should have text \"child1\"", "child1", - ((Button) layout.getChildAt(0)).getText()); - assertEquals("Second button should have text \"child2\"", "child2", - ((Button) layout.getChildAt(1)).getText()); - assertEquals("Third button should have text \"child3\"", "child3", - ((Button) layout.getChildAt(2)).getText()); - } - - @Test - public void testAddInvalidChild() { - ButtonBarItem item = new ButtonBarItem(); - - ItemHierarchy invalidChild = new Item(); - - try { - item.addChild(invalidChild); - fail("Adding non ButtonItem to ButtonBarItem should throw exception"); - } catch (UnsupportedOperationException e) { - // pass - } - } - - private ViewGroup createLayout() { - return new LinearLayout(InstrumentationRegistry.getContext()); - } + private ViewGroup createLayout() { + return new LinearLayout(InstrumentationRegistry.getContext()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/DrawableLayoutDirectionHelperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/DrawableLayoutDirectionHelperTest.java index 95245b0..1445660 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/DrawableLayoutDirectionHelperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/DrawableLayoutDirectionHelperTest.java @@ -28,117 +28,124 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; +import android.view.View; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; - import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper; - +import java.util.Locale; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Locale; - @RunWith(AndroidJUnit4.class) @SmallTest public class DrawableLayoutDirectionHelperTest { - @Test - public void testCreateRelativeInsetDrawableLtr() { - final Drawable drawable = new ColorDrawable(Color.RED); - @SuppressLint("InlinedApi") // Testing with inlined constant is OK here - final InsetDrawable insetDrawable = - DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable, - 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, - View.LAYOUT_DIRECTION_LTR); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - assertSame("Drawable from getDrawable() should be same as passed in", drawable, - insetDrawable.getDrawable()); - } - Rect outRect = new Rect(); - insetDrawable.getPadding(outRect); - assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), - outRect); + @Test + public void testCreateRelativeInsetDrawableLtr() { + final Drawable drawable = new ColorDrawable(Color.RED); + @SuppressLint("InlinedApi") // Testing with inlined constant is OK here + final InsetDrawable insetDrawable = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + drawable, + 1 /* start */, + 2 /* top */, + 3 /* end */, + 4 /* bottom */, + View.LAYOUT_DIRECTION_LTR); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertSame( + "Drawable from getDrawable() should be same as passed in", + drawable, + insetDrawable.getDrawable()); } + Rect outRect = new Rect(); + insetDrawable.getPadding(outRect); + assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), outRect); + } - @Test - public void testCreateRelativeInsetDrawableRtl() { - final Drawable drawable = new ColorDrawable(Color.RED); - @SuppressLint("InlinedApi") // Testing with inlined constant is OK here - final InsetDrawable insetDrawable = - DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable, - 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, - View.LAYOUT_DIRECTION_RTL); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - assertSame("Drawable from getDrawable() should be same as passed in", drawable, - insetDrawable.getDrawable()); - } - Rect outRect = new Rect(); - insetDrawable.getPadding(outRect); - assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), - outRect); + @Test + public void testCreateRelativeInsetDrawableRtl() { + final Drawable drawable = new ColorDrawable(Color.RED); + @SuppressLint("InlinedApi") // Testing with inlined constant is OK here + final InsetDrawable insetDrawable = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + drawable, + 1 /* start */, + 2 /* top */, + 3 /* end */, + 4 /* bottom */, + View.LAYOUT_DIRECTION_RTL); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertSame( + "Drawable from getDrawable() should be same as passed in", + drawable, + insetDrawable.getDrawable()); } + Rect outRect = new Rect(); + insetDrawable.getPadding(outRect); + assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), outRect); + } - @Test - public void testCreateRelativeInsetDrawableViewRtl() { - final Drawable drawable = new ColorDrawable(Color.RED); - final View view = new ForceRtlView(InstrumentationRegistry.getContext()); - final InsetDrawable insetDrawable = - DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable, - 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, view); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - assertSame("Drawable from getDrawable() should be same as passed in", drawable, - insetDrawable.getDrawable()); - } - Rect outRect = new Rect(); - insetDrawable.getPadding(outRect); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), - outRect); - } else { - assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), - outRect); - } + @Test + public void testCreateRelativeInsetDrawableViewRtl() { + final Drawable drawable = new ColorDrawable(Color.RED); + final View view = new ForceRtlView(InstrumentationRegistry.getContext()); + final InsetDrawable insetDrawable = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + drawable, 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, view); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertSame( + "Drawable from getDrawable() should be same as passed in", + drawable, + insetDrawable.getDrawable()); + } + Rect outRect = new Rect(); + insetDrawable.getPadding(outRect); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), outRect); + } else { + assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), outRect); } + } - @Test - public void testCreateRelativeInsetDrawableContextRtl() { - Context context = InstrumentationRegistry.getContext(); - final Drawable drawable = new ColorDrawable(Color.RED); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - final Configuration config = new Configuration(); - config.setLayoutDirection(new Locale("fa", "IR")); - context = context.createConfigurationContext(config); - } - final InsetDrawable insetDrawable = - DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable, - 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - assertSame("Drawable from getDrawable() should be same as passed in", drawable, - insetDrawable.getDrawable()); - } - Rect outRect = new Rect(); - insetDrawable.getPadding(outRect); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), - outRect); - } else { - assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), - outRect); - } + @Test + public void testCreateRelativeInsetDrawableContextRtl() { + Context context = InstrumentationRegistry.getContext(); + final Drawable drawable = new ColorDrawable(Color.RED); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + final Configuration config = new Configuration(); + config.setLayoutDirection(new Locale("fa", "IR")); + context = context.createConfigurationContext(config); + } + final InsetDrawable insetDrawable = + DrawableLayoutDirectionHelper.createRelativeInsetDrawable( + drawable, 1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, context); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertSame( + "Drawable from getDrawable() should be same as passed in", + drawable, + insetDrawable.getDrawable()); } + Rect outRect = new Rect(); + insetDrawable.getPadding(outRect); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4), outRect); + } else { + assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4), outRect); + } + } - private static class ForceRtlView extends View { + private static class ForceRtlView extends View { - ForceRtlView(Context context) { - super(context); - } + ForceRtlView(Context context) { + super(context); + } - @Override - @SuppressLint("InlinedApi") // Testing with inlined constant is OK here - public int getLayoutDirection() { - return View.LAYOUT_DIRECTION_RTL; - } + @Override + @SuppressLint("InlinedApi") // Testing with inlined constant is OK here + public int getLayoutDirection() { + return View.LAYOUT_DIRECTION_RTL; } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifLayoutTest.java index e12b31d..f8aae5a 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifLayoutTest.java @@ -26,9 +26,6 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; @@ -36,9 +33,10 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.GlifLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,94 +45,99 @@ import org.junit.runner.RunWith; @SmallTest public class GlifLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + GlifLayout layout = (GlifLayout) inflater.inflate(R.layout.test_glif_layout, null); + assertDefaultTemplateInflated(layout); + View content = layout.findViewById(R.id.test_content); + assertTrue("@id/test_content should be a TextView", content instanceof TextView); + } + + @Test + public void testPrimaryColorFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + GlifLayout layout = + (GlifLayout) inflater.inflate(R.layout.test_glif_layout_primary_color, null); + assertDefaultTemplateInflated(layout); + + assertEquals(ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); + } + + @Test + public void testSetProgressBarShownInvalid() { + GlifLayout layout = new GlifLayout(mContext, R.layout.test_template); + layout.setProgressBarShown(true); + // This is a no-op because there is no progress bar stub + } + + @Test + public void testGlifTheme() { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + final GlifLayout glifLayout = new GlifLayout(mContext); + + if (VERSION.SDK_INT >= VERSION_CODES.M) { + // Scroll indicators are only available on versions >= M + assertEquals(View.SCROLL_INDICATOR_BOTTOM, glifLayout.getScrollView().getScrollIndicators()); } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - GlifLayout layout = (GlifLayout) inflater.inflate(R.layout.test_glif_layout, null); - assertDefaultTemplateInflated(layout); - View content = layout.findViewById(R.id.test_content); - assertTrue("@id/test_content should be a TextView", content instanceof TextView); - } - - @Test - public void testPrimaryColorFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - GlifLayout layout = - (GlifLayout) inflater.inflate(R.layout.test_glif_layout_primary_color, null); - assertDefaultTemplateInflated(layout); - - assertEquals(ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); + } + + @Test + public void testGlifV2Theme() { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlifV2_Light); + final GlifLayout glifLayout = new GlifLayout(mContext); + final TextView titleView = (TextView) glifLayout.findManagedViewById(R.id.suw_layout_title); + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + assertEquals(View.TEXT_ALIGNMENT_GRAVITY, titleView.getTextAlignment()); } - - @Test - public void testSetProgressBarShownInvalid() { - GlifLayout layout = new GlifLayout(mContext, R.layout.test_template); - layout.setProgressBarShown(true); - // This is a no-op because there is no progress bar stub - } - - @Test - public void testGlifTheme() { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); - final GlifLayout glifLayout = new GlifLayout(mContext); - - if (VERSION.SDK_INT >= VERSION_CODES.M) { - // Scroll indicators are only available on versions >= M - assertEquals(View.SCROLL_INDICATOR_BOTTOM, - glifLayout.getScrollView().getScrollIndicators()); - } + assertEquals( + "Title text should be center aligned on GLIF v2 theme", + Gravity.CENTER_HORIZONTAL, + titleView.getGravity() & Gravity.CENTER_HORIZONTAL); + + if (VERSION.SDK_INT >= VERSION_CODES.N) { + // LinearLayout.getGravity is only available on versions >= N + final View iconView = glifLayout.findManagedViewById(R.id.suw_layout_icon); + final LinearLayout parent = (LinearLayout) iconView.getParent(); + assertEquals( + "Icon should be center aligned on GLIF v2 theme", + Gravity.CENTER_HORIZONTAL, + parent.getGravity() & Gravity.CENTER_HORIZONTAL); } - @Test - public void testGlifV2Theme() { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlifV2_Light); - final GlifLayout glifLayout = new GlifLayout(mContext); - final TextView titleView = (TextView) glifLayout.findManagedViewById(R.id.suw_layout_title); - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - assertEquals(View.TEXT_ALIGNMENT_GRAVITY, titleView.getTextAlignment()); - } - assertEquals("Title text should be center aligned on GLIF v2 theme", - Gravity.CENTER_HORIZONTAL, titleView.getGravity() & Gravity.CENTER_HORIZONTAL); - - if (VERSION.SDK_INT >= VERSION_CODES.N) { - // LinearLayout.getGravity is only available on versions >= N - final View iconView = glifLayout.findManagedViewById(R.id.suw_layout_icon); - final LinearLayout parent = (LinearLayout) iconView.getParent(); - assertEquals("Icon should be center aligned on GLIF v2 theme", - Gravity.CENTER_HORIZONTAL, parent.getGravity() & Gravity.CENTER_HORIZONTAL); - } - - assertEquals("Status bar color should be white in GLIF v2 theme", - "ffffffff", - Integer.toHexString(glifLayout.getBackgroundBaseColor().getDefaultColor())); - assertFalse("GLIF v2 theme shuold not have patterned background", - glifLayout.isBackgroundPatterned()); - - if (VERSION.SDK_INT >= VERSION_CODES.M) { - // Scroll indicators are only available on versions >= M - assertEquals(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM, - glifLayout.getScrollView().getScrollIndicators()); - } + assertEquals( + "Status bar color should be white in GLIF v2 theme", + "ffffffff", + Integer.toHexString(glifLayout.getBackgroundBaseColor().getDefaultColor())); + assertFalse( + "GLIF v2 theme shuold not have patterned background", glifLayout.isBackgroundPatterned()); + + if (VERSION.SDK_INT >= VERSION_CODES.M) { + // Scroll indicators are only available on versions >= M + assertEquals( + View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM, + glifLayout.getScrollView().getScrollIndicators()); } + } - private void assertDefaultTemplateInflated(GlifLayout layout) { - View title = layout.findViewById(R.id.suw_layout_title); - assertNotNull("@id/suw_layout_title should not be null", title); + private void assertDefaultTemplateInflated(GlifLayout layout) { + View title = layout.findViewById(R.id.suw_layout_title); + assertNotNull("@id/suw_layout_title should not be null", title); - View icon = layout.findViewById(R.id.suw_layout_icon); - assertNotNull("@id/suw_layout_icon should not be null", icon); + View icon = layout.findViewById(R.id.suw_layout_icon); + assertNotNull("@id/suw_layout_icon should not be null", icon); - View scrollView = layout.findViewById(R.id.suw_scroll_view); - assertTrue("@id/suw_scroll_view should be a ScrollView", scrollView instanceof ScrollView); - } + View scrollView = layout.findViewById(R.id.suw_scroll_view); + assertTrue("@id/suw_scroll_view should be a ScrollView", scrollView instanceof ScrollView); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifListLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifListLayoutTest.java index c2e932c..0665bfe 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifListLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifListLayoutTest.java @@ -26,9 +26,6 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -36,9 +33,10 @@ import android.widget.ArrayAdapter; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.GlifListLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,102 +45,100 @@ import org.junit.runner.RunWith; @SmallTest public class GlifListLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeGlif_Light); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.SuwThemeGlif_Light); + } + + @Test + public void testDefaultTemplate() { + GlifListLayout layout = new GlifListLayout(mContext); + assertListTemplateInflated(layout); + } + + @Test + public void testAddView() { + GlifListLayout layout = new GlifListLayout(mContext); + TextView tv = new TextView(mContext); + try { + layout.addView(tv); + fail("Adding view to ListLayout should throw"); + } catch (UnsupportedOperationException e) { + // Expected exception } - - @Test - public void testDefaultTemplate() { - GlifListLayout layout = new GlifListLayout(mContext); - assertListTemplateInflated(layout); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + GlifListLayout layout = (GlifListLayout) inflater.inflate(R.layout.test_glif_list_layout, null); + assertListTemplateInflated(layout); + } + + @Test + public void testGetListView() { + GlifListLayout layout = new GlifListLayout(mContext); + assertListTemplateInflated(layout); + assertNotNull("getListView should not be null", layout.getListView()); + } + + @Test + public void testAdapter() { + GlifListLayout layout = new GlifListLayout(mContext); + assertListTemplateInflated(layout); + + final ArrayAdapter adapter = + new ArrayAdapter<>(mContext, android.R.layout.simple_list_item_1); + adapter.add("Abracadabra"); + layout.setAdapter(adapter); + + final ListAdapter gotAdapter = layout.getAdapter(); + // Note: the wrapped adapter should be returned directly, not the HeaderViewListAdapter. + assertSame("Adapter got from GlifListLayout should be same as set", adapter, gotAdapter); + } + + @Test + public void testDividerInsetLegacy() { + GlifListLayout layout = new GlifListLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertListTemplateInflated(layout); - @Test - public void testAddView() { - GlifListLayout layout = new GlifListLayout(mContext); - TextView tv = new TextView(mContext); - try { - layout.addView(tv); - fail("Adding view to ListLayout should throw"); - } catch (UnsupportedOperationException e) { - // Expected exception - } - } + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - GlifListLayout layout = (GlifListLayout) - inflater.inflate(R.layout.test_glif_list_layout, null); - assertListTemplateInflated(layout); - } + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - @Test - public void testGetListView() { - GlifListLayout layout = new GlifListLayout(mContext); - assertListTemplateInflated(layout); - assertNotNull("getListView should not be null", layout.getListView()); + @Test + public void testDividerInsets() { + GlifListLayout layout = new GlifListLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertListTemplateInflated(layout); - @Test - public void testAdapter() { - GlifListLayout layout = new GlifListLayout(mContext); - assertListTemplateInflated(layout); + layout.setDividerInsets(10, 15); + assertEquals("Divider inset should be 10", 10, layout.getDividerInsetStart()); + assertEquals("Divider inset should be 15", 15, layout.getDividerInsetEnd()); - final ArrayAdapter adapter = - new ArrayAdapter<>(mContext, android.R.layout.simple_list_item_1); - adapter.add("Abracadabra"); - layout.setAdapter(adapter); + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - final ListAdapter gotAdapter = layout.getAdapter(); - // Note: the wrapped adapter should be returned directly, not the HeaderViewListAdapter. - assertSame("Adapter got from GlifListLayout should be same as set", - adapter, gotAdapter); - } - - @Test - public void testDividerInsetLegacy() { - GlifListLayout layout = new GlifListLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertListTemplateInflated(layout); - - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testDividerInsets() { - GlifListLayout layout = new GlifListLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertListTemplateInflated(layout); + private void assertListTemplateInflated(GlifListLayout layout) { + View title = layout.findViewById(R.id.suw_layout_title); + assertNotNull("@id/suw_layout_title should not be null", title); - layout.setDividerInsets(10, 15); - assertEquals("Divider inset should be 10", 10, layout.getDividerInsetStart()); - assertEquals("Divider inset should be 15", 15, layout.getDividerInsetEnd()); + View icon = layout.findViewById(R.id.suw_layout_icon); + assertNotNull("@id/suw_layout_icon should not be null", icon); - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - private void assertListTemplateInflated(GlifListLayout layout) { - View title = layout.findViewById(R.id.suw_layout_title); - assertNotNull("@id/suw_layout_title should not be null", title); - - View icon = layout.findViewById(R.id.suw_layout_icon); - assertNotNull("@id/suw_layout_icon should not be null", icon); - - View listView = layout.findViewById(android.R.id.list); - assertTrue("@android:id/list should be a ListView", listView instanceof ListView); - } + View listView = layout.findViewById(android.R.id.list); + assertTrue("@android:id/list should be a ListView", listView instanceof ListView); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java index 37ac41a..1783d4e 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java @@ -25,14 +25,11 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.os.Debug; +import android.util.Log; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.Log; - import com.android.setupwizardlib.GlifPatternDrawable; - import junit.framework.AssertionFailedError; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,141 +38,144 @@ import org.junit.runner.RunWith; @SmallTest public class GlifPatternDrawableTest { - private static final String TAG = "GlifPatternDrawableTest"; + private static final String TAG = "GlifPatternDrawableTest"; - @Before - public void setUp() throws Exception { - GlifPatternDrawable.invalidatePattern(); - } + @Before + public void setUp() throws Exception { + GlifPatternDrawable.invalidatePattern(); + } - @Test - public void testDraw() { - final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); + @Test + public void testDraw() { + final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 768); - drawable.draw(canvas); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 768); + drawable.draw(canvas); - assertSameColor("Top left pixel should be #e61a1a", 0xffe61a1a, bitmap.getPixel(0, 0)); - assertSameColor("Center pixel should be #d90d0d", 0xffd90d0d, bitmap.getPixel(683, 384)); - assertSameColor("Bottom right pixel should be #d40808", 0xffd40808, - bitmap.getPixel(1365, 767)); - } + assertSameColor("Top left pixel should be #e61a1a", 0xffe61a1a, bitmap.getPixel(0, 0)); + assertSameColor("Center pixel should be #d90d0d", 0xffd90d0d, bitmap.getPixel(683, 384)); + assertSameColor("Bottom right pixel should be #d40808", 0xffd40808, bitmap.getPixel(1365, 767)); + } - @Test - public void testDrawTwice() { - // Test that the second time the drawable is drawn is also correct, to make sure caching is - // done correctly. + @Test + public void testDrawTwice() { + // Test that the second time the drawable is drawn is also correct, to make sure caching is + // done correctly. - final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); + final Bitmap bitmap = Bitmap.createBitmap(1366, 768, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 768); - drawable.draw(canvas); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 768); + drawable.draw(canvas); - Paint paint = new Paint(); - paint.setColor(Color.WHITE); - canvas.drawRect(0, 0, 1366, 768, paint); // Erase the entire canvas + Paint paint = new Paint(); + paint.setColor(Color.WHITE); + canvas.drawRect(0, 0, 1366, 768, paint); // Erase the entire canvas - drawable.draw(canvas); + drawable.draw(canvas); - assertSameColor("Top left pixel should be #e61a1a", 0xffe61a1a, bitmap.getPixel(0, 0)); - assertSameColor("Center pixel should be #d90d0d", 0xffd90d0d, bitmap.getPixel(683, 384)); - assertSameColor("Bottom right pixel should be #d40808", 0xffd40808, - bitmap.getPixel(1365, 767)); - } + assertSameColor("Top left pixel should be #e61a1a", 0xffe61a1a, bitmap.getPixel(0, 0)); + assertSameColor("Center pixel should be #d90d0d", 0xffd90d0d, bitmap.getPixel(683, 384)); + assertSameColor("Bottom right pixel should be #d40808", 0xffd40808, bitmap.getPixel(1365, 767)); + } - @Test - public void testScaleToCanvasSquare() { - final Canvas canvas = new Canvas(); - Matrix expected = new Matrix(canvas.getMatrix()); + @Test + public void testScaleToCanvasSquare() { + final Canvas canvas = new Canvas(); + Matrix expected = new Matrix(canvas.getMatrix()); - Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); + Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 683, 384); // half each side of the view box - drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 683, 384); // half each side of the view box + drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); - expected.postScale(0.5f, 0.5f); + expected.postScale(0.5f, 0.5f); - assertEquals("Matrices should match", expected, canvas.getMatrix()); - } + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } - @Test - public void testScaleToCanvasTall() { - final Canvas canvas = new Canvas(); - final Matrix expected = new Matrix(canvas.getMatrix()); + @Test + public void testScaleToCanvasTall() { + final Canvas canvas = new Canvas(); + final Matrix expected = new Matrix(canvas.getMatrix()); - Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); + Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 683, 768); // half the width only - drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 683, 768); // half the width only + drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); - expected.postScale(1f, 1f); - expected.postTranslate(-99.718f, 0f); + expected.postScale(1f, 1f); + expected.postTranslate(-99.718f, 0f); - assertEquals("Matrices should match", expected, canvas.getMatrix()); - } + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } - @Test - public void testScaleToCanvasWide() { - final Canvas canvas = new Canvas(); - final Matrix expected = new Matrix(canvas.getMatrix()); + @Test + public void testScaleToCanvasWide() { + final Canvas canvas = new Canvas(); + final Matrix expected = new Matrix(canvas.getMatrix()); - Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); + Bitmap mockBitmapCache = Bitmap.createBitmap(1366, 768, Bitmap.Config.ALPHA_8); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 384); // half the height only - drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 384); // half the height only + drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); - expected.postScale(1f, 1f); - expected.postTranslate(0f, -87.552f); + expected.postScale(1f, 1f); + expected.postTranslate(0f, -87.552f); - assertEquals("Matrices should match", expected, canvas.getMatrix()); - } + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } - @Test - public void testScaleToCanvasMaxSize() { - final Canvas canvas = new Canvas(); - final Matrix expected = new Matrix(canvas.getMatrix()); + @Test + public void testScaleToCanvasMaxSize() { + final Canvas canvas = new Canvas(); + final Matrix expected = new Matrix(canvas.getMatrix()); - Bitmap mockBitmapCache = Bitmap.createBitmap(2049, 1152, Bitmap.Config.ALPHA_8); + Bitmap mockBitmapCache = Bitmap.createBitmap(2049, 1152, Bitmap.Config.ALPHA_8); - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 768); // original viewbox size - drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 768); // original viewbox size + drawable.scaleCanvasToBounds(canvas, mockBitmapCache, drawable.getBounds()); - expected.postScale(1 / 1.5f, 1 / 1.5f); - expected.postTranslate(0f, 0f); + expected.postScale(1 / 1.5f, 1 / 1.5f); + expected.postTranslate(0f, 0f); - assertEquals("Matrices should match", expected, canvas.getMatrix()); - } + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } - @Test - public void testMemoryAllocation() { - Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo(); - Debug.getMemoryInfo(memoryInfo); - final long memoryBefore = memoryInfo.getTotalPss(); // Get memory usage in KB + @Test + public void testMemoryAllocation() { + Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo(); + Debug.getMemoryInfo(memoryInfo); + final long memoryBefore = memoryInfo.getTotalPss(); // Get memory usage in KB - final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); - drawable.setBounds(0, 0, 1366, 768); - drawable.createBitmapCache(2049, 1152); + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 768); + drawable.createBitmapCache(2049, 1152); - Debug.getMemoryInfo(memoryInfo); - final long memoryAfter = memoryInfo.getTotalPss(); - Log.i(TAG, "Memory allocated for bitmap cache: " + (memoryAfter - memoryBefore)); - assertTrue("Memory allocation should not exceed 5MB", memoryAfter < memoryBefore + 5000); - } + Debug.getMemoryInfo(memoryInfo); + final long memoryAfter = memoryInfo.getTotalPss(); + Log.i(TAG, "Memory allocated for bitmap cache: " + (memoryAfter - memoryBefore)); + assertTrue("Memory allocation should not exceed 5MB", memoryAfter < memoryBefore + 5000); + } - private void assertSameColor(String message, int expected, int actual) { - try { - assertEquals(expected, actual); - } catch (AssertionFailedError e) { - throw new AssertionFailedError(message + " expected <#" + Integer.toHexString(expected) - + "> but found <#" + Integer.toHexString(actual) + "> instead"); - } + private void assertSameColor(String message, int expected, int actual) { + try { + assertEquals(expected, actual); + } catch (AssertionFailedError e) { + throw new AssertionFailedError( + message + + " expected <#" + + Integer.toHexString(expected) + + "> but found <#" + + Integer.toHexString(actual) + + "> instead"); } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/IllustrationTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/IllustrationTest.java index a4b6f27..253893e 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/IllustrationTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/IllustrationTest.java @@ -23,13 +23,11 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.view.View; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.View; - import com.android.setupwizardlib.view.Illustration; - import org.junit.Test; import org.junit.runner.RunWith; @@ -37,28 +35,29 @@ import org.junit.runner.RunWith; @SmallTest public class IllustrationTest { - @Test - public void testWillDraw() { - final Illustration illustration = new Illustration(InstrumentationRegistry.getContext()); - assertFalse("The illustration needs to be drawn", illustration.willNotDraw()); - } - - @Test - public void testAspectRatio() { - final Context context = InstrumentationRegistry.getContext(); - // Force the context to be xhdpi - context.getResources().getDisplayMetrics().density = 2.0f; - - final Illustration illustration = new Illustration(context); - illustration.setAspectRatio(3.0f); - final Drawable backgroundDrawable = new ColorDrawable(Color.RED); - final Drawable illustrationDrawable = new ColorDrawable(Color.BLUE); - illustration.setBackgroundDrawable(backgroundDrawable); - illustration.setIllustration(illustrationDrawable); - - illustration.measure(View.MeasureSpec.makeMeasureSpec(300, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); - // (300px / 3) round down to nearest mod (8dp = 16px) = 96px - assertEquals("Top padding should be 96", 96, illustration.getPaddingTop()); - } + @Test + public void testWillDraw() { + final Illustration illustration = new Illustration(InstrumentationRegistry.getContext()); + assertFalse("The illustration needs to be drawn", illustration.willNotDraw()); + } + + @Test + public void testAspectRatio() { + final Context context = InstrumentationRegistry.getContext(); + // Force the context to be xhdpi + context.getResources().getDisplayMetrics().density = 2.0f; + + final Illustration illustration = new Illustration(context); + illustration.setAspectRatio(3.0f); + final Drawable backgroundDrawable = new ColorDrawable(Color.RED); + final Drawable illustrationDrawable = new ColorDrawable(Color.BLUE); + illustration.setBackgroundDrawable(backgroundDrawable); + illustration.setIllustration(illustrationDrawable); + + illustration.measure( + View.MeasureSpec.makeMeasureSpec(300, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + // (300px / 3) round down to nearest mod (8dp = 16px) = 96px + assertEquals("Top padding should be 96", 96, illustration.getPaddingTop()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemAdapterTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemAdapterTest.java index e5875e4..63180dc 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemAdapterTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemAdapterTest.java @@ -24,78 +24,74 @@ import static org.mockito.Mockito.mock; import android.database.DataSetObserver; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.items.Item; import com.android.setupwizardlib.items.ItemAdapter; import com.android.setupwizardlib.items.ItemGroup; import com.android.setupwizardlib.items.ItemHierarchy; - +import java.util.Arrays; +import java.util.HashSet; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; -import java.util.Arrays; -import java.util.HashSet; - @RunWith(AndroidJUnit4.class) @SmallTest public class ItemAdapterTest { - private Item[] mItems = new Item[5]; - private ItemGroup mItemGroup = new ItemGroup(); - - @Before - public void setUp() throws Exception { - for (int i = 0; i < 5; i++) { - Item item = new Item(); - item.setTitle("TestTitle" + i); - item.setId(i); - item.setLayoutResource(((i % 3) + 1) * 10); - mItems[i] = item; - mItemGroup.addChild(item); - } - } - - @Test - public void testAdapter() { - ItemAdapter adapter = new ItemAdapter(mItemGroup); - assertEquals("Adapter should have 5 items", 5, adapter.getCount()); - assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); - assertEquals("ID should be same as position", 2, adapter.getItemId(2)); - - // Each test item has its own layout resource, and therefore its own view type - assertEquals("Should have 3 different view types", 3, adapter.getViewTypeCount()); - HashSet viewTypes = new HashSet<>(3); - viewTypes.add(adapter.getItemViewType(0)); - viewTypes.add(adapter.getItemViewType(1)); - viewTypes.add(adapter.getItemViewType(2)); - - assertEquals("View types should be 0, 1, 2", - new HashSet<>(Arrays.asList(0, 1, 2)), viewTypes); - } - - @Test - public void testGetRootItemHierarchy() { - ItemAdapter adapter = new ItemAdapter(mItemGroup); - ItemHierarchy root = adapter.getRootItemHierarchy(); - assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); - } - - @Test - public void testAdapterNotifications() { - ItemAdapter adapter = new ItemAdapter(mItemGroup); - final DataSetObserver observer = mock(DataSetObserver.class); - adapter.registerDataSetObserver(observer); - final InOrder inOrder = inOrder(observer); - - mItems[0].setTitle("Child 1"); - inOrder.verify(observer).onChanged(); - - mItemGroup.removeChild(mItems[1]); - inOrder.verify(observer).onChanged(); - - mItemGroup.addChild(mItems[1]); - inOrder.verify(observer).onChanged(); + private Item[] mItems = new Item[5]; + private ItemGroup mItemGroup = new ItemGroup(); + + @Before + public void setUp() throws Exception { + for (int i = 0; i < 5; i++) { + Item item = new Item(); + item.setTitle("TestTitle" + i); + item.setId(i); + item.setLayoutResource(((i % 3) + 1) * 10); + mItems[i] = item; + mItemGroup.addChild(item); } + } + + @Test + public void testAdapter() { + ItemAdapter adapter = new ItemAdapter(mItemGroup); + assertEquals("Adapter should have 5 items", 5, adapter.getCount()); + assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); + assertEquals("ID should be same as position", 2, adapter.getItemId(2)); + + // Each test item has its own layout resource, and therefore its own view type + assertEquals("Should have 3 different view types", 3, adapter.getViewTypeCount()); + HashSet viewTypes = new HashSet<>(3); + viewTypes.add(adapter.getItemViewType(0)); + viewTypes.add(adapter.getItemViewType(1)); + viewTypes.add(adapter.getItemViewType(2)); + + assertEquals("View types should be 0, 1, 2", new HashSet<>(Arrays.asList(0, 1, 2)), viewTypes); + } + + @Test + public void testGetRootItemHierarchy() { + ItemAdapter adapter = new ItemAdapter(mItemGroup); + ItemHierarchy root = adapter.getRootItemHierarchy(); + assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); + } + + @Test + public void testAdapterNotifications() { + ItemAdapter adapter = new ItemAdapter(mItemGroup); + final DataSetObserver observer = mock(DataSetObserver.class); + adapter.registerDataSetObserver(observer); + final InOrder inOrder = inOrder(observer); + + mItems[0].setTitle("Child 1"); + inOrder.verify(observer).onChanged(); + + mItemGroup.removeChild(mItems[1]); + inOrder.verify(observer).onChanged(); + + mItemGroup.addChild(mItems[1]); + inOrder.verify(observer).onChanged(); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemInflaterTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemInflaterTest.java index 20fd2cc..9e96bae 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemInflaterTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemInflaterTest.java @@ -22,12 +22,10 @@ import static org.junit.Assert.assertTrue; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.items.Item; import com.android.setupwizardlib.items.ItemGroup; import com.android.setupwizardlib.items.ItemHierarchy; import com.android.setupwizardlib.items.ItemInflater; - import org.junit.Test; import org.junit.runner.RunWith; @@ -35,25 +33,26 @@ import org.junit.runner.RunWith; @SmallTest public class ItemInflaterTest { - @Test - public void testDefaultPackage() { - ItemInflater inflater = new ItemInflater(InstrumentationRegistry.getContext()); - assertEquals("Default package should be the one containing Item class", - "com.android.setupwizardlib.items.", inflater.getDefaultPackage()); - } - - @Test - public void testInflate() { - ItemInflater inflater = new ItemInflater(InstrumentationRegistry.getContext()); - ItemHierarchy item = inflater.inflate(R.xml.test_items); - assertTrue("Inflated item should be ItemGroup", item instanceof ItemGroup); - ItemGroup itemGroup = (ItemGroup) item; - - Item child0 = (Item) itemGroup.getItemAt(0); - Item child1 = (Item) itemGroup.getItemAt(1); - assertEquals("Title of first child should be Title1", "Title1", child0.getTitle()); - assertEquals("ID of second child should be test_item_2", R.id.test_item_2, child1.getId()); - assertEquals("Summary of second child should be Summary2", "Summary2", - child1.getSummary()); - } + @Test + public void testDefaultPackage() { + ItemInflater inflater = new ItemInflater(InstrumentationRegistry.getContext()); + assertEquals( + "Default package should be the one containing Item class", + "com.android.setupwizardlib.items.", + inflater.getDefaultPackage()); + } + + @Test + public void testInflate() { + ItemInflater inflater = new ItemInflater(InstrumentationRegistry.getContext()); + ItemHierarchy item = inflater.inflate(R.xml.test_items); + assertTrue("Inflated item should be ItemGroup", item instanceof ItemGroup); + ItemGroup itemGroup = (ItemGroup) item; + + Item child0 = (Item) itemGroup.getItemAt(0); + Item child1 = (Item) itemGroup.getItemAt(1); + assertEquals("Title of first child should be Title1", "Title1", child0.getTitle()); + assertEquals("ID of second child should be test_item_2", R.id.test_item_2, child1.getId()); + assertEquals("Summary of second child should be Summary2", "Summary2", child1.getSummary()); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemLayoutTest.java index 85876b4..dbf71b2 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemLayoutTest.java @@ -17,26 +17,22 @@ package com.android.setupwizardlib.test; import static android.support.test.InstrumentationRegistry.getTargetContext; - import static org.junit.Assert.assertNotNull; import android.content.Context; -import android.support.test.filters.SmallTest; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.widget.FrameLayout; - +import android.support.test.filters.SmallTest; import com.android.setupwizardlib.R; import com.android.setupwizardlib.items.Item; - +import java.util.ArrayList; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import java.util.ArrayList; -import java.util.List; - /** * Sanity test for all the item layouts to make sure they won't crash when being inflated in * different themes. @@ -45,50 +41,50 @@ import java.util.List; @SmallTest public class ItemLayoutTest { - @Parameters - public static Iterable data() { - int[] themes = new int[] { - R.style.SuwThemeMaterial_Light, - R.style.SuwThemeMaterial, - R.style.SuwThemeGlif_Light, - R.style.SuwThemeGlif, - R.style.SuwThemeGlifV2_Light, - R.style.SuwThemeGlifV2 + @Parameters + public static Iterable data() { + int[] themes = + new int[] { + R.style.SuwThemeMaterial_Light, + R.style.SuwThemeMaterial, + R.style.SuwThemeGlif_Light, + R.style.SuwThemeGlif, + R.style.SuwThemeGlifV2_Light, + R.style.SuwThemeGlifV2 }; - int[] layouts = new int[] { - R.layout.suw_items_default, - R.layout.suw_items_verbose, - R.layout.suw_items_description + int[] layouts = + new int[] { + R.layout.suw_items_default, R.layout.suw_items_verbose, R.layout.suw_items_description }; - // Test all the possible combinations of themes and layouts. - List params = new ArrayList<>(); - for (int theme : themes) { - for (int layout : layouts) { - params.add(new Object[] { theme, layout }); - } - } - return params; + // Test all the possible combinations of themes and layouts. + List params = new ArrayList<>(); + for (int theme : themes) { + for (int layout : layouts) { + params.add(new Object[] {theme, layout}); + } } + return params; + } - private final Context mContext; - private final FrameLayout mParent; - private final Item mItem; + private final Context mContext; + private final FrameLayout mParent; + private final Item mItem; - public ItemLayoutTest(int theme, int layout) { - mContext = new ContextThemeWrapper(getTargetContext(), theme); - mParent = new FrameLayout(mContext); - mItem = new Item(); - mItem.setLayoutResource(layout); - } + public ItemLayoutTest(int theme, int layout) { + mContext = new ContextThemeWrapper(getTargetContext(), theme); + mParent = new FrameLayout(mContext); + mItem = new Item(); + mItem.setLayoutResource(layout); + } - @Test - public void testInflateLayoutHasBasicViews() { - LayoutInflater.from(mContext).inflate(mItem.getLayoutResource(), mParent, true); - mItem.onBindView(mParent); + @Test + public void testInflateLayoutHasBasicViews() { + LayoutInflater.from(mContext).inflate(mItem.getLayoutResource(), mParent, true); + mItem.onBindView(mParent); - assertNotNull("Title should exist", mParent.findViewById(R.id.suw_items_title)); - assertNotNull("Summary should exist", mParent.findViewById(R.id.suw_items_summary)); - assertNotNull("Icon should exist", mParent.findViewById(R.id.suw_items_icon)); - } + assertNotNull("Title should exist", mParent.findViewById(R.id.suw_items_title)); + assertNotNull("Summary should exist", mParent.findViewById(R.id.suw_items_summary)); + assertNotNull("Icon should exist", mParent.findViewById(R.id.suw_items_icon)); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemTest.java index b4ebabb..84990dd 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ItemTest.java @@ -28,19 +28,17 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.R; import com.android.setupwizardlib.items.Item; import com.android.setupwizardlib.items.ItemHierarchy.Observer; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,149 +50,149 @@ import org.mockito.MockitoAnnotations; @SmallTest public class ItemTest { - private TextView mTitleView; - private TextView mSummaryView; - private ImageView mIconView; - private FrameLayout mIconContainer; - - @Mock - private Observer mObserver; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testOnBindView() { - Item item = new Item(); - item.setTitle("TestTitle"); - item.setSummary("TestSummary"); - Drawable icon = new ShapeDrawable(); - icon.setLevel(4); - item.setIcon(icon); - View view = createLayout(); - - mIconView.setImageLevel(1); - Drawable recycledIcon = new ShapeDrawable(); - mIconView.setImageDrawable(recycledIcon); - - item.onBindView(view); - - assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString()); - assertEquals("Summary should be \"TestSummary\"", "TestSummary", - mSummaryView.getText().toString()); - assertSame("Icon should be the icon shape drawable", icon, mIconView.getDrawable()); - assertEquals("Recycled icon level should not change", 1, recycledIcon.getLevel()); - assertEquals("Icon should be level 4", 4, icon.getLevel()); - } - - @Test - public void testSingleLineItem() { - Item item = new Item(); - item.setTitle("TestTitle"); - View view = createLayout(); - - item.onBindView(view); - - assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString()); - assertEquals("Summary should be gone", View.GONE, mSummaryView.getVisibility()); - assertEquals("IconContainer should be gone", View.GONE, mIconContainer.getVisibility()); - } - - @Test - public void testProperties() { - Item item = new Item(); - item.registerObserver(mObserver); - final InOrder inOrder = inOrder(mObserver); - - item.setTitle("TestTitle"); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - item.setSummary("TestSummary"); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - item.setEnabled(false); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - ShapeDrawable icon = new ShapeDrawable(); - item.setIcon(icon); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - item.setId(12345); - - item.setLayoutResource(56789); - inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); - - assertEquals("Title should be \"TestTitle\"", "TestTitle", item.getTitle()); - assertEquals("Summary should be \"TestSummary\"", "TestSummary", item.getSummary()); - assertFalse("Enabled should be false", item.isEnabled()); - assertSame("Icon should be same as set", icon, item.getIcon()); - assertEquals("ID should be 12345", 12345, item.getId()); - assertEquals("Layout resource should be 56789", 56789, item.getLayoutResource()); - } - - @Test - public void testDefaultValues() { - Item item = new Item(); - - assertNull("Default title should be null", item.getTitle()); - assertNull("Default summary should be null", item.getSummary()); - assertNull("Default icon should be null", item.getIcon()); - assertTrue("Default enabled should be true", item.isEnabled()); - assertEquals("Default ID should be 0", 0, item.getId()); - assertEquals("Default layout resource should be R.layout.suw_items_text", - R.layout.suw_items_default, item.getLayoutResource()); - assertTrue("Default visible should be true", item.isVisible()); - } - - @Test - public void testHierarchyImplementation() { - Item item = new Item(); - item.setId(12345); - - assertEquals("getCount should be 1", 1, item.getCount()); - assertSame("getItemAt should return itself", item, item.getItemAt(0)); - assertSame("findItemById with same ID should return itself", item, - item.findItemById(12345)); - assertNull("findItemById with different ID should return null", item.findItemById(34567)); - } - - @Test - public void testVisible() { - Item item = new Item(); - item.registerObserver(mObserver); - item.setVisible(false); - - assertFalse("Item should not be visible", item.isVisible()); - assertEquals("Item count should be 0 when not visible", 0, item.getCount()); - - verify(mObserver).onItemRangeRemoved(eq(item), eq(0), eq(1)); - - item.setVisible(true); - verify(mObserver).onItemRangeInserted(eq(item), eq(0), eq(1)); - } - - private ViewGroup createLayout() { - Context context = InstrumentationRegistry.getContext(); - ViewGroup root = new FrameLayout(context); - - mTitleView = new TextView(context); - mTitleView.setId(R.id.suw_items_title); - root.addView(mTitleView); - - mSummaryView = new TextView(context); - mSummaryView.setId(R.id.suw_items_summary); - root.addView(mSummaryView); - - mIconContainer = new FrameLayout(context); - mIconContainer.setId(R.id.suw_items_icon_container); - root.addView(mIconContainer); - - mIconView = new ImageView(context); - mIconView.setId(R.id.suw_items_icon); - mIconContainer.addView(mIconView); - - return root; - } + private TextView mTitleView; + private TextView mSummaryView; + private ImageView mIconView; + private FrameLayout mIconContainer; + + @Mock private Observer mObserver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testOnBindView() { + Item item = new Item(); + item.setTitle("TestTitle"); + item.setSummary("TestSummary"); + Drawable icon = new ShapeDrawable(); + icon.setLevel(4); + item.setIcon(icon); + View view = createLayout(); + + mIconView.setImageLevel(1); + Drawable recycledIcon = new ShapeDrawable(); + mIconView.setImageDrawable(recycledIcon); + + item.onBindView(view); + + assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString()); + assertEquals( + "Summary should be \"TestSummary\"", "TestSummary", mSummaryView.getText().toString()); + assertSame("Icon should be the icon shape drawable", icon, mIconView.getDrawable()); + assertEquals("Recycled icon level should not change", 1, recycledIcon.getLevel()); + assertEquals("Icon should be level 4", 4, icon.getLevel()); + } + + @Test + public void testSingleLineItem() { + Item item = new Item(); + item.setTitle("TestTitle"); + View view = createLayout(); + + item.onBindView(view); + + assertEquals("Title should be \"TestTitle\"", "TestTitle", mTitleView.getText().toString()); + assertEquals("Summary should be gone", View.GONE, mSummaryView.getVisibility()); + assertEquals("IconContainer should be gone", View.GONE, mIconContainer.getVisibility()); + } + + @Test + public void testProperties() { + Item item = new Item(); + item.registerObserver(mObserver); + final InOrder inOrder = inOrder(mObserver); + + item.setTitle("TestTitle"); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + item.setSummary("TestSummary"); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + item.setEnabled(false); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + ShapeDrawable icon = new ShapeDrawable(); + item.setIcon(icon); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + item.setId(12345); + + item.setLayoutResource(56789); + inOrder.verify(mObserver).onItemRangeChanged(eq(item), eq(0), eq(1)); + + assertEquals("Title should be \"TestTitle\"", "TestTitle", item.getTitle()); + assertEquals("Summary should be \"TestSummary\"", "TestSummary", item.getSummary()); + assertFalse("Enabled should be false", item.isEnabled()); + assertSame("Icon should be same as set", icon, item.getIcon()); + assertEquals("ID should be 12345", 12345, item.getId()); + assertEquals("Layout resource should be 56789", 56789, item.getLayoutResource()); + } + + @Test + public void testDefaultValues() { + Item item = new Item(); + + assertNull("Default title should be null", item.getTitle()); + assertNull("Default summary should be null", item.getSummary()); + assertNull("Default icon should be null", item.getIcon()); + assertTrue("Default enabled should be true", item.isEnabled()); + assertEquals("Default ID should be 0", 0, item.getId()); + assertEquals( + "Default layout resource should be R.layout.suw_items_text", + R.layout.suw_items_default, + item.getLayoutResource()); + assertTrue("Default visible should be true", item.isVisible()); + } + + @Test + public void testHierarchyImplementation() { + Item item = new Item(); + item.setId(12345); + + assertEquals("getCount should be 1", 1, item.getCount()); + assertSame("getItemAt should return itself", item, item.getItemAt(0)); + assertSame("findItemById with same ID should return itself", item, item.findItemById(12345)); + assertNull("findItemById with different ID should return null", item.findItemById(34567)); + } + + @Test + public void testVisible() { + Item item = new Item(); + item.registerObserver(mObserver); + item.setVisible(false); + + assertFalse("Item should not be visible", item.isVisible()); + assertEquals("Item count should be 0 when not visible", 0, item.getCount()); + + verify(mObserver).onItemRangeRemoved(eq(item), eq(0), eq(1)); + + item.setVisible(true); + verify(mObserver).onItemRangeInserted(eq(item), eq(0), eq(1)); + } + + private ViewGroup createLayout() { + Context context = InstrumentationRegistry.getContext(); + ViewGroup root = new FrameLayout(context); + + mTitleView = new TextView(context); + mTitleView.setId(R.id.suw_items_title); + root.addView(mTitleView); + + mSummaryView = new TextView(context); + mSummaryView.setId(R.id.suw_items_summary); + root.addView(mSummaryView); + + mIconContainer = new FrameLayout(context); + mIconContainer.setId(R.id.suw_items_icon_container); + root.addView(mIconContainer); + + mIconView = new ImageView(context); + mIconView.setId(R.id.suw_items_icon); + mIconContainer.addView(mIconView); + + return root; + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ReflectionInflaterTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ReflectionInflaterTest.java index 137a146..69e5882 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ReflectionInflaterTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/ReflectionInflaterTest.java @@ -20,64 +20,59 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.annotation.NonNull; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.ScaleAnimation; - -import androidx.annotation.NonNull; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.items.ReflectionInflater; - +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.List; - @SmallTest @RunWith(AndroidJUnit4.class) public class ReflectionInflaterTest { - @Test - public void testInflateXml() { - final Context context = InstrumentationRegistry.getContext(); - TestInflater inflater = new TestInflater(context); - final Animation result = inflater.inflate(R.xml.reflection_inflater_test); + @Test + public void testInflateXml() { + final Context context = InstrumentationRegistry.getContext(); + TestInflater inflater = new TestInflater(context); + final Animation result = inflater.inflate(R.xml.reflection_inflater_test); - assertTrue(result instanceof AnimationSet); - final AnimationSet set = (AnimationSet) result; - final List animations = set.getAnimations(); - assertEquals(1, animations.size()); - assertTrue(animations.get(0) instanceof ScaleAnimation); - } + assertTrue(result instanceof AnimationSet); + final AnimationSet set = (AnimationSet) result; + final List animations = set.getAnimations(); + assertEquals(1, animations.size()); + assertTrue(animations.get(0) instanceof ScaleAnimation); + } - @Test - public void testDefaultPackage() { - final Context context = InstrumentationRegistry.getContext(); - TestInflater inflater = new TestInflater(context); - inflater.setDefaultPackage("android.view.animation."); - final Animation result = - inflater.inflate(R.xml.reflection_inflater_test_with_default_package); + @Test + public void testDefaultPackage() { + final Context context = InstrumentationRegistry.getContext(); + TestInflater inflater = new TestInflater(context); + inflater.setDefaultPackage("android.view.animation."); + final Animation result = inflater.inflate(R.xml.reflection_inflater_test_with_default_package); - assertTrue(result instanceof AnimationSet); - final AnimationSet set = (AnimationSet) result; - final List animations = set.getAnimations(); - assertEquals(1, animations.size()); - assertTrue(animations.get(0) instanceof ScaleAnimation); - } + assertTrue(result instanceof AnimationSet); + final AnimationSet set = (AnimationSet) result; + final List animations = set.getAnimations(); + assertEquals(1, animations.size()); + assertTrue(animations.get(0) instanceof ScaleAnimation); + } - private static class TestInflater extends ReflectionInflater { + private static class TestInflater extends ReflectionInflater { - protected TestInflater(@NonNull Context context) { - super(context); - } + protected TestInflater(@NonNull Context context) { + super(context); + } - @Override - protected void onAddChildItem(Animation parent, Animation child) { - final AnimationSet group = (AnimationSet) parent; - group.addAnimation(child); - } + @Override + protected void onAddChildItem(Animation parent, Animation child) { + final AnimationSet group = (AnimationSet) parent; + group.addAnimation(child); } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java index 531d69e..9d2f784 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardLayoutTest.java @@ -27,9 +27,7 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Parcelable; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.annotation.IdRes; import android.util.SparseArray; import android.view.AbsSavedState; import android.view.ContextThemeWrapper; @@ -37,15 +35,14 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; - -import androidx.annotation.IdRes; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.SetupWizardLayout; import com.android.setupwizardlib.template.HeaderMixin; import com.android.setupwizardlib.template.NavigationBarMixin; import com.android.setupwizardlib.template.ProgressBarMixin; import com.android.setupwizardlib.view.NavigationBar; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,191 +51,196 @@ import org.junit.runner.RunWith; @SmallTest public class SetupWizardLayoutTest { - @IdRes - private static final int ID1234 = 1234; - - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - } - - @Test - public void testDefaultTemplate() { - SetupWizardLayout layout = new SetupWizardLayout(mContext); - assertDefaultTemplateInflated(layout); - } - - @Test - public void testSetHeaderText() { - SetupWizardLayout layout = new SetupWizardLayout(mContext); - TextView title = (TextView) layout.findViewById(R.id.suw_layout_title); - layout.setHeaderText("Abracadabra"); - assertEquals("Header text should be \"Abracadabra\"", "Abracadabra", title.getText()); - } - - @Test - public void testAddView() { - SetupWizardLayout layout = new SetupWizardLayout(mContext); - TextView tv = new TextView(mContext); - tv.setId(R.id.test_view_id); - layout.addView(tv); - assertDefaultTemplateInflated(layout); - View view = layout.findViewById(R.id.test_view_id); - assertSame("The view added should be the same text view", tv, view); - } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - SetupWizardLayout layout = (SetupWizardLayout) inflater.inflate(R.layout.test_layout, null); - assertDefaultTemplateInflated(layout); - View content = layout.findViewById(R.id.test_content); - assertTrue("@id/test_content should be a TextView", content instanceof TextView); - } - - @Test - public void testCustomTemplate() { - SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); - View templateView = layout.findViewById(R.id.test_template_view); - assertNotNull("@id/test_template_view should exist in template", templateView); - - TextView tv = new TextView(mContext); - tv.setId(R.id.test_view_id); - layout.addView(tv); - - templateView = layout.findViewById(R.id.test_template_view); - assertNotNull("@id/test_template_view should exist in template", templateView); - View contentView = layout.findViewById(R.id.test_view_id); - assertSame("The view added should be the same text view", tv, contentView); - - // The following methods should be no-ops because the custom template doesn't contain the - // corresponding optional views. Just check that they don't throw exceptions. - layout.setHeaderText("Abracadabra"); - layout.setIllustration(new ColorDrawable(Color.MAGENTA)); - layout.setLayoutBackground(new ColorDrawable(Color.RED)); - } - - @Test - public void testGetNavigationBar() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - final NavigationBar navigationBar = layout.getNavigationBar(); - assertEquals("Navigation bar should have ID = @id/suw_layout_navigation_bar", - R.id.suw_layout_navigation_bar, navigationBar.getId()); - } - - @Test - public void testGetNavigationBarNull() { - // test_template does not have navigation bar so getNavigationBar() should return null. - final SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); - final NavigationBar navigationBar = layout.getNavigationBar(); - assertNull("getNavigationBar() in test_template should return null", navigationBar); - } - - @Test - public void testShowProgressBar() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.showProgressBar(); - assertTrue("Progress bar should be shown", layout.isProgressBarShown()); - final View progressBar = layout.findViewById(R.id.suw_layout_progress); - assertTrue("Progress bar view should be shown", - progressBar instanceof ProgressBar && progressBar.getVisibility() == View.VISIBLE); - } - - @Test - public void testHideProgressBar() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.showProgressBar(); - assertTrue("Progress bar should be shown", layout.isProgressBarShown()); - layout.hideProgressBar(); - assertFalse("Progress bar should be hidden", layout.isProgressBarShown()); - final View progressBar = layout.findViewById(R.id.suw_layout_progress); - assertTrue("Progress bar view should exist", - progressBar == null || progressBar.getVisibility() != View.VISIBLE); - } - - @Test - public void testShowProgressBarNotExist() { - // test_template does not have progress bar, so showNavigationBar() should do nothing. - final SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); - layout.showProgressBar(); - assertFalse("Progress bar should not be shown", layout.isProgressBarShown()); - } - - @Test - public void testNonMaterialTheme() { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - android.R.style.Theme); - new SetupWizardLayout(mContext); - // Inflating with a non-Material theme should not crash - } - - @Test - public void testOnRestoreFromInstanceState() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.setId(ID1234); - - SparseArray container = new SparseArray<>(); - layout.saveHierarchyState(container); - - final SetupWizardLayout layout2 = new SetupWizardLayout(mContext); - layout2.setId(ID1234); - layout2.restoreHierarchyState(container); - - assertFalse("Progress bar should not be shown", layout2.isProgressBarShown()); - } - - @Test - public void testOnRestoreFromInstanceStateProgressBarShown() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.setId(ID1234); - - layout.setProgressBarShown(true); - - SparseArray container = new SparseArray<>(); - layout.saveHierarchyState(container); - - final SetupWizardLayout layout2 = new SetupWizardLayout(mContext); - layout2.setId(ID1234); - layout2.restoreHierarchyState(container); - - assertTrue("Progress bar should be shown", layout2.isProgressBarShown()); - } - - @Test - public void testOnRestoreFromIncompatibleInstanceState() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - layout.setId(ID1234); - - SparseArray container = new SparseArray<>(); - container.put(1234, AbsSavedState.EMPTY_STATE); - layout.restoreHierarchyState(container); - - // SetupWizardLayout shouldn't crash with incompatible Parcelable - - assertFalse("Progress bar should not be shown", layout.isProgressBarShown()); - } - - @Test - public void testGetMixins() { - final SetupWizardLayout layout = new SetupWizardLayout(mContext); - assertNotNull("SetupWizardLayout should have header mixin", - layout.getMixin(HeaderMixin.class)); - assertNotNull("SetupWizardLayout should have progress bar mixin", - layout.getMixin(ProgressBarMixin.class)); - assertNotNull("SetupWizardLayout should have navigation bar mixin", - layout.getMixin(NavigationBarMixin.class)); - } - - private void assertDefaultTemplateInflated(SetupWizardLayout layout) { - View decorView = layout.findViewById(R.id.suw_layout_decor); - View navbar = layout.findViewById(R.id.suw_layout_navigation_bar); - View title = layout.findViewById(R.id.suw_layout_title); - assertNotNull("@id/suw_layout_decor_view should not be null", decorView); - assertTrue("@id/suw_layout_navigation_bar should be an instance of NavigationBar", - navbar instanceof NavigationBar); - assertNotNull("@id/suw_layout_title should not be null", title); - } + @IdRes private static final int ID1234 = 1234; + + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + } + + @Test + public void testDefaultTemplate() { + SetupWizardLayout layout = new SetupWizardLayout(mContext); + assertDefaultTemplateInflated(layout); + } + + @Test + public void testSetHeaderText() { + SetupWizardLayout layout = new SetupWizardLayout(mContext); + TextView title = (TextView) layout.findViewById(R.id.suw_layout_title); + layout.setHeaderText("Abracadabra"); + assertEquals("Header text should be \"Abracadabra\"", "Abracadabra", title.getText()); + } + + @Test + public void testAddView() { + SetupWizardLayout layout = new SetupWizardLayout(mContext); + TextView tv = new TextView(mContext); + tv.setId(R.id.test_view_id); + layout.addView(tv); + assertDefaultTemplateInflated(layout); + View view = layout.findViewById(R.id.test_view_id); + assertSame("The view added should be the same text view", tv, view); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + SetupWizardLayout layout = (SetupWizardLayout) inflater.inflate(R.layout.test_layout, null); + assertDefaultTemplateInflated(layout); + View content = layout.findViewById(R.id.test_content); + assertTrue("@id/test_content should be a TextView", content instanceof TextView); + } + + @Test + public void testCustomTemplate() { + SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); + View templateView = layout.findViewById(R.id.test_template_view); + assertNotNull("@id/test_template_view should exist in template", templateView); + + TextView tv = new TextView(mContext); + tv.setId(R.id.test_view_id); + layout.addView(tv); + + templateView = layout.findViewById(R.id.test_template_view); + assertNotNull("@id/test_template_view should exist in template", templateView); + View contentView = layout.findViewById(R.id.test_view_id); + assertSame("The view added should be the same text view", tv, contentView); + + // The following methods should be no-ops because the custom template doesn't contain the + // corresponding optional views. Just check that they don't throw exceptions. + layout.setHeaderText("Abracadabra"); + layout.setIllustration(new ColorDrawable(Color.MAGENTA)); + layout.setLayoutBackground(new ColorDrawable(Color.RED)); + } + + @Test + public void testGetNavigationBar() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + final NavigationBar navigationBar = layout.getNavigationBar(); + assertEquals( + "Navigation bar should have ID = @id/suw_layout_navigation_bar", + R.id.suw_layout_navigation_bar, + navigationBar.getId()); + } + + @Test + public void testGetNavigationBarNull() { + // test_template does not have navigation bar so getNavigationBar() should return null. + final SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); + final NavigationBar navigationBar = layout.getNavigationBar(); + assertNull("getNavigationBar() in test_template should return null", navigationBar); + } + + @Test + public void testShowProgressBar() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.showProgressBar(); + assertTrue("Progress bar should be shown", layout.isProgressBarShown()); + final View progressBar = layout.findViewById(R.id.suw_layout_progress); + assertTrue( + "Progress bar view should be shown", + progressBar instanceof ProgressBar && progressBar.getVisibility() == View.VISIBLE); + } + + @Test + public void testHideProgressBar() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.showProgressBar(); + assertTrue("Progress bar should be shown", layout.isProgressBarShown()); + layout.hideProgressBar(); + assertFalse("Progress bar should be hidden", layout.isProgressBarShown()); + final View progressBar = layout.findViewById(R.id.suw_layout_progress); + assertTrue( + "Progress bar view should exist", + progressBar == null || progressBar.getVisibility() != View.VISIBLE); + } + + @Test + public void testShowProgressBarNotExist() { + // test_template does not have progress bar, so showNavigationBar() should do nothing. + final SetupWizardLayout layout = new SetupWizardLayout(mContext, R.layout.test_template); + layout.showProgressBar(); + assertFalse("Progress bar should not be shown", layout.isProgressBarShown()); + } + + @Test + public void testNonMaterialTheme() { + mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), android.R.style.Theme); + new SetupWizardLayout(mContext); + // Inflating with a non-Material theme should not crash + } + + @Test + public void testOnRestoreFromInstanceState() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.setId(ID1234); + + SparseArray container = new SparseArray<>(); + layout.saveHierarchyState(container); + + final SetupWizardLayout layout2 = new SetupWizardLayout(mContext); + layout2.setId(ID1234); + layout2.restoreHierarchyState(container); + + assertFalse("Progress bar should not be shown", layout2.isProgressBarShown()); + } + + @Test + public void testOnRestoreFromInstanceStateProgressBarShown() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.setId(ID1234); + + layout.setProgressBarShown(true); + + SparseArray container = new SparseArray<>(); + layout.saveHierarchyState(container); + + final SetupWizardLayout layout2 = new SetupWizardLayout(mContext); + layout2.setId(ID1234); + layout2.restoreHierarchyState(container); + + assertTrue("Progress bar should be shown", layout2.isProgressBarShown()); + } + + @Test + public void testOnRestoreFromIncompatibleInstanceState() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + layout.setId(ID1234); + + SparseArray container = new SparseArray<>(); + container.put(1234, AbsSavedState.EMPTY_STATE); + layout.restoreHierarchyState(container); + + // SetupWizardLayout shouldn't crash with incompatible Parcelable + + assertFalse("Progress bar should not be shown", layout.isProgressBarShown()); + } + + @Test + public void testGetMixins() { + final SetupWizardLayout layout = new SetupWizardLayout(mContext); + assertNotNull("SetupWizardLayout should have header mixin", layout.getMixin(HeaderMixin.class)); + assertNotNull( + "SetupWizardLayout should have progress bar mixin", + layout.getMixin(ProgressBarMixin.class)); + assertNotNull( + "SetupWizardLayout should have navigation bar mixin", + layout.getMixin(NavigationBarMixin.class)); + } + + private void assertDefaultTemplateInflated(SetupWizardLayout layout) { + View decorView = layout.findViewById(R.id.suw_layout_decor); + View navbar = layout.findViewById(R.id.suw_layout_navigation_bar); + View title = layout.findViewById(R.id.suw_layout_title); + assertNotNull("@id/suw_layout_decor_view should not be null", decorView); + assertTrue( + "@id/suw_layout_navigation_bar should be an instance of NavigationBar", + navbar instanceof NavigationBar); + assertNotNull("@id/suw_layout_title should not be null", title); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardListLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardListLayoutTest.java index 5c34fe0..fc18a31 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardListLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SetupWizardListLayoutTest.java @@ -25,20 +25,18 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; - +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.SetupWizardLayout; import com.android.setupwizardlib.SetupWizardListLayout; import com.android.setupwizardlib.view.NavigationBar; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,90 +45,93 @@ import org.junit.runner.RunWith; @SmallTest public class SetupWizardListLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(InstrumentationRegistry.getContext(), - R.style.SuwThemeMaterial_Light); - } - - @Test - public void testDefaultTemplate() { - SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - assertListTemplateInflated(layout); + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = + new ContextThemeWrapper( + InstrumentationRegistry.getContext(), R.style.SuwThemeMaterial_Light); + } + + @Test + public void testDefaultTemplate() { + SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + assertListTemplateInflated(layout); + } + + @Test + public void testAddView() { + SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + TextView tv = new TextView(mContext); + try { + layout.addView(tv); + fail("Adding view to ListLayout should throw"); + } catch (UnsupportedOperationException e) { + // Expected exception } - - @Test - public void testAddView() { - SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - TextView tv = new TextView(mContext); - try { - layout.addView(tv); - fail("Adding view to ListLayout should throw"); - } catch (UnsupportedOperationException e) { - // Expected exception - } - } - - @Test - public void testInflateFromXml() { - LayoutInflater inflater = LayoutInflater.from(mContext); - SetupWizardListLayout layout = (SetupWizardListLayout) - inflater.inflate(R.layout.test_list_layout, null); - assertListTemplateInflated(layout); + } + + @Test + public void testInflateFromXml() { + LayoutInflater inflater = LayoutInflater.from(mContext); + SetupWizardListLayout layout = + (SetupWizardListLayout) inflater.inflate(R.layout.test_list_layout, null); + assertListTemplateInflated(layout); + } + + @Test + public void testShowProgressBar() { + final SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + layout.showProgressBar(); + assertTrue("Progress bar should be shown", layout.isProgressBarShown()); + final View progressBar = layout.findViewById(R.id.suw_layout_progress); + assertTrue( + "Progress bar view should be shown", + progressBar instanceof ProgressBar && progressBar.getVisibility() == View.VISIBLE); + } + + @Test + public void testDividerInsetLegacy() { + SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertListTemplateInflated(layout); - @Test - public void testShowProgressBar() { - final SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - layout.showProgressBar(); - assertTrue("Progress bar should be shown", layout.isProgressBarShown()); - final View progressBar = layout.findViewById(R.id.suw_layout_progress); - assertTrue("Progress bar view should be shown", - progressBar instanceof ProgressBar && progressBar.getVisibility() == View.VISIBLE); - } - - @Test - public void testDividerInsetLegacy() { - SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertListTemplateInflated(layout); - - layout.setDividerInset(10); - assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); + layout.setDividerInset(10); + assertEquals("Divider inset should be 10", 10, layout.getDividerInset()); - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } - - @Test - public void testDividerInsets() { - SetupWizardListLayout layout = new SetupWizardListLayout(mContext); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - } - assertListTemplateInflated(layout); - - layout.setDividerInsets(10, 15); - assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); - assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); - - final Drawable divider = layout.getDivider(); - assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); - } + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } - private void assertListTemplateInflated(SetupWizardLayout layout) { - View decorView = layout.findViewById(R.id.suw_layout_decor); - View navbar = layout.findViewById(R.id.suw_layout_navigation_bar); - View title = layout.findViewById(R.id.suw_layout_title); - View list = layout.findViewById(android.R.id.list); - assertNotNull("@id/suw_layout_decor_view should not be null", decorView); - assertTrue("@id/suw_layout_navigation_bar should be an instance of NavigationBar", - navbar instanceof NavigationBar); - assertNotNull("@id/suw_layout_title should not be null", title); - assertTrue("@android:id/list should be an instance of ListView", list instanceof ListView); + @Test + public void testDividerInsets() { + SetupWizardListLayout layout = new SetupWizardListLayout(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } + assertListTemplateInflated(layout); + + layout.setDividerInsets(10, 15); + assertEquals("Divider inset start should be 10", 10, layout.getDividerInsetStart()); + assertEquals("Divider inset end should be 15", 15, layout.getDividerInsetEnd()); + + final Drawable divider = layout.getDivider(); + assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable); + } + + private void assertListTemplateInflated(SetupWizardLayout layout) { + View decorView = layout.findViewById(R.id.suw_layout_decor); + View navbar = layout.findViewById(R.id.suw_layout_navigation_bar); + View title = layout.findViewById(R.id.suw_layout_title); + View list = layout.findViewById(android.R.id.list); + assertNotNull("@id/suw_layout_decor_view should not be null", decorView); + assertTrue( + "@id/suw_layout_navigation_bar should be an instance of NavigationBar", + navbar instanceof NavigationBar); + assertNotNull("@id/suw_layout_title should not be null", title); + assertTrue("@android:id/list should be an instance of ListView", list instanceof ListView); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SimpleInflaterTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SimpleInflaterTest.java index f4738ca..da39a7b 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SimpleInflaterTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SimpleInflaterTest.java @@ -20,15 +20,12 @@ import static org.junit.Assert.assertEquals; import android.content.Context; import android.content.res.Resources; +import androidx.annotation.NonNull; +import android.util.AttributeSet; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.AttributeSet; - -import androidx.annotation.NonNull; - import com.android.setupwizardlib.items.SimpleInflater; - import org.junit.Test; import org.junit.runner.RunWith; @@ -36,30 +33,30 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class SimpleInflaterTest { - @Test - public void testInflateXml() { - final Context context = InstrumentationRegistry.getContext(); - TestInflater inflater = new TestInflater(context.getResources()); - final StringBuilder result = inflater.inflate(R.xml.simple_inflater_test); + @Test + public void testInflateXml() { + final Context context = InstrumentationRegistry.getContext(); + TestInflater inflater = new TestInflater(context.getResources()); + final StringBuilder result = inflater.inflate(R.xml.simple_inflater_test); - assertEquals("Parent[null] > Child[foobar]", result.toString()); - } + assertEquals("Parent[null] > Child[foobar]", result.toString()); + } - private static class TestInflater extends SimpleInflater { + private static class TestInflater extends SimpleInflater { - protected TestInflater(@NonNull Resources resources) { - super(resources); - } + protected TestInflater(@NonNull Resources resources) { + super(resources); + } - @Override - protected StringBuilder onCreateItem(String tagName, AttributeSet attrs) { - final String attribute = attrs.getAttributeValue(null, "myattribute"); - return new StringBuilder(tagName).append("[").append(attribute).append("]"); - } + @Override + protected StringBuilder onCreateItem(String tagName, AttributeSet attrs) { + final String attribute = attrs.getAttributeValue(null, "myattribute"); + return new StringBuilder(tagName).append("[").append(attribute).append("]"); + } - @Override - protected void onAddChildItem(StringBuilder parent, StringBuilder child) { - parent.append(" > ").append(child); - } + @Override + protected void onAddChildItem(StringBuilder parent, StringBuilder child) { + parent.append(" > ").append(child); } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SpanHelperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SpanHelperTest.java index 903cf5e..920d7ab 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SpanHelperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SpanHelperTest.java @@ -19,13 +19,11 @@ package com.android.setupwizardlib.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.text.Annotation; import android.text.SpannableStringBuilder; - +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.setupwizardlib.span.SpanHelper; - import org.junit.Test; import org.junit.runner.RunWith; @@ -33,17 +31,17 @@ import org.junit.runner.RunWith; @SmallTest public class SpanHelperTest { - @Test - public void testReplaceSpan() { - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - Annotation oldSpan = new Annotation("key", "value"); - Annotation newSpan = new Annotation("newkey", "newvalue"); - ssb.setSpan(oldSpan, 2, 5, 0 /* flags */); + @Test + public void testReplaceSpan() { + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + Annotation oldSpan = new Annotation("key", "value"); + Annotation newSpan = new Annotation("newkey", "newvalue"); + ssb.setSpan(oldSpan, 2, 5, 0 /* flags */); - SpanHelper.replaceSpan(ssb, oldSpan, newSpan); + SpanHelper.replaceSpan(ssb, oldSpan, newSpan); - final Object[] spans = ssb.getSpans(0, ssb.length(), Object.class); - assertEquals("There should be one span in the builder", 1, spans.length); - assertSame("The span should be newSpan", newSpan, spans[0]); - } + final Object[] spans = ssb.getSpans(0, ssb.length(), Object.class); + assertEquals("There should be one span in the builder", 1, spans.length); + assertSame("The span should be newSpan", newSpan, spans[0]); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java index 006e5c4..e0fd49b 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java @@ -24,9 +24,7 @@ import android.graphics.drawable.ShapeDrawable; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - import com.android.setupwizardlib.view.StatusBarBackgroundLayout; - import org.junit.Test; import org.junit.runner.RunWith; @@ -34,46 +32,48 @@ import org.junit.runner.RunWith; @SmallTest public class StatusBarBackgroundLayoutTest { - @Test - public void testSetStatusBarBackground() { - final StatusBarBackgroundLayout layout = new StatusBarBackgroundLayout( - InstrumentationRegistry.getContext()); - final ShapeDrawable drawable = new ShapeDrawable(); - layout.setStatusBarBackground(drawable); - assertSame("Status bar background drawable should be same as set", - drawable, layout.getStatusBarBackground()); - } + @Test + public void testSetStatusBarBackground() { + final StatusBarBackgroundLayout layout = + new StatusBarBackgroundLayout(InstrumentationRegistry.getContext()); + final ShapeDrawable drawable = new ShapeDrawable(); + layout.setStatusBarBackground(drawable); + assertSame( + "Status bar background drawable should be same as set", + drawable, + layout.getStatusBarBackground()); + } - @Test - public void testAttachedToWindow() { - // Attaching to window should request apply window inset - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - final TestStatusBarBackgroundLayout layout = - new TestStatusBarBackgroundLayout(InstrumentationRegistry.getContext()); - layout.mRequestApplyInsets = false; - layout.onAttachedToWindow(); + @Test + public void testAttachedToWindow() { + // Attaching to window should request apply window inset + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + final TestStatusBarBackgroundLayout layout = + new TestStatusBarBackgroundLayout(InstrumentationRegistry.getContext()); + layout.mRequestApplyInsets = false; + layout.onAttachedToWindow(); - assertTrue("Attaching to window should apply window inset", layout.mRequestApplyInsets); - } + assertTrue("Attaching to window should apply window inset", layout.mRequestApplyInsets); } + } - private static class TestStatusBarBackgroundLayout extends StatusBarBackgroundLayout { + private static class TestStatusBarBackgroundLayout extends StatusBarBackgroundLayout { - boolean mRequestApplyInsets = false; + boolean mRequestApplyInsets = false; - TestStatusBarBackgroundLayout(Context context) { - super(context); - } + TestStatusBarBackgroundLayout(Context context) { + super(context); + } - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - } + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + } - @Override - public void requestApplyInsets() { - super.requestApplyInsets(); - mRequestApplyInsets = true; - } + @Override + public void requestApplyInsets() { + super.requestApplyInsets(); + mRequestApplyInsets = true; } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java index 98c28f6..1b534e1 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.test; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import android.annotation.SuppressLint; @@ -28,19 +27,17 @@ import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.support.test.InstrumentationRegistry; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; -import android.view.ContextThemeWrapper; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; - import com.android.setupwizardlib.test.util.MockWindow; import com.android.setupwizardlib.util.SystemBarHelper; - import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,241 +46,240 @@ import org.junit.runner.RunWith; @SmallTest public class SystemBarHelperTest { - @Rule - public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); - - private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; - - @SuppressLint("InlinedApi") - private static final int DEFAULT_IMMERSIVE_FLAGS = - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - - @SuppressLint("InlinedApi") - private static final int DIALOG_IMMERSIVE_FLAGS = - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - - @UiThreadTest - @Test - public void testAddVisibilityFlagView() { - final View view = createViewWithSystemUiVisibility(0x456); - SystemBarHelper.addVisibilityFlag(view, 0x1400); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - // Check that result is 0x1456, because 0x1400 | 0x456 = 0x1456. - assertEquals("View visibility should be 0x1456", 0x1456, view.getSystemUiVisibility()); - } + @Rule public UiThreadTestRule mUiThreadTestRule = new UiThreadTestRule(); + + private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; + + @SuppressLint("InlinedApi") + private static final int DEFAULT_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + + @SuppressLint("InlinedApi") + private static final int DIALOG_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + @UiThreadTest + @Test + public void testAddVisibilityFlagView() { + final View view = createViewWithSystemUiVisibility(0x456); + SystemBarHelper.addVisibilityFlag(view, 0x1400); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + // Check that result is 0x1456, because 0x1400 | 0x456 = 0x1456. + assertEquals("View visibility should be 0x1456", 0x1456, view.getSystemUiVisibility()); } - - @UiThreadTest - @Test - public void testRemoveVisibilityFlagView() { - final View view = createViewWithSystemUiVisibility(0x456); - SystemBarHelper.removeVisibilityFlag(view, 0x1400); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - // Check that result is 0x56, because 0x456 & ~0x1400 = 0x56. - assertEquals("View visibility should be 0x56", 0x56, view.getSystemUiVisibility()); - } + } + + @UiThreadTest + @Test + public void testRemoveVisibilityFlagView() { + final View view = createViewWithSystemUiVisibility(0x456); + SystemBarHelper.removeVisibilityFlag(view, 0x1400); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + // Check that result is 0x56, because 0x456 & ~0x1400 = 0x56. + assertEquals("View visibility should be 0x56", 0x56, view.getSystemUiVisibility()); } - - @UiThreadTest - @Test - public void testAddVisibilityFlagWindow() { - final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.addVisibilityFlag(window, 0x1400); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - // Check that result is 0x1456 = 0x1400 | 0x456. - assertEquals("View visibility should be 0x1456", 0x1456, - window.getAttributes().systemUiVisibility); - } + } + + @UiThreadTest + @Test + public void testAddVisibilityFlagWindow() { + final Window window = createWindowWithSystemUiVisibility(0x456); + SystemBarHelper.addVisibilityFlag(window, 0x1400); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + // Check that result is 0x1456 = 0x1400 | 0x456. + assertEquals( + "View visibility should be 0x1456", 0x1456, window.getAttributes().systemUiVisibility); } - - @UiThreadTest - @Test - public void testRemoveVisibilityFlagWindow() { - final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.removeVisibilityFlag(window, 0x1400); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - // Check that result is 0x56 = 0x456 & ~0x1400. - assertEquals("View visibility should be 0x56", 0x56, - window.getAttributes().systemUiVisibility); - } + } + + @UiThreadTest + @Test + public void testRemoveVisibilityFlagWindow() { + final Window window = createWindowWithSystemUiVisibility(0x456); + SystemBarHelper.removeVisibilityFlag(window, 0x1400); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + // Check that result is 0x56 = 0x456 & ~0x1400. + assertEquals( + "View visibility should be 0x56", 0x56, window.getAttributes().systemUiVisibility); } - - @UiThreadTest - @Test - public void testHideSystemBarsWindow() { - final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.hideSystemBars(window); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals("DEFAULT_IMMERSIVE_FLAGS should be added to window's systemUiVisibility", - DEFAULT_IMMERSIVE_FLAGS | 0x456, - window.getAttributes().systemUiVisibility); - assertEquals( - "DEFAULT_IMMERSIVE_FLAGS should be added to decorView's systemUiVisibility", - DEFAULT_IMMERSIVE_FLAGS | 0x456, - window.getDecorView().getSystemUiVisibility()); - assertEquals("Navigation bar should be transparent", window.getNavigationBarColor(), 0); - assertEquals("Status bar should be transparent", window.getStatusBarColor(), 0); - } + } + + @UiThreadTest + @Test + public void testHideSystemBarsWindow() { + final Window window = createWindowWithSystemUiVisibility(0x456); + SystemBarHelper.hideSystemBars(window); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals( + "DEFAULT_IMMERSIVE_FLAGS should be added to window's systemUiVisibility", + DEFAULT_IMMERSIVE_FLAGS | 0x456, + window.getAttributes().systemUiVisibility); + assertEquals( + "DEFAULT_IMMERSIVE_FLAGS should be added to decorView's systemUiVisibility", + DEFAULT_IMMERSIVE_FLAGS | 0x456, + window.getDecorView().getSystemUiVisibility()); + assertEquals("Navigation bar should be transparent", window.getNavigationBarColor(), 0); + assertEquals("Status bar should be transparent", window.getStatusBarColor(), 0); } - - @UiThreadTest - @Test - public void testShowSystemBarsWindow() { - final Window window = createWindowWithSystemUiVisibility(0x456); - Context context = new ContextThemeWrapper( - InstrumentationRegistry.getContext(), android.R.style.Theme); - SystemBarHelper.showSystemBars(window, context); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals( - "DEFAULT_IMMERSIVE_FLAGS should be removed from window's systemUiVisibility", - 0x456 & ~DEFAULT_IMMERSIVE_FLAGS, - window.getAttributes().systemUiVisibility); - assertEquals( - "DEFAULT_IMMERSIVE_FLAGS should be removed from decorView's systemUiVisibility", - 0x456 & ~DEFAULT_IMMERSIVE_FLAGS, - window.getDecorView().getSystemUiVisibility()); - assertEquals("Navigation bar should not be transparent", - window.getNavigationBarColor(), 0xff000000); - assertEquals("Status bar should not be transparent", - window.getStatusBarColor(), 0xff000000); - } + } + + @UiThreadTest + @Test + public void testShowSystemBarsWindow() { + final Window window = createWindowWithSystemUiVisibility(0x456); + Context context = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), android.R.style.Theme); + SystemBarHelper.showSystemBars(window, context); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals( + "DEFAULT_IMMERSIVE_FLAGS should be removed from window's systemUiVisibility", + 0x456 & ~DEFAULT_IMMERSIVE_FLAGS, + window.getAttributes().systemUiVisibility); + assertEquals( + "DEFAULT_IMMERSIVE_FLAGS should be removed from decorView's systemUiVisibility", + 0x456 & ~DEFAULT_IMMERSIVE_FLAGS, + window.getDecorView().getSystemUiVisibility()); + assertEquals( + "Navigation bar should not be transparent", window.getNavigationBarColor(), 0xff000000); + assertEquals("Status bar should not be transparent", window.getStatusBarColor(), 0xff000000); } - - @UiThreadTest - @Test - public void testHideSystemBarsNoInfiniteLoop() throws InterruptedException { - final TestWindow window = new TestWindow(InstrumentationRegistry.getContext(), null); - final HandlerThread thread = new HandlerThread("SystemBarHelperTest"); - thread.start(); - final Handler handler = new Handler(thread.getLooper()); - handler.post(new Runnable() { - @Override - public void run() { - SystemBarHelper.hideSystemBars(window); - } + } + + @UiThreadTest + @Test + public void testHideSystemBarsNoInfiniteLoop() throws InterruptedException { + final TestWindow window = new TestWindow(InstrumentationRegistry.getContext(), null); + final HandlerThread thread = new HandlerThread("SystemBarHelperTest"); + thread.start(); + final Handler handler = new Handler(thread.getLooper()); + handler.post( + new Runnable() { + @Override + public void run() { + SystemBarHelper.hideSystemBars(window); + } }); - SystemClock.sleep(500); // Wait for the looper to drain all the messages - thread.quit(); - // Initial peek + 3 retries = 4 tries total - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals("Peek decor view should give up after 4 tries", 4, - window.peekDecorViewCount); - } + SystemClock.sleep(500); // Wait for the looper to drain all the messages + thread.quit(); + // Initial peek + 3 retries = 4 tries total + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals("Peek decor view should give up after 4 tries", 4, window.peekDecorViewCount); } - - @UiThreadTest - @Test - public void testHideSystemBarsDialog() { - final Dialog dialog = new Dialog(InstrumentationRegistry.getContext()); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - final WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes(); - attrs.systemUiVisibility = 0x456; - dialog.getWindow().setAttributes(attrs); - } - - SystemBarHelper.hideSystemBars(dialog); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals("DIALOG_IMMERSIVE_FLAGS should be added to window's systemUiVisibility", - DIALOG_IMMERSIVE_FLAGS | 0x456, - dialog.getWindow().getAttributes().systemUiVisibility); - } + } + + @UiThreadTest + @Test + public void testHideSystemBarsDialog() { + final Dialog dialog = new Dialog(InstrumentationRegistry.getContext()); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + final WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes(); + attrs.systemUiVisibility = 0x456; + dialog.getWindow().setAttributes(attrs); } - @UiThreadTest - @Test - public void testSetBackButtonVisibleTrue() { - final Window window = createWindowWithSystemUiVisibility(STATUS_BAR_DISABLE_BACK | 0x456); - SystemBarHelper.setBackButtonVisible(window, true); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - assertThat(window.getAttributes().systemUiVisibility) - .named("window sysUiVisibility") - .isEqualTo(0x456); - assertThat(window.getDecorView().getSystemUiVisibility()) - .named("decor view sysUiVisibility") - .isEqualTo(0x456); - } + SystemBarHelper.hideSystemBars(dialog); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals( + "DIALOG_IMMERSIVE_FLAGS should be added to window's systemUiVisibility", + DIALOG_IMMERSIVE_FLAGS | 0x456, + dialog.getWindow().getAttributes().systemUiVisibility); } - - @UiThreadTest - @Test - public void testSetBackButtonVisibleFalse() { - final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.setBackButtonVisible(window, false); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - assertThat(window.getAttributes().systemUiVisibility) - .named("window sysUiVisibility") - .isEqualTo(0x456 | STATUS_BAR_DISABLE_BACK); - assertThat(window.getDecorView().getSystemUiVisibility()) - .named("decor view sysUiVisibility") - .isEqualTo(0x456 | STATUS_BAR_DISABLE_BACK); - } + } + + @UiThreadTest + @Test + public void testSetBackButtonVisibleTrue() { + final Window window = createWindowWithSystemUiVisibility(STATUS_BAR_DISABLE_BACK | 0x456); + SystemBarHelper.setBackButtonVisible(window, true); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + assertThat(window.getAttributes().systemUiVisibility) + .named("window sysUiVisibility") + .isEqualTo(0x456); + assertThat(window.getDecorView().getSystemUiVisibility()) + .named("decor view sysUiVisibility") + .isEqualTo(0x456); } - - private View createViewWithSystemUiVisibility(int vis) { - final View view = new View(InstrumentationRegistry.getContext()); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - view.setSystemUiVisibility(vis); - } - return view; + } + + @UiThreadTest + @Test + public void testSetBackButtonVisibleFalse() { + final Window window = createWindowWithSystemUiVisibility(0x456); + SystemBarHelper.setBackButtonVisible(window, false); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + assertThat(window.getAttributes().systemUiVisibility) + .named("window sysUiVisibility") + .isEqualTo(0x456 | STATUS_BAR_DISABLE_BACK); + assertThat(window.getDecorView().getSystemUiVisibility()) + .named("decor view sysUiVisibility") + .isEqualTo(0x456 | STATUS_BAR_DISABLE_BACK); } + } - private Window createWindowWithSystemUiVisibility(int vis) { - final Window window = new TestWindow(InstrumentationRegistry.getContext(), - createViewWithSystemUiVisibility(vis)); - if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - WindowManager.LayoutParams attrs = window.getAttributes(); - attrs.systemUiVisibility = vis; - window.setAttributes(attrs); - } - return window; + private View createViewWithSystemUiVisibility(int vis) { + final View view = new View(InstrumentationRegistry.getContext()); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + view.setSystemUiVisibility(vis); + } + return view; + } + + private Window createWindowWithSystemUiVisibility(int vis) { + final Window window = + new TestWindow(InstrumentationRegistry.getContext(), createViewWithSystemUiVisibility(vis)); + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.systemUiVisibility = vis; + window.setAttributes(attrs); } + return window; + } - private static class TestWindow extends MockWindow { + private static class TestWindow extends MockWindow { - private View mDecorView; - public int peekDecorViewCount = 0; + private View mDecorView; + public int peekDecorViewCount = 0; - private int mNavigationBarColor = -1; - private int mStatusBarColor = -1; + private int mNavigationBarColor = -1; + private int mStatusBarColor = -1; - TestWindow(Context context, View decorView) { - super(context); - mDecorView = decorView; - } + TestWindow(Context context, View decorView) { + super(context); + mDecorView = decorView; + } - @Override - public View getDecorView() { - return mDecorView; - } + @Override + public View getDecorView() { + return mDecorView; + } - @Override - public View peekDecorView() { - peekDecorViewCount++; - return mDecorView; - } + @Override + public View peekDecorView() { + peekDecorViewCount++; + return mDecorView; + } - @Override - public void setNavigationBarColor(int i) { - mNavigationBarColor = i; - } + @Override + public void setNavigationBarColor(int i) { + mNavigationBarColor = i; + } - @Override - public int getNavigationBarColor() { - return mNavigationBarColor; - } + @Override + public int getNavigationBarColor() { + return mNavigationBarColor; + } - @Override - public void setStatusBarColor(int i) { - mStatusBarColor = i; - } + @Override + public void setStatusBarColor(int i) { + mStatusBarColor = i; + } - @Override - public int getStatusBarColor() { - return mStatusBarColor; - } + @Override + public int getStatusBarColor() { + return mStatusBarColor; } + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestHelper.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestHelper.java index 6910513..918d63a 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestHelper.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/util/DrawingTestHelper.java @@ -24,69 +24,69 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.support.test.InstrumentationRegistry; +import androidx.annotation.StyleRes; import android.view.View; import android.view.View.MeasureSpec; - -import androidx.annotation.StyleRes; +import android.support.test.InstrumentationRegistry; public class DrawingTestHelper { - /** - * Creates an activity of which to inflate views and drawables for drawing tests. This method - * will return an instance of AppCompatActivity which allows testing of drawing behavior - * injected by support libraries (like drawable tinting) as well. - */ - public static Activity createCanvasActivity(@StyleRes int theme) - throws IllegalAccessException, InstantiationException { - final Context context = InstrumentationRegistry.getTargetContext(); - final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + /** + * Creates an activity of which to inflate views and drawables for drawing tests. This method will + * return an instance of AppCompatActivity which allows testing of drawing behavior injected by + * support libraries (like drawable tinting) as well. + */ + public static Activity createCanvasActivity(@StyleRes int theme) + throws IllegalAccessException, InstantiationException { + final Context context = InstrumentationRegistry.getTargetContext(); + final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - final Intent intent = new Intent(context, DrawingTestActivity.class); - final Activity activity = instrumentation.newActivity( - DrawingTestActivity.class, - context, - null, /* token */ - new Application(), - intent, - new ActivityInfo(), - "", /* title */ - null, /* parent */ - null, /* id */ - null /* lastNonConfigurationInstance */); - instrumentation.callActivityOnCreate(activity, null); - activity.setTheme(theme); - return activity; - } + final Intent intent = new Intent(context, DrawingTestActivity.class); + final Activity activity = + instrumentation.newActivity( + DrawingTestActivity.class, + context, + null, /* token */ + new Application(), + intent, + new ActivityInfo(), + "", /* title */ + null, /* parent */ + null, /* id */ + null /* lastNonConfigurationInstance */); + instrumentation.callActivityOnCreate(activity, null); + activity.setTheme(theme); + return activity; + } - private final int mWidth; - private final int mHeight; - private final Canvas mCanvas; - private final Bitmap mBitmap; + private final int mWidth; + private final int mHeight; + private final Canvas mCanvas; + private final Bitmap mBitmap; - public DrawingTestHelper(int width, int height) { - mWidth = width; - mHeight = height; + public DrawingTestHelper(int width, int height) { + mWidth = width; + mHeight = height; - mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(mBitmap); - } + mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBitmap); + } - public void drawView(View view) { - view.measure( - MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY)); - view.layout(0, 0, mWidth, mHeight); - view.draw(mCanvas); - } + public void drawView(View view) { + view.measure( + MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY)); + view.layout(0, 0, mWidth, mHeight); + view.draw(mCanvas); + } - public int[] getPixels() { - int[] out = new int[mWidth * mHeight]; - mBitmap.getPixels(out, 0, mWidth, 0, 0, mWidth, mHeight); - return out; - } + public int[] getPixels() { + int[] out = new int[mWidth * mHeight]; + mBitmap.getPixels(out, 0, mWidth, 0, 0, mWidth, mHeight); + return out; + } - public int getPixel(int x, int y) { - return mBitmap.getPixel(x, y); - } + public int getPixel(int x, int y) { + return mBitmap.getPixel(x, y); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/util/MockWindow.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/util/MockWindow.java index 7af20eb..1e096eb 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/util/MockWindow.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/util/MockWindow.java @@ -21,6 +21,7 @@ import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import androidx.annotation.NonNull; import android.view.InputQueue; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -30,252 +31,250 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; -import androidx.annotation.NonNull; - public class MockWindow extends Window { - public MockWindow(Context context) { - super(context); - } - - @Override - public void takeSurface(SurfaceHolder.Callback2 callback2) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void takeInputQueue(InputQueue.Callback callback) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean isFloating() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setContentView(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setContentView(View view) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setContentView(View view, ViewGroup.LayoutParams layoutParams) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void addContentView(View view, ViewGroup.LayoutParams layoutParams) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public View getCurrentFocus() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @NonNull - @Override - public LayoutInflater getLayoutInflater() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setTitle(CharSequence charSequence) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setTitleColor(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void openPanel(int i, KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void closePanel(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void togglePanel(int i, KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void invalidatePanelMenu(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean performPanelShortcut(int i, int i1, KeyEvent keyEvent, int i2) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean performPanelIdentifierAction(int i, int i1, int i2) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void closeAllPanels() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean performContextMenuIdentifierAction(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void onConfigurationChanged(Configuration configuration) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setBackgroundDrawable(Drawable drawable) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureDrawableResource(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureDrawableUri(int i, Uri uri) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureDrawable(int i, Drawable drawable) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureDrawableAlpha(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setFeatureInt(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void takeKeyEvents(boolean b) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchKeyEvent(KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchKeyShortcutEvent(KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchTouchEvent(MotionEvent motionEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchTrackballEvent(MotionEvent motionEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean superDispatchGenericMotionEvent(MotionEvent motionEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public View getDecorView() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public View peekDecorView() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public Bundle saveHierarchyState() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void restoreHierarchyState(Bundle bundle) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - protected void onActive() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setChildDrawable(int i, Drawable drawable) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setChildInt(int i, int i1) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public boolean isShortcutKey(int i, KeyEvent keyEvent) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setVolumeControlStream(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public int getVolumeControlStream() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public int getStatusBarColor() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setStatusBarColor(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public int getNavigationBarColor() { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setNavigationBarColor(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setDecorCaptionShade(int i) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } - - @Override - public void setResizingCaptionDrawable(Drawable drawable) { - throw new UnsupportedOperationException("Unexpected method call on mock"); - } + public MockWindow(Context context) { + super(context); + } + + @Override + public void takeSurface(SurfaceHolder.Callback2 callback2) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void takeInputQueue(InputQueue.Callback callback) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean isFloating() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setContentView(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setContentView(View view) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams layoutParams) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams layoutParams) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public View getCurrentFocus() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @NonNull + @Override + public LayoutInflater getLayoutInflater() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setTitle(CharSequence charSequence) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setTitleColor(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void openPanel(int i, KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void closePanel(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void togglePanel(int i, KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void invalidatePanelMenu(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean performPanelShortcut(int i, int i1, KeyEvent keyEvent, int i2) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean performPanelIdentifierAction(int i, int i1, int i2) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void closeAllPanels() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean performContextMenuIdentifierAction(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void onConfigurationChanged(Configuration configuration) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setBackgroundDrawable(Drawable drawable) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureDrawableResource(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureDrawableUri(int i, Uri uri) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureDrawable(int i, Drawable drawable) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureDrawableAlpha(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setFeatureInt(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void takeKeyEvents(boolean b) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchKeyEvent(KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchKeyShortcutEvent(KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchTouchEvent(MotionEvent motionEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchTrackballEvent(MotionEvent motionEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean superDispatchGenericMotionEvent(MotionEvent motionEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public View getDecorView() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public View peekDecorView() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public Bundle saveHierarchyState() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void restoreHierarchyState(Bundle bundle) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + protected void onActive() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setChildDrawable(int i, Drawable drawable) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setChildInt(int i, int i1) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public boolean isShortcutKey(int i, KeyEvent keyEvent) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setVolumeControlStream(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public int getVolumeControlStream() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public int getStatusBarColor() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setStatusBarColor(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public int getNavigationBarColor() { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setNavigationBarColor(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setDecorCaptionShade(int i) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } + + @Override + public void setResizingCaptionDrawable(Drawable drawable) { + throw new UnsupportedOperationException("Unexpected method call on mock"); + } } diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/util/FallbackThemeWrapperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/util/FallbackThemeWrapperTest.java index d492765..99d997b 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/util/FallbackThemeWrapperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/util/FallbackThemeWrapperTest.java @@ -20,13 +20,11 @@ import static org.junit.Assert.assertEquals; import android.content.Context; import android.content.res.TypedArray; +import android.view.ContextThemeWrapper; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.ContextThemeWrapper; - import com.android.setupwizardlib.test.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,37 +33,35 @@ import org.junit.runner.RunWith; @SmallTest public class FallbackThemeWrapperTest { - private FallbackThemeWrapper mThemedContext; + private FallbackThemeWrapper mThemedContext; - @Before - public void setUp() { - Context baseContext = new ContextThemeWrapper( - InstrumentationRegistry.getContext(), - R.style.TestBaseTheme); - mThemedContext = new FallbackThemeWrapper(baseContext, R.style.TestFallbackTheme); - } + @Before + public void setUp() { + Context baseContext = + new ContextThemeWrapper(InstrumentationRegistry.getContext(), R.style.TestBaseTheme); + mThemedContext = new FallbackThemeWrapper(baseContext, R.style.TestFallbackTheme); + } - @Test - public void testThemeValueOnlyInBase() { - final TypedArray a = - mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.background}); - assertEquals(0xffff0000, a.getColor(0, 0)); - a.recycle(); - } + @Test + public void testThemeValueOnlyInBase() { + final TypedArray a = + mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.background}); + assertEquals(0xffff0000, a.getColor(0, 0)); + a.recycle(); + } - @Test - public void testThemeValueOnlyInFallback() { - final TypedArray a = - mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.foreground}); - assertEquals(0xff0000ff, a.getColor(0, 0)); - a.recycle(); - } + @Test + public void testThemeValueOnlyInFallback() { + final TypedArray a = + mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.foreground}); + assertEquals(0xff0000ff, a.getColor(0, 0)); + a.recycle(); + } - @Test - public void testThemeValueInBoth() { - final TypedArray a = - mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.theme}); - assertEquals(R.style.TestBaseTheme, a.getResourceId(0, 0)); - a.recycle(); - } + @Test + public void testThemeValueInBoth() { + final TypedArray a = mThemedContext.obtainStyledAttributes(new int[] {android.R.attr.theme}); + assertEquals(R.style.TestBaseTheme, a.getResourceId(0, 0)); + a.recycle(); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java b/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java index 2a4a8c0..ccc6ce7 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java @@ -34,21 +34,18 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import androidx.annotation.IdRes; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; - -import androidx.annotation.IdRes; - import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.template.ColoredHeaderMixin; import com.android.setupwizardlib.template.HeaderMixin; import com.android.setupwizardlib.template.IconMixin; import com.android.setupwizardlib.template.ProgressBarMixin; import com.android.setupwizardlib.view.StatusBarBackgroundLayout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,306 +53,317 @@ import org.robolectric.Robolectric; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class GlifLayoutTest { - private Context mContext; - - @Before - public void setUp() throws Exception { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - } - - @Test - public void testDefaultTemplate() { - GlifLayout layout = new GlifLayout(mContext); - assertDefaultTemplateInflated(layout); - } - - @Test - public void testSetHeaderText() { - GlifLayout layout = new GlifLayout(mContext); - TextView title = (TextView) layout.findViewById(R.id.suw_layout_title); - layout.setHeaderText("Abracadabra"); - assertEquals("Header text should be \"Abracadabra\"", "Abracadabra", title.getText()); - } - - @Test - public void testAddView() { - @IdRes int testViewId = 123456; - GlifLayout layout = new GlifLayout(mContext); - TextView tv = new TextView(mContext); - tv.setId(testViewId); - layout.addView(tv); - assertDefaultTemplateInflated(layout); - View view = layout.findViewById(testViewId); - assertSame("The view added should be the same text view", tv, view); - } - - @Test - public void testGetScrollView() { - GlifLayout layout = new GlifLayout(mContext); - assertNotNull("Get scroll view should not be null with default template", - layout.getScrollView()); - } - - @Test - public void testSetPrimaryColor() { - GlifLayout layout = new GlifLayout(mContext); - layout.setProgressBarShown(true); - layout.setPrimaryColor(ColorStateList.valueOf(Color.RED)); - assertEquals("Primary color should be red", - ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) layout.findViewById(R.id.suw_layout_progress); - assertEquals("Progress bar should be tinted red", - ColorStateList.valueOf(Color.RED), progressBar.getIndeterminateTintList()); - assertEquals("Determinate progress bar should also be tinted red", - ColorStateList.valueOf(Color.RED), progressBar.getProgressBackgroundTintList()); - } - } - - @Config(qualifiers = "sw600dp") - @Test - public void testSetPrimaryColorTablet() { - GlifLayout layout = new GlifLayout(mContext); - layout.setProgressBarShown(true); - layout.setPrimaryColor(ColorStateList.valueOf(Color.RED)); - assertEquals("Primary color should be red", - ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - ProgressBar progressBar = (ProgressBar) layout.findViewById(R.id.suw_layout_progress); - assertEquals("Progress bar should be tinted red", - ColorStateList.valueOf(Color.RED), progressBar.getIndeterminateTintList()); - assertEquals("Determinate progress bar should also be tinted red", - ColorStateList.valueOf(Color.RED), progressBar.getProgressBackgroundTintList()); - } - - assertEquals(Color.RED, ((GlifPatternDrawable) getTabletBackground(layout)).getColor()); - } - - @Test - public void testSetBackgroundBaseColor() { - GlifLayout layout = new GlifLayout(mContext); - layout.setPrimaryColor(ColorStateList.valueOf(Color.BLUE)); - layout.setBackgroundBaseColor(ColorStateList.valueOf(Color.RED)); - - assertEquals(Color.RED, ((GlifPatternDrawable) getPhoneBackground(layout)).getColor()); - assertEquals(Color.RED, layout.getBackgroundBaseColor().getDefaultColor()); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testSetBackgroundBaseColorTablet() { - GlifLayout layout = new GlifLayout(mContext); - layout.setPrimaryColor(ColorStateList.valueOf(Color.BLUE)); - layout.setBackgroundBaseColor(ColorStateList.valueOf(Color.RED)); - - assertEquals(Color.RED, ((GlifPatternDrawable) getTabletBackground(layout)).getColor()); - assertEquals(Color.RED, layout.getBackgroundBaseColor().getDefaultColor()); - } - - @Test - public void testSetBackgroundPatternedTrue() { - GlifLayout layout = new GlifLayout(mContext); - layout.setBackgroundPatterned(true); - - assertThat(getPhoneBackground(layout), instanceOf(GlifPatternDrawable.class)); - assertTrue("Background should be patterned", layout.isBackgroundPatterned()); - } - - @Test - public void testSetBackgroundPatternedFalse() { - GlifLayout layout = new GlifLayout(mContext); - layout.setBackgroundPatterned(false); - - assertThat(getPhoneBackground(layout), instanceOf(ColorDrawable.class)); - assertFalse("Background should not be patterned", layout.isBackgroundPatterned()); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testSetBackgroundPatternedTrueTablet() { - GlifLayout layout = new GlifLayout(mContext); - layout.setBackgroundPatterned(true); - - assertThat(getTabletBackground(layout), instanceOf(GlifPatternDrawable.class)); - assertTrue("Background should be patterned", layout.isBackgroundPatterned()); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testSetBackgroundPatternedFalseTablet() { - GlifLayout layout = new GlifLayout(mContext); - layout.setBackgroundPatterned(false); - - assertThat(getTabletBackground(layout), instanceOf(ColorDrawable.class)); - assertFalse("Background should not be patterned", layout.isBackgroundPatterned()); - } - - @Test - public void testNonGlifTheme() { - mContext = new ContextThemeWrapper(application, android.R.style.Theme); - new GlifLayout(mContext); - // Inflating with a non-GLIF theme should not crash - } - - @Test - public void testPeekProgressBarNull() { - GlifLayout layout = new GlifLayout(mContext); - assertNull("PeekProgressBar should return null initially", layout.peekProgressBar()); - } - - @Test - public void testPeekProgressBar() { - GlifLayout layout = new GlifLayout(mContext); - layout.setProgressBarShown(true); - assertNotNull("Peek progress bar should return the bar after setProgressBarShown(true)", - layout.peekProgressBar()); - } - - @Test - public void testMixins() { - GlifLayout layout = new GlifLayout(mContext); - final HeaderMixin header = layout.getMixin(HeaderMixin.class); - assertTrue("Header should be instance of ColoredHeaderMixin. " - + "Found " + header.getClass() + " instead.", header instanceof ColoredHeaderMixin); - - assertNotNull("GlifLayout should have icon mixin", layout.getMixin(IconMixin.class)); - assertNotNull("GlifLayout should have progress bar mixin", - layout.getMixin(ProgressBarMixin.class)); - } - - @Test - public void testInflateFooter() { - GlifLayout layout = new GlifLayout(mContext); - - final View view = layout.inflateFooter(android.R.layout.simple_list_item_1); - assertEquals(android.R.id.text1, view.getId()); - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testInflateFooterTablet() { - testInflateFooter(); - } - - @Test - public void testInflateFooterBlankTemplate() { - GlifLayout layout = new GlifLayout(mContext, R.layout.suw_glif_blank_template); - - final View view = layout.inflateFooter(android.R.layout.simple_list_item_1); - assertEquals(android.R.id.text1, view.getId()); - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testInflateFooterBlankTemplateTablet() { - testInflateFooterBlankTemplate(); - } - - @Test - public void testFooterXml() { - GlifLayout layout = new GlifLayout( - mContext, - Robolectric.buildAttributeSet() - .addAttribute(R.attr.suwFooter, "@android:layout/simple_list_item_1") - .build()); - - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Test - public void inflateStickyHeader_shouldAddViewToLayout() { - GlifLayout layout = new GlifLayout(mContext); - - final View view = layout.inflateStickyHeader(android.R.layout.simple_list_item_1); - assertEquals(android.R.id.text1, view.getId()); - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Config(qualifiers = "sw600dp") - @Test - public void inflateStickyHeader_whenOnTablets_shouldAddViewToLayout() { - inflateStickyHeader_shouldAddViewToLayout(); - } - - @Test - public void inflateStickyHeader_whenInXml_shouldAddViewToLayout() { - GlifLayout layout = new GlifLayout( - mContext, - Robolectric.buildAttributeSet() - .addAttribute(R.attr.suwStickyHeader, "@android:layout/simple_list_item_1") - .build()); - - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Test - public void inflateStickyHeader_whenOnBlankTemplate_shouldAddViewToLayout() { - GlifLayout layout = new GlifLayout(mContext, R.layout.suw_glif_blank_template); - - final View view = layout.inflateStickyHeader(android.R.layout.simple_list_item_1); - assertEquals(android.R.id.text1, view.getId()); - assertNotNull(layout.findViewById(android.R.id.text1)); - } - - @Config(qualifiers = "sw600dp") - @Test - public void inflateStickyHeader_whenOnBlankTemplateTablet_shouldAddViewToLayout() { - inflateStickyHeader_whenOnBlankTemplate_shouldAddViewToLayout(); - } - - @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.NEWEST_SDK) - @Test - public void createFromXml_shouldSetLayoutFullscreen_whenLayoutFullscreenIsNotSet() { - GlifLayout layout = new GlifLayout( - mContext, - Robolectric.buildAttributeSet() - .build()); - if (VERSION.SDK_INT >= VERSION_CODES.M) { - assertEquals( - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN, - layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - } - - @Test - public void createFromXml_shouldNotSetLayoutFullscreen_whenLayoutFullscreenIsFalse() { - GlifLayout layout = new GlifLayout( - mContext, - Robolectric.buildAttributeSet() - .addAttribute(R.attr.suwLayoutFullscreen, "false") - .build()); - - assertEquals( - 0, - layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - - private Drawable getPhoneBackground(GlifLayout layout) { - final StatusBarBackgroundLayout patternBg = - (StatusBarBackgroundLayout) layout.findManagedViewById(R.id.suw_pattern_bg); - return patternBg.getStatusBarBackground(); - } - - private Drawable getTabletBackground(GlifLayout layout) { - final View patternBg = layout.findManagedViewById(R.id.suw_pattern_bg); - return patternBg.getBackground(); - } - - private void assertDefaultTemplateInflated(GlifLayout layout) { - View title = layout.findViewById(R.id.suw_layout_title); - assertNotNull("@id/suw_layout_title should not be null", title); - - View icon = layout.findViewById(R.id.suw_layout_icon); - assertNotNull("@id/suw_layout_icon should not be null", icon); - - View scrollView = layout.findViewById(R.id.suw_scroll_view); - assertTrue("@id/suw_scroll_view should be a ScrollView", scrollView instanceof ScrollView); - } + private Context context; + + @Before + public void setUp() throws Exception { + context = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + } + + @Test + public void testDefaultTemplate() { + GlifLayout layout = new GlifLayout(context); + assertDefaultTemplateInflated(layout); + } + + @Test + public void testSetHeaderText() { + GlifLayout layout = new GlifLayout(context); + TextView title = (TextView) layout.findViewById(R.id.suw_layout_title); + layout.setHeaderText("Abracadabra"); + assertEquals("Header text should be \"Abracadabra\"", "Abracadabra", title.getText()); + } + + @Test + public void testAddView() { + @IdRes int testViewId = 123456; + GlifLayout layout = new GlifLayout(context); + TextView tv = new TextView(context); + tv.setId(testViewId); + layout.addView(tv); + assertDefaultTemplateInflated(layout); + View view = layout.findViewById(testViewId); + assertSame("The view added should be the same text view", tv, view); + } + + @Test + public void testGetScrollView() { + GlifLayout layout = new GlifLayout(context); + assertNotNull( + "Get scroll view should not be null with default template", layout.getScrollView()); + } + + @Test + public void testSetPrimaryColor() { + GlifLayout layout = new GlifLayout(context); + layout.setProgressBarShown(true); + layout.setPrimaryColor(ColorStateList.valueOf(Color.RED)); + assertEquals( + "Primary color should be red", ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = (ProgressBar) layout.findViewById(R.id.suw_layout_progress); + assertEquals( + "Progress bar should be tinted red", + ColorStateList.valueOf(Color.RED), + progressBar.getIndeterminateTintList()); + assertEquals( + "Determinate progress bar should also be tinted red", + ColorStateList.valueOf(Color.RED), + progressBar.getProgressBackgroundTintList()); + } + } + + @Config(qualifiers = "sw600dp") + @Test + public void testSetPrimaryColorTablet() { + GlifLayout layout = new GlifLayout(context); + layout.setProgressBarShown(true); + layout.setPrimaryColor(ColorStateList.valueOf(Color.RED)); + assertEquals( + "Primary color should be red", ColorStateList.valueOf(Color.RED), layout.getPrimaryColor()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ProgressBar progressBar = (ProgressBar) layout.findViewById(R.id.suw_layout_progress); + assertEquals( + "Progress bar should be tinted red", + ColorStateList.valueOf(Color.RED), + progressBar.getIndeterminateTintList()); + assertEquals( + "Determinate progress bar should also be tinted red", + ColorStateList.valueOf(Color.RED), + progressBar.getProgressBackgroundTintList()); + } + + assertEquals(Color.RED, ((GlifPatternDrawable) getTabletBackground(layout)).getColor()); + } + + @Test + public void testSetBackgroundBaseColor() { + GlifLayout layout = new GlifLayout(context); + layout.setPrimaryColor(ColorStateList.valueOf(Color.BLUE)); + layout.setBackgroundBaseColor(ColorStateList.valueOf(Color.RED)); + + assertEquals(Color.RED, ((GlifPatternDrawable) getPhoneBackground(layout)).getColor()); + assertEquals(Color.RED, layout.getBackgroundBaseColor().getDefaultColor()); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testSetBackgroundBaseColorTablet() { + GlifLayout layout = new GlifLayout(context); + layout.setPrimaryColor(ColorStateList.valueOf(Color.BLUE)); + layout.setBackgroundBaseColor(ColorStateList.valueOf(Color.RED)); + + assertEquals(Color.RED, ((GlifPatternDrawable) getTabletBackground(layout)).getColor()); + assertEquals(Color.RED, layout.getBackgroundBaseColor().getDefaultColor()); + } + + @Test + public void testSetBackgroundPatternedTrue() { + GlifLayout layout = new GlifLayout(context); + layout.setBackgroundPatterned(true); + + assertThat(getPhoneBackground(layout), instanceOf(GlifPatternDrawable.class)); + assertTrue("Background should be patterned", layout.isBackgroundPatterned()); + } + + @Test + public void testSetBackgroundPatternedFalse() { + GlifLayout layout = new GlifLayout(context); + layout.setBackgroundPatterned(false); + + assertThat(getPhoneBackground(layout), instanceOf(ColorDrawable.class)); + assertFalse("Background should not be patterned", layout.isBackgroundPatterned()); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testSetBackgroundPatternedTrueTablet() { + GlifLayout layout = new GlifLayout(context); + layout.setBackgroundPatterned(true); + + assertThat(getTabletBackground(layout), instanceOf(GlifPatternDrawable.class)); + assertTrue("Background should be patterned", layout.isBackgroundPatterned()); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testSetBackgroundPatternedFalseTablet() { + GlifLayout layout = new GlifLayout(context); + layout.setBackgroundPatterned(false); + + assertThat(getTabletBackground(layout), instanceOf(ColorDrawable.class)); + assertFalse("Background should not be patterned", layout.isBackgroundPatterned()); + } + + @Test + public void testNonGlifTheme() { + context = new ContextThemeWrapper(application, android.R.style.Theme); + new GlifLayout(context); + // Inflating with a non-GLIF theme should not crash + } + + @Test + public void testPeekProgressBarNull() { + GlifLayout layout = new GlifLayout(context); + assertNull("PeekProgressBar should return null initially", layout.peekProgressBar()); + } + + @Test + public void testPeekProgressBar() { + GlifLayout layout = new GlifLayout(context); + layout.setProgressBarShown(true); + assertNotNull( + "Peek progress bar should return the bar after setProgressBarShown(true)", + layout.peekProgressBar()); + } + + @Test + public void testMixins() { + GlifLayout layout = new GlifLayout(context); + final HeaderMixin header = layout.getMixin(HeaderMixin.class); + assertTrue( + "Header should be instance of ColoredHeaderMixin. " + + "Found " + + header.getClass() + + " instead.", + header instanceof ColoredHeaderMixin); + + assertNotNull("GlifLayout should have icon mixin", layout.getMixin(IconMixin.class)); + assertNotNull( + "GlifLayout should have progress bar mixin", layout.getMixin(ProgressBarMixin.class)); + } + + @Test + public void testInflateFooter() { + GlifLayout layout = new GlifLayout(context); + + final View view = layout.inflateFooter(android.R.layout.simple_list_item_1); + assertEquals(android.R.id.text1, view.getId()); + assertNotNull(layout.findViewById(android.R.id.text1)); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testInflateFooterTablet() { + testInflateFooter(); + } + + @Test + public void testInflateFooterBlankTemplate() { + GlifLayout layout = new GlifLayout(context, R.layout.suw_glif_blank_template); + + final View view = layout.inflateFooter(android.R.layout.simple_list_item_1); + assertEquals(android.R.id.text1, view.getId()); + assertNotNull(layout.findViewById(android.R.id.text1)); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testInflateFooterBlankTemplateTablet() { + testInflateFooterBlankTemplate(); + } + + @Test + public void testFooterXml() { + GlifLayout layout = + new GlifLayout( + context, + Robolectric.buildAttributeSet() + .addAttribute(R.attr.suwFooter, "@android:layout/simple_list_item_1") + .build()); + + assertNotNull(layout.findViewById(android.R.id.text1)); + } + + @Test + public void inflateStickyHeader_shouldAddViewToLayout() { + GlifLayout layout = new GlifLayout(context); + + final View view = layout.inflateStickyHeader(android.R.layout.simple_list_item_1); + assertEquals(android.R.id.text1, view.getId()); + assertNotNull(layout.findViewById(android.R.id.text1)); + } + + @Config(qualifiers = "sw600dp") + @Test + public void inflateStickyHeader_whenOnTablets_shouldAddViewToLayout() { + inflateStickyHeader_shouldAddViewToLayout(); + } + + @Test + public void inflateStickyHeader_whenInXml_shouldAddViewToLayout() { + GlifLayout layout = + new GlifLayout( + context, + Robolectric.buildAttributeSet() + .addAttribute(R.attr.suwStickyHeader, "@android:layout/simple_list_item_1") + .build()); + + assertNotNull(layout.findViewById(android.R.id.text1)); + } + + @Test + public void inflateStickyHeader_whenOnBlankTemplate_shouldAddViewToLayout() { + GlifLayout layout = new GlifLayout(context, R.layout.suw_glif_blank_template); + + final View view = layout.inflateStickyHeader(android.R.layout.simple_list_item_1); + assertEquals(android.R.id.text1, view.getId()); + assertNotNull(layout.findViewById(android.R.id.text1)); + } + + @Config(qualifiers = "sw600dp") + @Test + public void inflateStickyHeader_whenOnBlankTemplateTablet_shouldAddViewToLayout() { + inflateStickyHeader_whenOnBlankTemplate_shouldAddViewToLayout(); + } + + @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.NEWEST_SDK) + @Test + public void createFromXml_shouldSetLayoutFullscreen_whenLayoutFullscreenIsNotSet() { + GlifLayout layout = new GlifLayout(context, Robolectric.buildAttributeSet().build()); + if (VERSION.SDK_INT >= VERSION_CODES.M) { + assertEquals( + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN, + layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + } + + @Test + public void createFromXml_shouldNotSetLayoutFullscreen_whenLayoutFullscreenIsFalse() { + GlifLayout layout = + new GlifLayout( + context, + Robolectric.buildAttributeSet() + .addAttribute(R.attr.suwLayoutFullscreen, "false") + .build()); + + assertEquals(0, layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + + private Drawable getPhoneBackground(GlifLayout layout) { + final StatusBarBackgroundLayout patternBg = + (StatusBarBackgroundLayout) layout.findManagedViewById(R.id.suw_pattern_bg); + return patternBg.getStatusBarBackground(); + } + + private Drawable getTabletBackground(GlifLayout layout) { + final View patternBg = layout.findManagedViewById(R.id.suw_pattern_bg); + return patternBg.getBackground(); + } + + private void assertDefaultTemplateInflated(GlifLayout layout) { + View title = layout.findViewById(R.id.suw_layout_title); + assertNotNull("@id/suw_layout_title should not be null", title); + + View icon = layout.findViewById(R.id.suw_layout_icon); + assertNotNull("@id/suw_layout_icon should not be null", icon); + + View scrollView = layout.findViewById(R.id.suw_scroll_view); + assertTrue("@id/suw_scroll_view should be a ScrollView", scrollView instanceof ScrollView); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java b/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java index aa2cce3..4b366e3 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java @@ -23,9 +23,7 @@ import static org.robolectric.RuntimeEnvironment.application; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; - import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,85 +32,84 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) @RunWith(SuwLibRobolectricTestRunner.class) public class ConsecutiveTapsGestureDetectorTest { - @Mock - private ConsecutiveTapsGestureDetector.OnConsecutiveTapsListener mListener; + @Mock private ConsecutiveTapsGestureDetector.OnConsecutiveTapsListener listener; - private ConsecutiveTapsGestureDetector mDetector; - private int mSlop; - private int mTapTimeout; + private ConsecutiveTapsGestureDetector detector; + private int slop; + private int tapTimeout; - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); - View view = new View(application); - view.measure(500, 500); - view.layout(0, 0, 500, 500); - mDetector = new ConsecutiveTapsGestureDetector(mListener, view); + View view = new View(application); + view.measure(500, 500); + view.layout(0, 0, 500, 500); + detector = new ConsecutiveTapsGestureDetector(listener, view); - mSlop = ViewConfiguration.get(application).getScaledDoubleTapSlop(); - mTapTimeout = ViewConfiguration.getDoubleTapTimeout(); - } + slop = ViewConfiguration.get(application).getScaledDoubleTapSlop(); + tapTimeout = ViewConfiguration.getDoubleTapTimeout(); + } - @Test - public void onTouchEvent_shouldTriggerCallbackOnFourTaps() { - InOrder inOrder = inOrder(mListener); + @Test + public void onTouchEvent_shouldTriggerCallbackOnFourTaps() { + InOrder inOrder = inOrder(listener); - tap(0, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(0, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(100, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + tap(100, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); - tap(200, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(3)); + tap(200, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(3)); - tap(300, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(4)); - } + tap(300, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(4)); + } - @Test - public void onTouchEvent_tapOnDifferentLocation_shouldResetCounter() { - InOrder inOrder = inOrder(mListener); + @Test + public void onTouchEvent_tapOnDifferentLocation_shouldResetCounter() { + InOrder inOrder = inOrder(listener); - tap(0, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(0, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(100, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + tap(100, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); - tap(200, 25f + mSlop * 2, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(200, 25f + slop * 2, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(300, 25f + mSlop * 2, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); - } + tap(300, 25f + slop * 2, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); + } - @Test - public void onTouchEvent_tapAfterTimeout_shouldResetCounter() { - InOrder inOrder = inOrder(mListener); + @Test + public void onTouchEvent_tapAfterTimeout_shouldResetCounter() { + InOrder inOrder = inOrder(listener); - tap(0, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(0, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(100, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + tap(100, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); - tap(200 + mTapTimeout, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + tap(200 + tapTimeout, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(1)); - tap(300 + mTapTimeout, 25f, 25f); - inOrder.verify(mListener).onConsecutiveTaps(eq(2)); - } + tap(300 + tapTimeout, 25f, 25f); + inOrder.verify(listener).onConsecutiveTaps(eq(2)); + } - private void tap(int timeMillis, float x, float y) { - mDetector.onTouchEvent( - MotionEvent.obtain(timeMillis, timeMillis, MotionEvent.ACTION_DOWN, x, y, 0)); - mDetector.onTouchEvent( - MotionEvent.obtain(timeMillis, timeMillis + 10, MotionEvent.ACTION_UP, x, y, 0)); - } + private void tap(int timeMillis, float x, float y) { + detector.onTouchEvent( + MotionEvent.obtain(timeMillis, timeMillis, MotionEvent.ACTION_DOWN, x, y, 0)); + detector.onTouchEvent( + MotionEvent.obtain(timeMillis, timeMillis + 10, MotionEvent.ACTION_UP, x, y, 0)); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java b/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java index 40e5da8..bfd5270 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java @@ -38,145 +38,145 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; import android.widget.LinearLayout; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.items.ButtonItem.OnClickListener; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class ButtonItemTest { - private ViewGroup mParent; - private Context mContext; - - @Before - public void setUp() { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - mParent = new LinearLayout(mContext); - } - - @Test - public void testDefaultItem() { - ButtonItem item = new ButtonItem(); - - assertTrue("ButtonItem should be enabled by default", item.isEnabled()); - assertEquals("ButtonItem should return count = 0", 0, item.getCount()); - assertEquals("ButtonItem should return layout resource = 0", 0, item.getLayoutResource()); - assertEquals("Default theme should be @style/SuwButtonItem", R.style.SuwButtonItem, - item.getTheme()); - assertNull("Default text should be null", item.getText()); - } - - @Test - public void testOnBindView() { - ButtonItem item = new ButtonItem(); - - try { - item.onBindView(new View(mContext)); - fail("Calling onBindView on ButtonItem should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException e) { - // pass - } - } - - @Test - public void testCreateButton() { - TestButtonItem item = new TestButtonItem(); - final Button button = item.createButton(mParent); - - assertTrue("Default button should be enabled", button.isEnabled()); - assertTrue("Default button text should be empty", TextUtils.isEmpty(button.getText())); - } - - @Test - public void testButtonItemSetsItsId() { - TestButtonItem item = new TestButtonItem(); - final int id = 12345; - item.setId(id); - - assertEquals("Button's id should be set", item.createButton(mParent).getId(), id); - } - - @Test - public void testCreateButtonTwice() { - TestButtonItem item = new TestButtonItem(); - final Button button = item.createButton(mParent); - - FrameLayout frameLayout = new FrameLayout(mContext); - frameLayout.addView(button); - - final Button button2 = item.createButton(mParent); - assertSame("createButton should be reused", button, button2); - assertNull("Should be removed from parent after createButton", button2.getParent()); + private ViewGroup parent; + private Context context; + + @Before + public void setUp() { + context = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + parent = new LinearLayout(context); + } + + @Test + public void testDefaultItem() { + ButtonItem item = new ButtonItem(); + + assertTrue("ButtonItem should be enabled by default", item.isEnabled()); + assertEquals("ButtonItem should return count = 0", 0, item.getCount()); + assertEquals("ButtonItem should return layout resource = 0", 0, item.getLayoutResource()); + assertEquals( + "Default theme should be @style/SuwButtonItem", R.style.SuwButtonItem, item.getTheme()); + assertNull("Default text should be null", item.getText()); + } + + @Test + public void testOnBindView() { + ButtonItem item = new ButtonItem(); + + try { + item.onBindView(new View(context)); + fail("Calling onBindView on ButtonItem should throw UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // pass } - - @Test - public void testSetEnabledTrue() { - TestButtonItem item = new TestButtonItem(); - item.setEnabled(true); - - final Button button = item.createButton(mParent); - assertTrue("ButtonItem should be enabled", item.isEnabled()); - assertTrue("Button should be enabled", button.isEnabled()); - } - - @Test - public void testSetEnabledFalse() { - TestButtonItem item = new TestButtonItem(); - item.setEnabled(false); - - final Button button = item.createButton(mParent); - assertFalse("ButtonItem should be disabled", item.isEnabled()); - assertFalse("Button should be disabled", button.isEnabled()); - } - - @Test - public void testSetText() { - TestButtonItem item = new TestButtonItem(); - item.setText("lorem ipsum"); - - final Button button = item.createButton(mParent); - assertEquals("ButtonItem text should be \"lorem ipsum\"", "lorem ipsum", item.getText()); - assertEquals("Button text should be \"lorem ipsum\"", "lorem ipsum", button.getText()); - } - - @Test - public void testSetTheme() { - TestButtonItem item = new TestButtonItem(); - item.setTheme(R.style.SuwButtonItem_Colored); - - final Button button = item.createButton(mParent); - assertEquals("ButtonItem theme should be SuwButtonItem.Colored", - R.style.SuwButtonItem_Colored, item.getTheme()); - assertNotNull(button.getContext().getTheme()); - } - - @Test - public void testOnClickListener() { - TestButtonItem item = new TestButtonItem(); - final OnClickListener listener = mock(OnClickListener.class); - item.setOnClickListener(listener); - - verify(listener, never()).onClick(any(ButtonItem.class)); - - final Button button = item.createButton(mParent); - button.performClick(); - - verify(listener).onClick(same(item)); - } - - private static class TestButtonItem extends ButtonItem { - - @Override - public Button createButton(ViewGroup parent) { - // Make this method public for testing - return super.createButton(parent); - } + } + + @Test + public void testCreateButton() { + TestButtonItem item = new TestButtonItem(); + final Button button = item.createButton(parent); + + assertTrue("Default button should be enabled", button.isEnabled()); + assertTrue("Default button text should be empty", TextUtils.isEmpty(button.getText())); + } + + @Test + public void testButtonItemSetsItsId() { + TestButtonItem item = new TestButtonItem(); + final int id = 12345; + item.setId(id); + + assertEquals("Button's id should be set", id, item.createButton(parent).getId()); + } + + @Test + public void testCreateButtonTwice() { + TestButtonItem item = new TestButtonItem(); + final Button button = item.createButton(parent); + + FrameLayout frameLayout = new FrameLayout(context); + frameLayout.addView(button); + + final Button button2 = item.createButton(parent); + assertSame("createButton should be reused", button, button2); + assertNull("Should be removed from parent after createButton", button2.getParent()); + } + + @Test + public void testSetEnabledTrue() { + TestButtonItem item = new TestButtonItem(); + item.setEnabled(true); + + final Button button = item.createButton(parent); + assertTrue("ButtonItem should be enabled", item.isEnabled()); + assertTrue("Button should be enabled", button.isEnabled()); + } + + @Test + public void testSetEnabledFalse() { + TestButtonItem item = new TestButtonItem(); + item.setEnabled(false); + + final Button button = item.createButton(parent); + assertFalse("ButtonItem should be disabled", item.isEnabled()); + assertFalse("Button should be disabled", button.isEnabled()); + } + + @Test + public void testSetText() { + TestButtonItem item = new TestButtonItem(); + item.setText("lorem ipsum"); + + final Button button = item.createButton(parent); + assertEquals("ButtonItem text should be \"lorem ipsum\"", "lorem ipsum", item.getText()); + assertEquals("Button text should be \"lorem ipsum\"", "lorem ipsum", button.getText()); + } + + @Test + public void testSetTheme() { + TestButtonItem item = new TestButtonItem(); + item.setTheme(R.style.SuwButtonItem_Colored); + + final Button button = item.createButton(parent); + assertEquals( + "ButtonItem theme should be SuwButtonItem.Colored", + R.style.SuwButtonItem_Colored, + item.getTheme()); + assertNotNull(button.getContext().getTheme()); + } + + @Test + public void testOnClickListener() { + TestButtonItem item = new TestButtonItem(); + final OnClickListener listener = mock(OnClickListener.class); + item.setOnClickListener(listener); + + verify(listener, never()).onClick(any(ButtonItem.class)); + + final Button button = item.createButton(parent); + button.performClick(); + + verify(listener).onClick(same(item)); + } + + private static class TestButtonItem extends ButtonItem { + + @Override + public Button createButton(ViewGroup parent) { + // Make this method public for testing + return super.createButton(parent); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java b/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java index ecaec71..d0c2785 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java @@ -25,7 +25,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,270 +34,268 @@ import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class ItemGroupTest { - private static final Item CHILD_1 = new EqualsItem("Child 1"); - private static final Item CHILD_2 = new EqualsItem("Child 2"); - private static final Item CHILD_3 = new EqualsItem("Child 3"); - private static final Item CHILD_4 = new EqualsItem("Child 4"); - - private ItemGroup mItemGroup; - - @Mock - private ItemHierarchy.Observer mObserver; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mItemGroup = new ItemGroup(); - mItemGroup.registerObserver(mObserver); - } - - @Test - public void testGroup() { - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - assertSame("Item at position 0 should be child1", CHILD_1, mItemGroup.getItemAt(0)); - assertSame("Item at position 1 should be child2", CHILD_2, mItemGroup.getItemAt(1)); - assertEquals("Should have 2 children", 2, mItemGroup.getCount()); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(mItemGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(mItemGroup), eq(1), eq(1)); - } - - @Test - public void testRemoveChild() { - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - mItemGroup.addChild(CHILD_3); - - mItemGroup.removeChild(CHILD_2); - - assertSame("Item at position 0 should be child1", CHILD_1, mItemGroup.getItemAt(0)); - assertSame("Item at position 1 should be child3", CHILD_3, mItemGroup.getItemAt(1)); - assertEquals("Should have 2 children", 2, mItemGroup.getCount()); - - verify(mObserver).onItemRangeRemoved(eq(mItemGroup), eq(1), eq(1)); - } - - @Test - public void testClear() { - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - mItemGroup.clear(); - - assertEquals("Should have 0 child", 0, mItemGroup.getCount()); - - verify(mObserver).onItemRangeRemoved(eq(mItemGroup), eq(0), eq(2)); - } - - @Test - public void testNestedGroup() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - parentGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - childGroup.addChild(CHILD_3); - parentGroup.addChild(childGroup); - parentGroup.addChild(CHILD_4); - - assertSame("Position 0 should be child 1", CHILD_1, parentGroup.getItemAt(0)); - assertSame("Position 1 should be child 2", CHILD_2, parentGroup.getItemAt(1)); - assertSame("Position 2 should be child 3", CHILD_3, parentGroup.getItemAt(2)); - assertSame("Position 3 should be child 4", CHILD_4, parentGroup.getItemAt(3)); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNestedGroupClearNotification() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - parentGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - childGroup.addChild(CHILD_3); - parentGroup.addChild(childGroup); - parentGroup.addChild(CHILD_4); - - childGroup.clear(); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); - verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(1), eq(2)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNestedGroupRemoveNotification() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - parentGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - childGroup.addChild(CHILD_3); - parentGroup.addChild(childGroup); - parentGroup.addChild(CHILD_4); - - childGroup.removeChild(CHILD_3); - childGroup.removeChild(CHILD_2); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(2), eq(1)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(1), eq(1)); - verifyNoMoreInteractions(mObserver); + private static final Item CHILD_1 = new EqualsItem("Child 1"); + private static final Item CHILD_2 = new EqualsItem("Child 2"); + private static final Item CHILD_3 = new EqualsItem("Child 3"); + private static final Item CHILD_4 = new EqualsItem("Child 4"); + + private ItemGroup itemGroup; + + @Mock private ItemHierarchy.Observer observer; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + itemGroup = new ItemGroup(); + itemGroup.registerObserver(observer); + } + + @Test + public void testGroup() { + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + assertSame("Item at position 0 should be child1", CHILD_1, itemGroup.getItemAt(0)); + assertSame("Item at position 1 should be child2", CHILD_2, itemGroup.getItemAt(1)); + assertEquals("Should have 2 children", 2, itemGroup.getCount()); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(itemGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(itemGroup), eq(1), eq(1)); + } + + @Test + public void testRemoveChild() { + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + itemGroup.addChild(CHILD_3); + + itemGroup.removeChild(CHILD_2); + + assertSame("Item at position 0 should be child1", CHILD_1, itemGroup.getItemAt(0)); + assertSame("Item at position 1 should be child3", CHILD_3, itemGroup.getItemAt(1)); + assertEquals("Should have 2 children", 2, itemGroup.getCount()); + + verify(observer).onItemRangeRemoved(eq(itemGroup), eq(1), eq(1)); + } + + @Test + public void testClear() { + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + itemGroup.clear(); + + assertEquals("Should have 0 child", 0, itemGroup.getCount()); + + verify(observer).onItemRangeRemoved(eq(itemGroup), eq(0), eq(2)); + } + + @Test + public void testNestedGroup() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + parentGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + childGroup.addChild(CHILD_3); + parentGroup.addChild(childGroup); + parentGroup.addChild(CHILD_4); + + assertSame("Position 0 should be child 1", CHILD_1, parentGroup.getItemAt(0)); + assertSame("Position 1 should be child 2", CHILD_2, parentGroup.getItemAt(1)); + assertSame("Position 2 should be child 3", CHILD_3, parentGroup.getItemAt(2)); + assertSame("Position 3 should be child 4", CHILD_4, parentGroup.getItemAt(3)); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupClearNotification() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + parentGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + childGroup.addChild(CHILD_3); + parentGroup.addChild(childGroup); + parentGroup.addChild(CHILD_4); + + childGroup.clear(); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); + verify(observer).onItemRangeRemoved(eq(parentGroup), eq(1), eq(2)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupRemoveNotification() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + parentGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + childGroup.addChild(CHILD_3); + parentGroup.addChild(childGroup); + parentGroup.addChild(CHILD_4); + + childGroup.removeChild(CHILD_3); + childGroup.removeChild(CHILD_2); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(3), eq(1)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(2), eq(1)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(1), eq(1)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupClear() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + parentGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + childGroup.addChild(CHILD_3); + parentGroup.addChild(childGroup); + + childGroup.clear(); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(1), eq(2)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupRemoveLastChild() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup1 = new ItemGroup(); + ItemGroup childGroup2 = new ItemGroup(); + parentGroup.registerObserver(observer); + + childGroup1.addChild(CHILD_1); + childGroup1.addChild(CHILD_2); + parentGroup.addChild(childGroup1); + childGroup2.addChild(CHILD_3); + childGroup2.addChild(CHILD_4); + parentGroup.addChild(childGroup2); + + childGroup2.removeChild(CHILD_4); + childGroup2.removeChild(CHILD_3); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(2)); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(2), eq(2)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(3), eq(1)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(2), eq(1)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNestedGroupClearOnlyChild() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + parentGroup.registerObserver(observer); + + childGroup.addChild(CHILD_1); + childGroup.addChild(CHILD_2); + parentGroup.addChild(childGroup); + + childGroup.clear(); + + final InOrder inOrder = inOrder(observer); + inOrder.verify(observer).onItemRangeInserted(eq(parentGroup), eq(0), eq(2)); + inOrder.verify(observer).onItemRangeRemoved(eq(parentGroup), eq(0), eq(2)); + verifyNoMoreInteractions(observer); + } + + @Test + public void testNotifyChange() { + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + CHILD_2.setTitle("Child 2 modified"); + + verify(observer).onItemRangeChanged(eq(itemGroup), eq(1), eq(1)); + } + + @Test + public void testEmptyChildGroup() { + ItemGroup parentGroup = new ItemGroup(); + ItemGroup childGroup = new ItemGroup(); + + parentGroup.addChild(CHILD_1); + parentGroup.addChild(childGroup); + parentGroup.addChild(CHILD_2); + + assertSame("Position 0 should be child 1", CHILD_1, parentGroup.getItemAt(0)); + assertSame("Position 1 should be child 2", CHILD_2, parentGroup.getItemAt(1)); + } + + @Test + public void testFindItemById() { + CHILD_1.setId(12345); + CHILD_2.setId(23456); + + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + assertSame("Find item 23456 should return child 2", CHILD_2, itemGroup.findItemById(23456)); + } + + @Test + public void testFindItemByIdNotFound() { + CHILD_1.setId(12345); + CHILD_2.setId(23456); + + itemGroup.addChild(CHILD_1); + itemGroup.addChild(CHILD_2); + + assertNull("ID not found should return null", itemGroup.findItemById(56789)); + } + + /** + * This class will always return true on {@link #equals(Object)}. Used to ensure that ItemGroup is + * using identity rather than equals(). Be sure to use assertSame rather than assertEquals when + * comparing items of this class. + */ + private static class EqualsItem extends Item { + + EqualsItem(String name) { + setTitle(name); } - @Test - public void testNestedGroupClear() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - parentGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - childGroup.addChild(CHILD_3); - parentGroup.addChild(childGroup); - - childGroup.clear(); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(1)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(1), eq(2)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(1), eq(2)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNestedGroupRemoveLastChild() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup1 = new ItemGroup(); - ItemGroup childGroup2 = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - childGroup1.addChild(CHILD_1); - childGroup1.addChild(CHILD_2); - parentGroup.addChild(childGroup1); - childGroup2.addChild(CHILD_3); - childGroup2.addChild(CHILD_4); - parentGroup.addChild(childGroup2); - - childGroup2.removeChild(CHILD_4); - childGroup2.removeChild(CHILD_3); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(2)); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(2), eq(2)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(3), eq(1)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(2), eq(1)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNestedGroupClearOnlyChild() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - parentGroup.registerObserver(mObserver); - - childGroup.addChild(CHILD_1); - childGroup.addChild(CHILD_2); - parentGroup.addChild(childGroup); - - childGroup.clear(); - - final InOrder inOrder = inOrder(mObserver); - inOrder.verify(mObserver).onItemRangeInserted(eq(parentGroup), eq(0), eq(2)); - inOrder.verify(mObserver).onItemRangeRemoved(eq(parentGroup), eq(0), eq(2)); - verifyNoMoreInteractions(mObserver); - } - - @Test - public void testNotifyChange() { - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - CHILD_2.setTitle("Child 2 modified"); - - verify(mObserver).onItemRangeChanged(eq(mItemGroup), eq(1), eq(1)); - } - - @Test - public void testEmptyChildGroup() { - ItemGroup parentGroup = new ItemGroup(); - ItemGroup childGroup = new ItemGroup(); - - parentGroup.addChild(CHILD_1); - parentGroup.addChild(childGroup); - parentGroup.addChild(CHILD_2); - - assertSame("Position 0 should be child 1", CHILD_1, parentGroup.getItemAt(0)); - assertSame("Position 1 should be child 2", CHILD_2, parentGroup.getItemAt(1)); + @Override + public int hashCode() { + return 1; } - @Test - public void testFindItemById() { - CHILD_1.setId(12345); - CHILD_2.setId(23456); - - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - assertSame("Find item 23456 should return child 2", - CHILD_2, mItemGroup.findItemById(23456)); - } - - @Test - public void testFindItemByIdNotFound() { - CHILD_1.setId(12345); - CHILD_2.setId(23456); - - mItemGroup.addChild(CHILD_1); - mItemGroup.addChild(CHILD_2); - - assertNull("ID not found should return null", mItemGroup.findItemById(56789)); + @Override + public boolean equals(Object obj) { + return obj instanceof Item; } - /** - * This class will always return true on {@link #equals(Object)}. Used to ensure that ItemGroup - * is using identity rather than equals(). Be sure to use assertSame rather than assertEquals - * when comparing items of this class. - */ - private static class EqualsItem extends Item { - - EqualsItem(String name) { - setTitle(name); - } - - @Override - public int hashCode() { - return 1; - } - - @Override - public boolean equals(Object obj) { - return obj instanceof Item; - } - - @Override - public String toString() { - return "EqualsItem{title=" + getTitle() + "}"; - } + @Override + public String toString() { + return "EqualsItem{title=" + getTitle() + "}"; } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/robolectric/ExternalResources.java b/library/test/robotest/src/com/android/setupwizardlib/robolectric/ExternalResources.java new file mode 100644 index 0000000..06ef508 --- /dev/null +++ b/library/test/robotest/src/com/android/setupwizardlib/robolectric/ExternalResources.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.robolectric; + +import static org.robolectric.RuntimeEnvironment.application; +import static org.robolectric.Shadows.shadowOf; + +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import androidx.annotation.AnyRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.util.DisplayMetrics; +import java.util.HashMap; +import java.util.Map; +import org.robolectric.res.ResName; +import org.robolectric.res.ResType; +import org.robolectric.res.TypedResource; +import org.robolectric.shadows.ShadowPackageManager; + +/** + * Utility class to inject resources for an "external" application in Robolectric tests. This can be + * used with {@link org.robolectric.shadows.ShadowPackageManager#resources} to simulate loading + * resources from another package. + */ +public final class ExternalResources { + + public static Resources injectExternalResources(String packageName) { + return injectExternalResources(createPackageInfo(packageName)); + } + + public static Resources injectExternalResources(PackageInfo packageInfo) { + try { + application.getPackageManager().getPackageInfo(packageInfo.packageName, 0); + } catch (NameNotFoundException e) { + // Add the package if it does not exist + shadowOf(application.getPackageManager()).addPackage(packageInfo); + } + Resources resources = Resources.forPackageName(packageInfo.packageName); + ShadowPackageManager.resources.put(packageInfo.packageName, resources); + return resources; + } + + /** + * Constructed resources for testing, representing resources external to the current package under + * test. + */ + public static class Resources extends android.content.res.Resources { + + private final String packageName; + + public static Resources forPackageName(String packageName) { + android.content.res.Resources res = application.getResources(); + return new Resources( + packageName, res.getAssets(), res.getDisplayMetrics(), res.getConfiguration()); + } + + private Resources( + String packageName, AssetManager assets, DisplayMetrics metrics, Configuration config) { + super(assets, metrics, config); + this.packageName = packageName; + } + + @Override + public int getIdentifier(String name, String defType, String defPackage) { + Integer resourceId = resourceIds.get(ResName.qualifyResName(name, defPackage, defType)); + if (resourceId == null) { + return 0; + } + return resourceId; + } + + @Override + public int getInteger(int id) { + return (int) get(id, ResType.INTEGER); + } + + public void putInteger(String name, int value) { + put( + ResName.qualifyResName(name, packageName, "integer"), + new TypedResource<>(value, ResType.INTEGER, null)); + } + + @Override + public int getColor(int id) { + return (int) get(id, ResType.COLOR); + } + + @Override + public int getColor(int id, @Nullable Theme theme) { + return (int) get(id, ResType.COLOR); + } + + public void putColor(String name, int value) { + put( + ResName.qualifyResName(name, packageName, "color"), + new TypedResource<>(value, ResType.COLOR, null)); + } + + @NonNull + @Override + public CharSequence getText(int id) { + return (CharSequence) get(id, ResType.CHAR_SEQUENCE); + } + + @NonNull + @Override + public String getString(int id) { + return get(id, ResType.CHAR_SEQUENCE).toString(); + } + + public void putText(String name, CharSequence value) { + put( + ResName.qualifyResName(name, packageName, "string"), + new TypedResource<>(value, ResType.CHAR_SEQUENCE, null)); + } + + private final Map> overrideResources = new HashMap<>(); + private final Map resourceIds = new HashMap<>(); + private int nextId = 1; + + private void put(ResName resName, TypedResource value) { + int id = nextId++; + overrideResources.put(id, value); + resourceIds.put(resName, id); + } + + private Object get(@AnyRes int id, ResType type) { + TypedResource override = overrideResources.get(id); + if (override != null && override.getResType() == type) { + return override.getData(); + } + throw new NotFoundException(); + } + } + + private static PackageInfo createPackageInfo(String packageName) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + return packageInfo; + } + + private ExternalResources() {} +} diff --git a/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java b/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java index 61baa23..25b7207 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java +++ b/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java @@ -23,13 +23,13 @@ import org.robolectric.RobolectricTestRunner; public class SuwLibRobolectricTestRunner extends RobolectricTestRunner { - public SuwLibRobolectricTestRunner(Class testClass) throws InitializationError { - super(testClass); - } + public SuwLibRobolectricTestRunner(Class testClass) throws InitializationError { + super(testClass); + } - @Override - protected void runChild(FrameworkMethod method, RunNotifier notifier) { - System.out.println("===== Running " + method + " ====="); - super.runChild(method, notifier); - } + @Override + protected void runChild(FrameworkMethod method, RunNotifier notifier) { + System.out.println("===== Running " + method + " ====="); + super.runChild(method, notifier); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java b/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java deleted file mode 100644 index f1d37c8..0000000 --- a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.setupwizardlib.shadow; - -import android.util.Log; - -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -@Implements(Log.class) -public class ShadowLog extends org.robolectric.shadows.ShadowLog { - - public static boolean sWtfIsFatal = true; - - public static class TerribleFailure extends RuntimeException { - - public TerribleFailure(String msg, Throwable cause) { - super(msg, cause); - } - } - - @Implementation - public static void wtf(String tag, String msg) { - org.robolectric.shadows.ShadowLog.wtf(tag, msg); - if (sWtfIsFatal) { - throw new TerribleFailure(msg, null); - } - } - - @Implementation - public static void wtf(String tag, String msg, Throwable throwable) { - org.robolectric.shadows.ShadowLog.wtf(tag, msg, throwable); - if (sWtfIsFatal) { - throw new TerribleFailure(msg, throwable); - } - } -} diff --git a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowMediaPlayer.java b/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowMediaPlayer.java deleted file mode 100644 index 7bcefd0..0000000 --- a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowMediaPlayer.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.setupwizardlib.shadow; - -import android.content.Context; -import android.media.MediaPlayer; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -import java.io.IOException; -import java.net.HttpCookie; -import java.util.List; -import java.util.Map; - -@Implements(MediaPlayer.class) -public class ShadowMediaPlayer extends org.robolectric.shadows.ShadowMediaPlayer { - - private int mVideoWidth; - private int mVideoHeight; - - public ShadowMediaPlayer() { - super(); - mVideoWidth = -1; - mVideoHeight = -1; - } - - @Implementation - public void setDataSource( - @NonNull Context context, - @NonNull Uri uri, - @Nullable Map headers, - @Nullable List cookies) - throws IOException { - setDataSource(context, uri, headers); - } - - @Implementation - public void seekTo(long msec, int mode) { - seekTo((int) msec); - } - - public void setVideoSize(int width, int height) { - if (width < 0) { - throw new IllegalArgumentException("Unexpected negative width=" + width); - } - if (height < 0) { - throw new IllegalArgumentException("Unexpected negative height=" + height); - } - - mVideoWidth = width; - mVideoHeight = height; - } - - @Override - public int getVideoWidth() { - return mVideoWidth; - } - - @Override - public int getVideoHeight() { - return mVideoHeight; - } -} diff --git a/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java b/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java index 3aafa7d..def820e 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.span; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertSame; import static org.robolectric.RuntimeEnvironment.application; @@ -27,82 +26,79 @@ import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.method.LinkMovementMethod; import android.widget.TextView; - import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Test; import org.junit.runner.RunWith; @RunWith(SuwLibRobolectricTestRunner.class) public class LinkSpanTest { - @Test - public void onClick_shouldCallListenerOnContext() { - final TestContext context = new TestContext(application); - final TextView textView = new TextView(context); - final LinkSpan linkSpan = new LinkSpan("test_id"); + @Test + public void onClick_shouldCallListenerOnContext() { + final TestContext context = new TestContext(application); + final TextView textView = new TextView(context); + final LinkSpan linkSpan = new LinkSpan("test_id"); - linkSpan.onClick(textView); + linkSpan.onClick(textView); - assertSame("Clicked LinkSpan should be passed to setup", linkSpan, context.clickedSpan); - } + assertSame("Clicked LinkSpan should be passed to setup", linkSpan, context.clickedSpan); + } - @Test - public void onClick_contextDoesNotImplementOnClickListener_shouldBeNoOp() { - final TextView textView = new TextView(application); - final LinkSpan linkSpan = new LinkSpan("test_id"); + @Test + public void onClick_contextDoesNotImplementOnClickListener_shouldBeNoOp() { + final TextView textView = new TextView(application); + final LinkSpan linkSpan = new LinkSpan("test_id"); - linkSpan.onClick(textView); + linkSpan.onClick(textView); - // This would be no-op, because the context doesn't implement LinkSpan.OnClickListener. - // Just check that no uncaught exception here. - } + // This would be no-op, because the context doesn't implement LinkSpan.OnClickListener. + // Just check that no uncaught exception here. + } - @Test - public void onClick_contextWrapsOnClickListener_shouldCallWrappedListener() { - final TestContext context = new TestContext(application); - final Context wrapperContext = new ContextWrapper(context); - final TextView textView = new TextView(wrapperContext); - final LinkSpan linkSpan = new LinkSpan("test_id"); + @Test + public void onClick_contextWrapsOnClickListener_shouldCallWrappedListener() { + final TestContext context = new TestContext(application); + final Context wrapperContext = new ContextWrapper(context); + final TextView textView = new TextView(wrapperContext); + final LinkSpan linkSpan = new LinkSpan("test_id"); + linkSpan.onClick(textView); + assertSame("Clicked LinkSpan should be passed to setup", linkSpan, context.clickedSpan); + } - linkSpan.onClick(textView); - assertSame("Clicked LinkSpan should be passed to setup", linkSpan, context.clickedSpan); - } + @Test + public void onClick_shouldClearSelection() { + final TestContext context = new TestContext(application); + final TextView textView = new TextView(context); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setFocusable(true); + textView.setFocusableInTouchMode(true); + final LinkSpan linkSpan = new LinkSpan("test_id"); - @Test - public void onClick_shouldClearSelection() { - final TestContext context = new TestContext(application); - final TextView textView = new TextView(context); - textView.setMovementMethod(LinkMovementMethod.getInstance()); - textView.setFocusable(true); - textView.setFocusableInTouchMode(true); - final LinkSpan linkSpan = new LinkSpan("test_id"); - - SpannableStringBuilder text = new SpannableStringBuilder("Lorem ipsum dolor sit"); - textView.setText(text); - text.setSpan(linkSpan, /* start= */ 0, /* end= */ 5, /* flags= */ 0); - // Simulate the touch effect set by TextView when touched. - Selection.setSelection(text, /* start= */ 0, /* end= */ 5); - - linkSpan.onClick(textView); - - assertThat(Selection.getSelectionStart(textView.getText())).isEqualTo(0); - assertThat(Selection.getSelectionEnd(textView.getText())).isEqualTo(0); - } + SpannableStringBuilder text = new SpannableStringBuilder("Lorem ipsum dolor sit"); + textView.setText(text); + text.setSpan(linkSpan, /* start= */ 0, /* end= */ 5, /* flags= */ 0); + // Simulate the touch effect set by TextView when touched. + Selection.setSelection(text, /* start= */ 0, /* stop= */ 5); + + linkSpan.onClick(textView); - @SuppressWarnings("deprecation") - private static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { + assertThat(Selection.getSelectionStart(textView.getText())).isEqualTo(0); + assertThat(Selection.getSelectionEnd(textView.getText())).isEqualTo(0); + } - public LinkSpan clickedSpan = null; + @SuppressWarnings("deprecation") + private static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { - TestContext(Context base) { - super(base); - } + public LinkSpan clickedSpan = null; + + TestContext(Context base) { + super(base); + } - @Override - public void onClick(LinkSpan span) { - clickedSpan = span; - } + @Override + public void onClick(LinkSpan span) { + clickedSpan = span; } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java index ec3622d..0a82e8b 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java @@ -16,105 +16,96 @@ package com.android.setupwizardlib.template; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; import android.content.Context; import android.view.View; import android.view.ViewGroup; -import android.widget.AbsListView.OnScrollListener; import android.widget.BaseAdapter; import android.widget.ListView; - import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Shadows; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowListView; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) @RunWith(SuwLibRobolectricTestRunner.class) public class ListViewScrollHandlingDelegateTest { - @Mock - private RequireScrollMixin mRequireScrollMixin; + @Mock private RequireScrollMixin requireScrollMixin; - private ListView mListView; - private ListViewScrollHandlingDelegate mDelegate; - private ArgumentCaptor mListenerCaptor; + private ListView listView; + private ShadowListView shadowListView; + private ListViewScrollHandlingDelegate delegate; - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); - mListView = spy(new TestListView(application)); - mDelegate = new ListViewScrollHandlingDelegate(mRequireScrollMixin, mListView); + listView = new TestListView(application); + delegate = new ListViewScrollHandlingDelegate(requireScrollMixin, listView); + shadowListView = Shadows.shadowOf(listView); - mListenerCaptor = ArgumentCaptor.forClass(OnScrollListener.class); - doNothing().when(mListView).setOnScrollListener(mListenerCaptor.capture()); + listView.layout(0, 0, 50, 50); + } - mListView.layout(0, 0, 50, 50); - } + @Test + public void testRequireScroll() throws Throwable { + delegate.startListening(); - @Test - public void testRequireScroll() throws Throwable { - mDelegate.startListening(); + verify(requireScrollMixin).notifyScrollabilityChange(true); + } - verify(mRequireScrollMixin).notifyScrollabilityChange(true); - } + @Test + public void testScrolledToBottom() throws Throwable { + delegate.startListening(); - @Test - public void testScrolledToBottom() throws Throwable { - mDelegate.startListening(); + verify(requireScrollMixin).notifyScrollabilityChange(true); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); + shadowListView.getOnScrollListener().onScroll(listView, 2, 20, 20); - doReturn(20).when(mListView).getLastVisiblePosition(); - mListenerCaptor.getValue().onScroll(mListView, 2, 20, 20); + verify(requireScrollMixin).notifyScrollabilityChange(false); + } - verify(mRequireScrollMixin).notifyScrollabilityChange(false); - } + @Test + public void testPageScrollDown() throws Throwable { + delegate.pageScrollDown(); + assertThat(shadowListView.getLastSmoothScrollByDistance()).isEqualTo(50); + } - @Test - public void testPageScrollDown() throws Throwable { - mDelegate.pageScrollDown(); - verify(mListView).smoothScrollBy(eq(50), anyInt()); - } + private static class TestListView extends ListView { + + TestListView(Context context) { + super(context); + setAdapter( + new BaseAdapter() { + @Override + public int getCount() { + return 20; + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } - private static class TestListView extends ListView { - - TestListView(Context context) { - super(context); - setAdapter(new BaseAdapter() { - @Override - public int getCount() { - return 20; - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - return new View(parent.getContext()); - } - }); - } + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return new View(parent.getContext()); + } + }); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java index c641449..ab35eb3 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -33,13 +32,12 @@ import android.annotation.SuppressLint; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; - +import com.android.setupwizardlib.GlifLayout; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.template.RequireScrollMixin.OnRequireScrollStateChangedListener; import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate; import com.android.setupwizardlib.view.NavigationBar; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,121 +45,122 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) @RunWith(SuwLibRobolectricTestRunner.class) public class RequireScrollMixinTest { - @Mock - private TemplateLayout mTemplateLayout; - - @Mock - private ScrollHandlingDelegate mDelegate; - - private RequireScrollMixin mRequireScrollMixin; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - doReturn(application).when(mTemplateLayout).getContext(); - mRequireScrollMixin = new RequireScrollMixin(mTemplateLayout); - mRequireScrollMixin.setScrollHandlingDelegate(mDelegate); - } - - @Test - public void testRequireScroll() { - mRequireScrollMixin.requireScroll(); - - verify(mDelegate).startListening(); - } - - @Test - public void testScrollStateChangedListener() { - OnRequireScrollStateChangedListener listener = - mock(OnRequireScrollStateChangedListener.class); - mRequireScrollMixin.setOnRequireScrollStateChangedListener(listener); - assertFalse("Scrolling should not be required initially", - mRequireScrollMixin.isScrollingRequired()); - - mRequireScrollMixin.notifyScrollabilityChange(true); - verify(listener).onRequireScrollStateChanged(true); - assertTrue("Scrolling should be required when there is more content below the fold", - mRequireScrollMixin.isScrollingRequired()); - - mRequireScrollMixin.notifyScrollabilityChange(false); - verify(listener).onRequireScrollStateChanged(false); - assertFalse("Scrolling should not be required after scrolling to bottom", - mRequireScrollMixin.isScrollingRequired()); - - // Once the user has scrolled to the bottom, they should not be forced to scroll down again - mRequireScrollMixin.notifyScrollabilityChange(true); - verifyNoMoreInteractions(listener); - - assertFalse("Scrolling should not be required after scrolling to bottom once", - mRequireScrollMixin.isScrollingRequired()); - - assertSame(listener, mRequireScrollMixin.getOnRequireScrollStateChangedListener()); - } - - @Test - public void testCreateOnClickListener() { - OnClickListener wrappedListener = mock(OnClickListener.class); - final OnClickListener onClickListener = - mRequireScrollMixin.createOnClickListener(wrappedListener); - - mRequireScrollMixin.notifyScrollabilityChange(true); - onClickListener.onClick(null); - - verify(wrappedListener, never()).onClick(any(View.class)); - verify(mDelegate).pageScrollDown(); - - mRequireScrollMixin.notifyScrollabilityChange(false); - onClickListener.onClick(null); - - verify(wrappedListener).onClick(any(View.class)); - } - - @Test - public void testRequireScrollWithNavigationBar() { - final NavigationBar navigationBar = new NavigationBar(application); - mRequireScrollMixin.requireScrollWithNavigationBar(navigationBar); - - mRequireScrollMixin.notifyScrollabilityChange(true); - assertEquals("More button should be visible", - View.VISIBLE, navigationBar.getMoreButton().getVisibility()); - assertEquals("Next button should be hidden", - View.GONE, navigationBar.getNextButton().getVisibility()); - - navigationBar.getMoreButton().performClick(); - verify(mDelegate).pageScrollDown(); - - mRequireScrollMixin.notifyScrollabilityChange(false); - assertEquals("More button should be hidden", - View.GONE, navigationBar.getMoreButton().getVisibility()); - assertEquals("Next button should be visible", - View.VISIBLE, navigationBar.getNextButton().getVisibility()); - } - - @SuppressLint("SetTextI18n") // It's OK for testing - @Test - public void testRequireScrollWithButton() { - final Button button = new Button(application); - button.setText("OriginalLabel"); - OnClickListener wrappedListener = mock(OnClickListener.class); - mRequireScrollMixin.requireScrollWithButton( - button, "TestMoreLabel", wrappedListener); - - assertEquals("Button label should be kept initially", "OriginalLabel", button.getText()); - - mRequireScrollMixin.notifyScrollabilityChange(true); - assertEquals("TestMoreLabel", button.getText()); - button.performClick(); - verify(wrappedListener, never()).onClick(eq(button)); - verify(mDelegate).pageScrollDown(); - - mRequireScrollMixin.notifyScrollabilityChange(false); - assertEquals("OriginalLabel", button.getText()); - button.performClick(); - verify(wrappedListener).onClick(eq(button)); - } + @Mock private ScrollHandlingDelegate delegate; + + private RequireScrollMixin requireScrollMixin; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + TemplateLayout templateLayout = new GlifLayout(application); + requireScrollMixin = new RequireScrollMixin(templateLayout); + requireScrollMixin.setScrollHandlingDelegate(delegate); + } + + @Test + public void testRequireScroll() { + requireScrollMixin.requireScroll(); + + verify(delegate).startListening(); + } + + @Test + public void testScrollStateChangedListener() { + OnRequireScrollStateChangedListener listener = mock(OnRequireScrollStateChangedListener.class); + requireScrollMixin.setOnRequireScrollStateChangedListener(listener); + assertFalse( + "Scrolling should not be required initially", requireScrollMixin.isScrollingRequired()); + + requireScrollMixin.notifyScrollabilityChange(true); + verify(listener).onRequireScrollStateChanged(true); + assertTrue( + "Scrolling should be required when there is more content below the fold", + requireScrollMixin.isScrollingRequired()); + + requireScrollMixin.notifyScrollabilityChange(false); + verify(listener).onRequireScrollStateChanged(false); + assertFalse( + "Scrolling should not be required after scrolling to bottom", + requireScrollMixin.isScrollingRequired()); + + // Once the user has scrolled to the bottom, they should not be forced to scroll down again + requireScrollMixin.notifyScrollabilityChange(true); + verifyNoMoreInteractions(listener); + + assertFalse( + "Scrolling should not be required after scrolling to bottom once", + requireScrollMixin.isScrollingRequired()); + + assertSame(listener, requireScrollMixin.getOnRequireScrollStateChangedListener()); + } + + @Test + public void testCreateOnClickListener() { + OnClickListener wrappedListener = mock(OnClickListener.class); + final OnClickListener onClickListener = + requireScrollMixin.createOnClickListener(wrappedListener); + + requireScrollMixin.notifyScrollabilityChange(true); + onClickListener.onClick(null); + + verify(wrappedListener, never()).onClick(any(View.class)); + verify(delegate).pageScrollDown(); + + requireScrollMixin.notifyScrollabilityChange(false); + onClickListener.onClick(null); + + verify(wrappedListener).onClick(any(View.class)); + } + + @Test + public void testRequireScrollWithNavigationBar() { + final NavigationBar navigationBar = new NavigationBar(application); + requireScrollMixin.requireScrollWithNavigationBar(navigationBar); + + requireScrollMixin.notifyScrollabilityChange(true); + assertEquals( + "More button should be visible", + View.VISIBLE, + navigationBar.getMoreButton().getVisibility()); + assertEquals( + "Next button should be hidden", View.GONE, navigationBar.getNextButton().getVisibility()); + + navigationBar.getMoreButton().performClick(); + verify(delegate).pageScrollDown(); + + requireScrollMixin.notifyScrollabilityChange(false); + assertEquals( + "More button should be hidden", View.GONE, navigationBar.getMoreButton().getVisibility()); + assertEquals( + "Next button should be visible", + View.VISIBLE, + navigationBar.getNextButton().getVisibility()); + } + + @SuppressLint("SetTextI18n") // It's OK for testing + @Test + public void testRequireScrollWithButton() { + final Button button = new Button(application); + button.setText("OriginalLabel"); + OnClickListener wrappedListener = mock(OnClickListener.class); + requireScrollMixin.requireScrollWithButton(button, "TestMoreLabel", wrappedListener); + + assertEquals("Button label should be kept initially", "OriginalLabel", button.getText()); + + requireScrollMixin.notifyScrollabilityChange(true); + assertEquals("TestMoreLabel", button.getText()); + button.performClick(); + verify(wrappedListener, never()).onClick(eq(button)); + verify(delegate).pageScrollDown(); + + requireScrollMixin.notifyScrollabilityChange(false); + assertEquals("OriginalLabel", button.getText()); + button.performClick(); + verify(wrappedListener).onClick(eq(button)); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java index 429445c..52411e3 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java @@ -16,72 +16,65 @@ package com.android.setupwizardlib.template; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.spy; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; +import android.view.View; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.view.BottomScrollView; -import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) @RunWith(SuwLibRobolectricTestRunner.class) public class ScrollViewScrollHandlingDelegateTest { - @Mock - private RequireScrollMixin mRequireScrollMixin; - - private BottomScrollView mScrollView; - private ScrollViewScrollHandlingDelegate mDelegate; - private ArgumentCaptor mListenerCaptor; + @Mock private RequireScrollMixin requireScrollMixin; - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + private BottomScrollView scrollView; + private ScrollViewScrollHandlingDelegate delegate; - mScrollView = spy(new BottomScrollView(application)); - mDelegate = new ScrollViewScrollHandlingDelegate(mRequireScrollMixin, mScrollView); + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); - mListenerCaptor = ArgumentCaptor.forClass(BottomScrollListener.class); - doNothing().when(mScrollView).setBottomScrollListener(mListenerCaptor.capture()); + scrollView = new BottomScrollView(application); + View childView = new View(application); + scrollView.addView(childView); + delegate = new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView); - mScrollView.layout(0, 0, 50, 50); - } + scrollView.layout(0, 0, 500, 500); + childView.layout(0, 0, 1000, 1000); + } - @Test - public void testRequireScroll() throws Throwable { - mDelegate.startListening(); + @Test + public void testRequireScroll() throws Throwable { + delegate.startListening(); - mListenerCaptor.getValue().onRequiresScroll(); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); - } + scrollView.getBottomScrollListener().onRequiresScroll(); + verify(requireScrollMixin).notifyScrollabilityChange(true); + } - @Test - public void testScrolledToBottom() throws Throwable { - mDelegate.startListening(); + @Test + public void testScrolledToBottom() throws Throwable { + delegate.startListening(); - mListenerCaptor.getValue().onRequiresScroll(); - verify(mRequireScrollMixin).notifyScrollabilityChange(true); + scrollView.getBottomScrollListener().onRequiresScroll(); + verify(requireScrollMixin).notifyScrollabilityChange(true); - mListenerCaptor.getValue().onScrolledToBottom(); + scrollView.getBottomScrollListener().onScrolledToBottom(); - verify(mRequireScrollMixin).notifyScrollabilityChange(false); - } + verify(requireScrollMixin).notifyScrollabilityChange(false); + } - @Test - public void testPageScrollDown() throws Throwable { - mDelegate.pageScrollDown(); - verify(mScrollView).smoothScrollBy(anyInt(), eq(50)); - } + @Test + public void testPageScrollDown() throws Throwable { + delegate.pageScrollDown(); + assertThat(scrollView.getScrollY()).isEqualTo(500); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java index c10c122..f6c23f1 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java @@ -25,10 +25,8 @@ import android.content.res.TypedArray; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.ContextThemeWrapper; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,77 +36,77 @@ import org.robolectric.annotation.Config; @Config(sdk = Config.ALL_SDKS) public class GlifDimensionTest { - private Context mContext; - - @Before - public void setUp() { - mContext = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); - } - - @Test - public void testDividerInsetPhone() { - assertDividerInset(); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testDividerInsetSw600dp() { - assertDividerInset(); - } - - private void assertDividerInset() { - final Resources res = mContext.getResources(); - - final TypedArray a = mContext.obtainStyledAttributes(new int[]{R.attr.suwMarginSides}); - final int marginSides = a.getDimensionPixelSize(0, 0); - a.recycle(); - - assertEquals( - "Dimensions should satisfy constraint: " - + "?attr/suwMarginSides = suw_items_glif_text_divider_inset", - marginSides, - res.getDimensionPixelSize(R.dimen.suw_items_glif_text_divider_inset)); - - assertEquals( - "Dimensions should satisfy constraint: ?attr/suwMarginSides + " - + "suw_items_icon_container_width = suw_items_glif_icon_divider_inset", - marginSides + res.getDimensionPixelSize(R.dimen.suw_items_icon_container_width), - res.getDimensionPixelSize(R.dimen.suw_items_glif_icon_divider_inset)); - } - - @Test - public void testButtonMargin() { - assertButtonMargin(); - } - - @Config(qualifiers = "sw600dp") - @Test - public void testButtonMarginSw600dp() { - assertButtonMargin(); - } - - private void assertButtonMargin() { - final Resources res = mContext.getResources(); - - final TypedArray a = mContext.obtainStyledAttributes(new int[]{R.attr.suwMarginSides}); - final int marginSides = a.getDimensionPixelSize(0, 0); - a.recycle(); - - assertEquals( - "Dimensions should satisfy constraint: ?attr/suwMarginSides - " - + "4dp (internal padding of button) = suw_glif_button_margin_end", - marginSides - dp2Px(4), - res.getDimensionPixelSize(R.dimen.suw_glif_button_margin_end)); - - assertEquals( - "Dimensions should satisfy constraint: ?attr/suwMarginSides - " - + "suw_glif_button_padding = suw_glif_button_margin_start", - marginSides - res.getDimensionPixelSize(R.dimen.suw_glif_button_padding), - res.getDimensionPixelSize(R.dimen.suw_glif_button_margin_start)); - } - - private int dp2Px(float dp) { - DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); - } + private Context context; + + @Before + public void setUp() { + context = new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light); + } + + @Test + public void testDividerInsetPhone() { + assertDividerInset(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testDividerInsetSw600dp() { + assertDividerInset(); + } + + private void assertDividerInset() { + final Resources res = context.getResources(); + + final TypedArray a = context.obtainStyledAttributes(new int[] {R.attr.suwMarginSides}); + final int marginSides = a.getDimensionPixelSize(0, 0); + a.recycle(); + + assertEquals( + "Dimensions should satisfy constraint: " + + "?attr/suwMarginSides = suw_items_glif_text_divider_inset", + marginSides, + res.getDimensionPixelSize(R.dimen.suw_items_glif_text_divider_inset)); + + assertEquals( + "Dimensions should satisfy constraint: ?attr/suwMarginSides + " + + "suw_items_icon_container_width = suw_items_glif_icon_divider_inset", + marginSides + res.getDimensionPixelSize(R.dimen.suw_items_icon_container_width), + res.getDimensionPixelSize(R.dimen.suw_items_glif_icon_divider_inset)); + } + + @Test + public void testButtonMargin() { + assertButtonMargin(); + } + + @Config(qualifiers = "sw600dp") + @Test + public void testButtonMarginSw600dp() { + assertButtonMargin(); + } + + private void assertButtonMargin() { + final Resources res = context.getResources(); + + final TypedArray a = context.obtainStyledAttributes(new int[] {R.attr.suwMarginSides}); + final int marginSides = a.getDimensionPixelSize(0, 0); + a.recycle(); + + assertEquals( + "Dimensions should satisfy constraint: ?attr/suwMarginSides - " + + "4dp (internal padding of button) = suw_glif_button_margin_end", + marginSides - dp2Px(4), + res.getDimensionPixelSize(R.dimen.suw_glif_button_margin_end)); + + assertEquals( + "Dimensions should satisfy constraint: ?attr/suwMarginSides - " + + "suw_glif_button_padding = suw_glif_button_margin_start", + marginSides - res.getDimensionPixelSize(R.dimen.suw_glif_button_padding), + res.getDimensionPixelSize(R.dimen.suw_glif_button_margin_start)); + } + + private int dp2Px(float dp) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java index 195fc9b..bc4ba75 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifStyleTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.util; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.robolectric.RuntimeEnvironment.application; @@ -28,16 +27,13 @@ import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.ContextThemeWrapper; import android.widget.Button; import android.widget.ProgressBar; - -import androidx.annotation.Nullable; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; @@ -49,61 +45,63 @@ import org.robolectric.util.ReflectionHelpers.ClassParameter; @Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class GlifStyleTest { - @Test - public void testSuwGlifButtonTertiary() { - Button button = createButton( - new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light), - Robolectric.buildAttributeSet() - .setStyleAttribute("@style/SuwGlifButton.Tertiary") - .build()); - assertThat(button.getBackground()).named("background").isNotNull(); - assertThat(button.getTransformationMethod()).named("transformation method").isNull(); - if (VERSION.SDK_INT < VERSION_CODES.M) { - // Robolectric resolved the wrong theme attribute on versions >= M - // https://github.com/robolectric/robolectric/issues/2940 - assertEquals("ff4285f4", Integer.toHexString(button.getTextColors().getDefaultColor())); - } + @Test + public void testSuwGlifButtonTertiary() { + Button button = + createButton( + new ContextThemeWrapper(application, R.style.SuwThemeGlif_Light), + Robolectric.buildAttributeSet() + .setStyleAttribute("@style/SuwGlifButton.Tertiary") + .build()); + assertThat(button.getBackground()).named("background").isNotNull(); + assertThat(button.getTransformationMethod()).named("transformation method").isNull(); + if (VERSION.SDK_INT < VERSION_CODES.M) { + // Robolectric resolved the wrong theme attribute on versions >= M + // https://github.com/robolectric/robolectric/issues/2940 + assertEquals("ff4285f4", Integer.toHexString(button.getTextColors().getDefaultColor())); } + } - @TargetApi(VERSION_CODES.LOLLIPOP) - @Config(sdk = Config.NEWEST_SDK) - @Test - public void glifThemeLight_statusBarColorShouldBeTransparent() { - GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); - assertEquals(0x00000000, activity.getWindow().getStatusBarColor()); - } + @TargetApi(VERSION_CODES.LOLLIPOP) + @Config(sdk = Config.NEWEST_SDK) + @Test + public void glifThemeLight_statusBarColorShouldBeTransparent() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + assertEquals(0x00000000, activity.getWindow().getStatusBarColor()); + } - @Test - public void glifLoadingScreen_shouldHaveProgressBar() { - GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); - activity.setContentView(R.layout.suw_glif_loading_screen); + @Test + public void glifLoadingScreen_shouldHaveProgressBar() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + activity.setContentView(R.layout.suw_glif_loading_screen); - assertTrue("Progress bar should exist", - activity.findViewById(R.id.suw_large_progress_bar) instanceof ProgressBar); - } + assertTrue( + "Progress bar should exist", + activity.findViewById(R.id.suw_large_progress_bar) instanceof ProgressBar); + } - private Button createButton(Context context, AttributeSet attrs) { - Class buttonClass; - try { - // Use AppCompatButton in builds that have them (i.e. gingerbreadCompat) - // noinspection unchecked - buttonClass = (Class) - Class.forName("androidx.appcompat.widget.AppCompatButton"); - } catch (ClassNotFoundException e) { - buttonClass = Button.class; - } - return ReflectionHelpers.callConstructor( - buttonClass, - ClassParameter.from(Context.class, context), - ClassParameter.from(AttributeSet.class, attrs)); + private Button createButton(Context context, AttributeSet attrs) { + Class buttonClass; + try { + // Use AppCompatButton in builds that have them (i.e. gingerbreadCompat) + // noinspection unchecked + buttonClass = + (Class) Class.forName("androidx.appcompat.widget.AppCompatButton"); + } catch (ClassNotFoundException e) { + buttonClass = Button.class; } + return ReflectionHelpers.callConstructor( + buttonClass, + ClassParameter.from(Context.class, context), + ClassParameter.from(AttributeSet.class, attrs)); + } - private static class GlifThemeActivity extends Activity { + private static class GlifThemeActivity extends Activity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - setTheme(R.style.SuwThemeGlif_Light); - super.onCreate(savedInstanceState); - } + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(R.style.SuwThemeGlif_Light); + super.onCreate(savedInstanceState); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java index 44b8886..1d5276f 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.util; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -27,14 +26,11 @@ import android.graphics.Typeface; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; +import androidx.annotation.Nullable; import android.view.View; import android.widget.Button; - -import androidx.annotation.Nullable; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; @@ -44,42 +40,39 @@ import org.robolectric.annotation.Config; @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.NEWEST_SDK) public class GlifV3StyleTest { - @Test - public void activityWithGlifV3Theme_shouldUseLightNavBarOnV27OrAbove() { - GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); - if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) { - assertEquals( - activity.getWindow().getNavigationBarColor(), - Color.WHITE); - int vis = activity.getWindow().getDecorView().getSystemUiVisibility(); - assertTrue((vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0); - } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - assertEquals( - activity.getWindow().getNavigationBarColor(), - Color.BLACK); - } - // Nav bar color is not customizable pre-L + @Test + public void activityWithGlifV3Theme_shouldUseLightNavBarOnV27OrAbove() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) { + assertEquals(Color.WHITE, activity.getWindow().getNavigationBarColor()); + int vis = activity.getWindow().getDecorView().getSystemUiVisibility(); + assertTrue((vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0); + } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals(Color.BLACK, activity.getWindow().getNavigationBarColor()); } + // Nav bar color is not customizable pre-L + } - @Test - public void buttonWithGlifV3_shouldBeGoogleSans() { - GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); - Button button = new Button( - activity, - Robolectric.buildAttributeSet() - .setStyleAttribute("@style/SuwGlifButton.Primary") - .build()); - assertThat(button.getTypeface()).isEqualTo(Typeface.create("google-sans", 0)); - // Button should not be all caps - assertThat(button.getTransformationMethod()).isNull(); - } + @Test + public void buttonWithGlifV3_shouldBeGoogleSans() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + Button button = + new Button( + activity, + Robolectric.buildAttributeSet() + .setStyleAttribute("@style/SuwGlifButton.Primary") + .build()); + assertThat(button.getTypeface()).isEqualTo(Typeface.create("google-sans", 0)); + // Button should not be all caps + assertThat(button.getTransformationMethod()).isNull(); + } - private static class GlifThemeActivity extends Activity { + private static class GlifThemeActivity extends Activity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - setTheme(R.style.SuwThemeGlifV3_Light); - super.onCreate(savedInstanceState); - } + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(R.style.SuwThemeGlifV3_Light); + super.onCreate(savedInstanceState); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java index 2285cd5..0aae115 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java @@ -17,211 +17,164 @@ package com.android.setupwizardlib.util; import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; import static org.robolectric.RuntimeEnvironment.application; +import static org.robolectric.Shadows.shadowOf; -import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; - import com.android.setupwizardlib.R; +import com.android.setupwizardlib.robolectric.ExternalResources; +import com.android.setupwizardlib.robolectric.ExternalResources.Resources; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.util.Partner.ResourceEntry; -import com.android.setupwizardlib.util.PartnerTest.ShadowApplicationPackageManager; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.Shadows; +import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.shadows.ShadowResources; - -import java.util.Arrays; -import java.util.Collections; @RunWith(SuwLibRobolectricTestRunner.class) -@Config( - sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }, - shadows = ShadowApplicationPackageManager.class) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class PartnerTest { - private static final String ACTION_PARTNER_CUSTOMIZATION = - "com.android.setupwizard.action.PARTNER_CUSTOMIZATION"; - - private Context mContext; - private Resources mPartnerResources; - - private ShadowApplicationPackageManager mPackageManager; - - @Before - public void setUp() throws Exception { - Partner.resetForTesting(); - - mContext = spy(application); - mPartnerResources = spy(ShadowResources.getSystem()); - - mPackageManager = - (ShadowApplicationPackageManager) Shadows.shadowOf(application.getPackageManager()); - mPackageManager.partnerResources = mPartnerResources; - } - - @Test - public void testLoadPartner() { - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList( - createResolveInfo("foo.bar", false, true), - createResolveInfo("test.partner.package", true, true)) - ); - - Partner partner = Partner.get(mContext); - assertNotNull("Partner should not be null", partner); - } - - @Test - public void testLoadNoPartner() { - Partner partner = Partner.get(mContext); - assertNull("Partner should be null", partner); - } - - @Test - public void testLoadNonSystemPartner() { - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList( - createResolveInfo("foo.bar", false, true), - createResolveInfo("test.partner.package", false, true)) - ); - - Partner partner = Partner.get(mContext); - assertNull("Partner should be null", partner); - } - - @Test - public void testLoadPartnerValue() { - doReturn(0x7f010000).when(mPartnerResources) - .getIdentifier(eq("suwTransitionDuration"), eq("integer"), anyString()); - doReturn(5000).when(mPartnerResources).getInteger(eq(0x7f010000)); - - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList( - createResolveInfo("foo.bar", false, true), - createResolveInfo("test.partner.package", true, true)) - ); - - ResourceEntry entry = Partner.getResourceEntry(mContext, R.integer.suwTransitionDuration); - int partnerValue = entry.resources.getInteger(entry.id); - assertEquals("Partner value should be overlaid to 5000", 5000, partnerValue); - assertTrue("Partner value should come from overlay", entry.isOverlay); - } - - @Test - public void getColor_shouldReturnPartnerValueIfPresent() { - final int expectedPartnerColor = 1111; - doReturn(12345).when(mPartnerResources) - .getIdentifier(eq("suw_color_accent_dark"), eq("color"), anyString()); - doReturn(expectedPartnerColor).when(mPartnerResources).getColor(eq(12345)); - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList(createResolveInfo("test.partner.package", true, true))); - final int foundColor = Partner.getColor(mContext, R.color.suw_color_accent_dark); - assertEquals("Partner color should be overlayed to: " + expectedPartnerColor, - expectedPartnerColor, foundColor); - } - - @Test - public void getText_shouldReturnPartnerValueIfPresent() { - final CharSequence expectedPartnerText = "partner"; - doReturn(12345).when(mPartnerResources) - .getIdentifier(eq("suw_next_button_label"), eq("string"), anyString()); - doReturn(expectedPartnerText).when(mPartnerResources).getText(eq(12345)); - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Collections.singletonList(createResolveInfo("test.partner.package", true, true))); - final CharSequence partnerText = Partner.getText(mContext, R.string.suw_next_button_label); - assertThat(partnerText).isEqualTo(expectedPartnerText); + private static final String ACTION_PARTNER_CUSTOMIZATION = + "com.android.setupwizard.action.PARTNER_CUSTOMIZATION"; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Partner.resetForTesting(); + } + + @Test + public void get_withPartnerPackage_shouldReturnNonNull() { + new PartnerPackageBuilder("foo.bar") + .setIsSystem(false) + .setDirectBootAware(true) + .injectResources(); + new PartnerPackageBuilder("test.partner.package").setDirectBootAware(true).injectResources(); + + Partner partner = Partner.get(application); + assertThat(partner).isNotNull(); + assertThat(partner.getPackageName()).isEqualTo("test.partner.package"); + } + + @Test + public void get_noPartnerPackage_shouldReturnNull() { + Partner partner = Partner.get(application); + assertThat(partner).isNull(); + } + + @Test + public void get_nonSystemPartnerPackage_shouldIgnoreAndReturnNull() { + new PartnerPackageBuilder("foo.bar") + .setIsSystem(false) + .setDirectBootAware(true) + .injectResources(); + new PartnerPackageBuilder("test.partner.package") + .setIsSystem(false) + .setDirectBootAware(true) + .injectResources(); + + Partner partner = Partner.get(application); + assertThat(partner).isNull(); + } + + @Test + public void getResourceEntry_hasOverlay_shouldReturnOverlayValue() { + new PartnerPackageBuilder("test.partner.package") + .injectResources() + .putInteger("suwTransitionDuration", 5000); + + ResourceEntry entry = Partner.getResourceEntry(application, R.integer.suwTransitionDuration); + int partnerValue = entry.resources.getInteger(entry.id); + assertThat(partnerValue).named("partner value").isEqualTo(5000); + assertThat(entry.isOverlay).isTrue(); + } + + @Test + public void getColor_partnerValuePresent_shouldReturnPartnerValue() { + new PartnerPackageBuilder("test.partner.package") + .injectResources() + .putColor("suw_color_accent_dark", 0xffff00ff); + + final int color = Partner.getColor(application, R.color.suw_color_accent_dark); + assertThat(color).isEqualTo(0xffff00ff); + } + + @Test + public void getText_partnerValuePresent_shouldReturnPartnerValue() { + new PartnerPackageBuilder("test.partner.package") + .injectResources() + .putText("suw_next_button_label", "partner"); + + final CharSequence partnerText = Partner.getText(application, R.string.suw_next_button_label); + assertThat(partnerText.toString()).isEqualTo("partner"); + } + + @Test + public void getResourceEntry_partnerValueNotPresent_shouldReturnDefault() { + new PartnerPackageBuilder("test.partner.package").injectResources(); + + ResourceEntry entry = Partner.getResourceEntry(application, R.color.suw_color_accent_dark); + int partnerValue = entry.resources.getColor(entry.id); + assertThat(partnerValue).isEqualTo(0xff448aff); + assertThat(entry.isOverlay).isFalse(); + } + + @Test + public void getResourceEntry_directBootUnawareNoValueDefined_shouldReturnDefaultValue() { + new PartnerPackageBuilder("test.partner.package").injectResources(); + + ResourceEntry entry = Partner.getResourceEntry(application, R.color.suw_color_accent_dark); + int partnerValue = entry.resources.getColor(entry.id); + assertThat(partnerValue).isEqualTo(0xff448aff); + assertThat(entry.isOverlay).isFalse(); + } + + private static class PartnerPackageBuilder { + private final String packageName; + private final ResolveInfo resolveInfo; + + PartnerPackageBuilder(String packageName) { + this.packageName = packageName; + + resolveInfo = new ResolveInfo(); + resolveInfo.resolvePackageName = packageName; + ActivityInfo activityInfo = new ActivityInfo(); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.flags = ApplicationInfo.FLAG_SYSTEM; + appInfo.packageName = packageName; + activityInfo.applicationInfo = appInfo; + activityInfo.packageName = packageName; + activityInfo.name = packageName; + resolveInfo.activityInfo = activityInfo; } - @Test - public void testLoadDefaultValue() { - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Arrays.asList( - createResolveInfo("foo.bar", false, true), - createResolveInfo("test.partner.package", true, true)) - ); - - ResourceEntry entry = Partner.getResourceEntry(mContext, R.color.suw_color_accent_dark); - int partnerValue = entry.resources.getColor(entry.id); - assertEquals("Partner value should default to 0xff448aff", 0xff448aff, partnerValue); - assertFalse("Partner value should come from fallback", entry.isOverlay); - } - - @Test - public void testNotDirectBootAware() { - mPackageManager.addResolveInfoForIntent( - new Intent(ACTION_PARTNER_CUSTOMIZATION), - Collections.singletonList(createResolveInfo("test.partner.package", true, false))); - - ResourceEntry entry = Partner.getResourceEntry(mContext, R.color.suw_color_accent_dark); - int partnerValue = entry.resources.getColor(entry.id); - assertEquals("Partner value should default to 0xff448aff", 0xff448aff, partnerValue); - assertFalse("Partner value should come from fallback", entry.isOverlay); + PartnerPackageBuilder setIsSystem(boolean isSystem) { + if (isSystem) { + resolveInfo.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; + } else { + resolveInfo.activityInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; + } + return this; } - private ResolveInfo createResolveInfo( - String packageName, - boolean isSystem, - boolean directBootAware) { - ResolveInfo info = new ResolveInfo(); - info.resolvePackageName = packageName; - ActivityInfo activityInfo = new ActivityInfo(); - ApplicationInfo appInfo = new ApplicationInfo(); - appInfo.flags = isSystem ? ApplicationInfo.FLAG_SYSTEM : 0; - appInfo.packageName = packageName; - activityInfo.applicationInfo = appInfo; - activityInfo.packageName = packageName; - activityInfo.name = packageName; - if (VERSION.SDK_INT >= VERSION_CODES.N) { - activityInfo.directBootAware = directBootAware; - } - info.activityInfo = activityInfo; - return info; + PartnerPackageBuilder setDirectBootAware(boolean directBootAware) { + if (VERSION.SDK_INT >= VERSION_CODES.N) { + resolveInfo.activityInfo.directBootAware = directBootAware; + } + return this; } - @Implements(className = "android.app.ApplicationPackageManager") - public static class ShadowApplicationPackageManager extends - org.robolectric.shadows.ShadowApplicationPackageManager { - - public Resources partnerResources; - - @Implementation - @Override - public Resources getResourcesForApplication(ApplicationInfo app) - throws NameNotFoundException { - if (app != null && "test.partner.package".equals(app.packageName)) { - return partnerResources; - } else { - return super.getResourcesForApplication(app); - } - } + Resources injectResources() { + shadowOf(application.getPackageManager()) + .addResolveInfoForIntent(new Intent(ACTION_PARTNER_CUSTOMIZATION), resolveInfo); + return ExternalResources.injectExternalResources(packageName); } + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java index 20549f2..5259f6a 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java @@ -29,315 +29,320 @@ import android.os.Bundle; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; - import androidx.annotation.StyleRes; - import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) @Config(sdk = Config.NEWEST_SDK) public class WizardManagerHelperTest { - @Test - public void testGetNextIntent() { - final Intent intent = new Intent("test.intent.ACTION"); - intent.putExtra("scriptUri", "android-resource://test-script"); - intent.putExtra("actionId", "test_action_id"); - intent.putExtra("theme", "test_theme"); - intent.putExtra("ignoreExtra", "poof"); // extra is ignored because it's not known - - final Intent data = new Intent(); - data.putExtra("extraData", "shazam"); - - final Intent nextIntent = - WizardManagerHelper.getNextIntent(intent, Activity.RESULT_OK, data); - assertEquals("Next intent action should be NEXT", "com.android.wizard.NEXT", - nextIntent.getAction()); - assertEquals("Script URI should be the same as original intent", - "android-resource://test-script", nextIntent.getStringExtra("scriptUri")); - assertEquals("Action ID should be the same as original intent", "test_action_id", - nextIntent.getStringExtra("actionId")); - assertEquals("Theme extra should be the same as original intent", "test_theme", - nextIntent.getStringExtra("theme")); - assertFalse("ignoreExtra should not be in nextIntent", nextIntent.hasExtra("ignoreExtra")); - assertEquals("Result code extra should be RESULT_OK", Activity.RESULT_OK, - nextIntent.getIntExtra("com.android.setupwizard.ResultCode", 0)); - assertEquals("Extra data should surface as extra in nextIntent", "shazam", - nextIntent.getStringExtra("extraData")); - } - - @Test - public void testIsSetupWizardTrue() { - final Intent intent = new Intent(); - intent.putExtra("firstRun", true); - assertTrue("Is setup wizard should be true", - WizardManagerHelper.isSetupWizardIntent(intent)); - } - - @Test - public void testIsDeferredSetupTrue() { - final Intent intent = new Intent(); - intent.putExtra("deferredSetup", true); - assertTrue("Is deferred setup wizard should be true", - WizardManagerHelper.isDeferredSetupWizard(intent)); - } - - @Test - public void testIsPreDeferredSetupTrue() { - final Intent intent = new Intent(); - intent.putExtra("preDeferredSetup", true); - assertTrue("Is pre-deferred setup wizard should be true", - WizardManagerHelper.isPreDeferredSetupWizard(intent)); - } - - @Test - public void testIsSetupWizardFalse() { - final Intent intent = new Intent(); - intent.putExtra("firstRun", false); - assertFalse("Is setup wizard should be true", - WizardManagerHelper.isSetupWizardIntent(intent)); - } - - @Test - public void isLightTheme_shouldReturnTrue_whenThemeIsLight() { - List lightThemes = Arrays.asList( - "holo_light", - "material_light", - "glif_light", - "glif_v2_light", - "glif_v3_light" - ); - ArrayList unexpectedIntentThemes = new ArrayList<>(); - ArrayList unexpectedStringThemes = new ArrayList<>(); - for (final String theme : lightThemes) { - Intent intent = new Intent(); - intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); - if (!WizardManagerHelper.isLightTheme(intent, false)) { - unexpectedIntentThemes.add(theme); - } - if (!WizardManagerHelper.isLightTheme(theme, false)) { - unexpectedStringThemes.add(theme); - } - } - assertTrue("Intent themes " + unexpectedIntentThemes + " should be light", - unexpectedIntentThemes.isEmpty()); - assertTrue("String themes " + unexpectedStringThemes + " should be light", - unexpectedStringThemes.isEmpty()); - } - - @Test - public void isLightTheme_shouldReturnFalse_whenThemeIsNotLight() { - List lightThemes = Arrays.asList( - "holo", - "material", - "glif", - "glif_v2", - "glif_v3" - ); - ArrayList unexpectedIntentThemes = new ArrayList<>(); - ArrayList unexpectedStringThemes = new ArrayList<>(); - for (final String theme : lightThemes) { - Intent intent = new Intent(); - intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); - if (WizardManagerHelper.isLightTheme(intent, true)) { - unexpectedIntentThemes.add(theme); - } - if (WizardManagerHelper.isLightTheme(theme, true)) { - unexpectedStringThemes.add(theme); - } - } - assertTrue("Intent themes " + unexpectedIntentThemes + " should not be light", - unexpectedIntentThemes.isEmpty()); - assertTrue("String themes " + unexpectedStringThemes + " should not be light", - unexpectedStringThemes.isEmpty()); - } - - @Test - public void getThemeRes_whenOldestSupportedThemeTakeEffect_shouldReturnDefault() { - Intent intent = new Intent(); - intent.putExtra(WizardManagerHelper.EXTRA_THEME, "material"); - assertEquals(0, - WizardManagerHelper.getThemeRes(intent, 0, WizardManagerHelper.THEME_GLIF_V2)); - } - - @Test - public void getThemeRes_whenOldestSupportedThemeNotTakeEffect_shouldReturnCurrent() { - Intent intent = new Intent(); - intent.putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v3"); - assertEquals(R.style.SuwThemeGlifV3, - WizardManagerHelper.getThemeRes(intent, 0, WizardManagerHelper.THEME_GLIF_V2)); - } - - @Test - public void testIsLightThemeDefault() { - final Intent intent = new Intent(); - intent.putExtra("theme", "abracadabra"); - assertTrue("isLightTheme should return default value true", - WizardManagerHelper.isLightTheme(intent, true)); - assertFalse("isLightTheme should return default value false", - WizardManagerHelper.isLightTheme(intent, false)); - } - - @Test - public void testIsLightThemeUnspecified() { - final Intent intent = new Intent(); - assertTrue("isLightTheme should return default value true", - WizardManagerHelper.isLightTheme(intent, true)); - assertFalse("isLightTheme should return default value false", - WizardManagerHelper.isLightTheme(intent, false)); - } - - @Test - public void testGetThemeResGlifV3Light() { - assertEquals(R.style.SuwThemeGlifV3_Light, - WizardManagerHelper.getThemeRes("glif_v3_light", 0)); + @Test + public void testGetNextIntent() { + final Intent intent = new Intent("test.intent.ACTION"); + intent.putExtra("scriptUri", "android-resource://test-script"); + intent.putExtra("actionId", "test_action_id"); + intent.putExtra("theme", "test_theme"); + intent.putExtra("ignoreExtra", "poof"); // extra is ignored because it's not known + + final Intent data = new Intent(); + data.putExtra("extraData", "shazam"); + + final Intent nextIntent = WizardManagerHelper.getNextIntent(intent, Activity.RESULT_OK, data); + assertEquals( + "Next intent action should be NEXT", "com.android.wizard.NEXT", nextIntent.getAction()); + assertEquals( + "Script URI should be the same as original intent", + "android-resource://test-script", + nextIntent.getStringExtra("scriptUri")); + assertEquals( + "Action ID should be the same as original intent", + "test_action_id", + nextIntent.getStringExtra("actionId")); + assertEquals( + "Theme extra should be the same as original intent", + "test_theme", + nextIntent.getStringExtra("theme")); + assertFalse("ignoreExtra should not be in nextIntent", nextIntent.hasExtra("ignoreExtra")); + assertEquals( + "Result code extra should be RESULT_OK", + Activity.RESULT_OK, + nextIntent.getIntExtra("com.android.setupwizard.ResultCode", 0)); + assertEquals( + "Extra data should surface as extra in nextIntent", + "shazam", + nextIntent.getStringExtra("extraData")); + } + + @Test + public void testIsSetupWizardTrue() { + final Intent intent = new Intent(); + intent.putExtra("firstRun", true); + assertTrue("Is setup wizard should be true", WizardManagerHelper.isSetupWizardIntent(intent)); + } + + @Test + public void testIsDeferredSetupTrue() { + final Intent intent = new Intent(); + intent.putExtra("deferredSetup", true); + assertTrue( + "Is deferred setup wizard should be true", + WizardManagerHelper.isDeferredSetupWizard(intent)); + } + + @Test + public void testIsPreDeferredSetupTrue() { + final Intent intent = new Intent(); + intent.putExtra("preDeferredSetup", true); + assertTrue( + "Is pre-deferred setup wizard should be true", + WizardManagerHelper.isPreDeferredSetupWizard(intent)); + } + + @Test + public void testIsSetupWizardFalse() { + final Intent intent = new Intent(); + intent.putExtra("firstRun", false); + assertFalse("Is setup wizard should be true", WizardManagerHelper.isSetupWizardIntent(intent)); + } + + @Test + public void isLightTheme_shouldReturnTrue_whenThemeIsLight() { + List lightThemes = + Arrays.asList( + "holo_light", "material_light", "glif_light", "glif_v2_light", "glif_v3_light"); + ArrayList unexpectedIntentThemes = new ArrayList<>(); + ArrayList unexpectedStringThemes = new ArrayList<>(); + for (final String theme : lightThemes) { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); + if (!WizardManagerHelper.isLightTheme(intent, false)) { + unexpectedIntentThemes.add(theme); + } + if (!WizardManagerHelper.isLightTheme(theme, false)) { + unexpectedStringThemes.add(theme); + } } - - @Test - public void testGetThemeResGlifV3() { - assertEquals(R.style.SuwThemeGlifV3, - WizardManagerHelper.getThemeRes("glif_v3", 0)); - } - - @Test - public void testGetThemeResGlifV2Light() { - assertEquals(R.style.SuwThemeGlifV2_Light, - WizardManagerHelper.getThemeRes("glif_v2_light", 0)); - } - - @Test - public void testGetThemeResGlifV2() { - assertEquals(R.style.SuwThemeGlifV2, - WizardManagerHelper.getThemeRes("glif_v2", 0)); - } - - @Test - public void testGetThemeResGlifLight() { - assertEquals(R.style.SuwThemeGlif_Light, - WizardManagerHelper.getThemeRes("glif_light", 0)); - } - - @Test - public void testGetThemeResGlif() { - assertEquals(R.style.SuwThemeGlif, - WizardManagerHelper.getThemeRes("glif", 0)); - } - - @Test - public void testGetThemeResMaterialLight() { - assertEquals(R.style.SuwThemeMaterial_Light, - WizardManagerHelper.getThemeRes("material_light", 0)); - } - - @Test - public void testGetThemeResMaterial() { - assertEquals(R.style.SuwThemeMaterial, - WizardManagerHelper.getThemeRes("material", 0)); - } - - @Test - public void testGetThemeResDefault() { - @StyleRes int def = 123; - assertEquals(def, WizardManagerHelper.getThemeRes("abracadabra", def)); - } - - @Test - public void testGetThemeResNull() { - @StyleRes int def = 123; - assertEquals(def, WizardManagerHelper.getThemeRes((String) null, def)); - } - - @Test - public void testGetThemeResFromIntent() { - Intent intent = new Intent(); - intent.putExtra(WizardManagerHelper.EXTRA_THEME, "material"); - assertEquals(R.style.SuwThemeMaterial, WizardManagerHelper.getThemeRes(intent, 0)); - } - - @Test - public void testCopyWizardManagerIntent() { - Bundle wizardBundle = new Bundle(); - wizardBundle.putString("foo", "bar"); - Intent originalIntent = new Intent() - .putExtra(WizardManagerHelper.EXTRA_THEME, "test_theme") - .putExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE, wizardBundle) - .putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true) - .putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true) - .putExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, true) - // Script URI and Action ID are kept for backwards compatibility - .putExtra(WizardManagerHelper.EXTRA_SCRIPT_URI, "test_script_uri") - .putExtra(WizardManagerHelper.EXTRA_ACTION_ID, "test_action_id"); - - Intent intent = new Intent("test.intent.action"); - WizardManagerHelper.copyWizardManagerExtras(originalIntent, intent); - - assertEquals("Intent action should be kept", "test.intent.action", intent.getAction()); - assertEquals("EXTRA_THEME should be copied", - "test_theme", intent.getStringExtra(WizardManagerHelper.EXTRA_THEME)); - Bundle copiedWizardBundle = - intent.getParcelableExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE); - assertEquals("Wizard bundle should be copied", "bar", copiedWizardBundle.getString("foo")); - - assertTrue("EXTRA_IS_FIRST_RUN should be copied", - intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, false)); - assertTrue("EXTRA_IS_DEFERRED_SETUP should be copied", - intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, false)); - assertTrue("EXTRA_IS_PRE_DEFERRED_SETUP should be copied", - intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, false)); - - // Script URI and Action ID are replaced by Wizard Bundle in M, but are kept for backwards - // compatibility - assertEquals("EXTRA_SCRIPT_URI should be copied", - "test_script_uri", intent.getStringExtra(WizardManagerHelper.EXTRA_SCRIPT_URI)); - assertEquals("EXTRA_ACTION_ID should be copied", - "test_action_id", intent.getStringExtra(WizardManagerHelper.EXTRA_ACTION_ID)); - } - - @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) - @Test - public void testIsUserSetupComplete() { - Settings.Secure.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); - Settings.Secure.putInt(application.getContentResolver(), "user_setup_complete", 1); - assertTrue(WizardManagerHelper.isUserSetupComplete(application)); - - Settings.Secure.putInt(application.getContentResolver(), "user_setup_complete", 0); - assertFalse(WizardManagerHelper.isUserSetupComplete(application)); - } - - @Test - @Config(sdk = VERSION_CODES.JELLY_BEAN) - public void testIsUserSetupCompleteCompat() { - Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 1); - assertTrue(WizardManagerHelper.isUserSetupComplete(application)); - - Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 0); - assertFalse(WizardManagerHelper.isUserSetupComplete(application)); - } - - @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) - @Test - public void testIsDeviceProvisioned() { - Settings.Secure.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); - assertTrue(WizardManagerHelper.isDeviceProvisioned(application)); - Settings.Secure.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 0); - assertFalse(WizardManagerHelper.isDeviceProvisioned(application)); - } - - @Test - @Config(sdk = VERSION_CODES.JELLY_BEAN) - public void testIsDeviceProvisionedCompat() { - Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 1); - assertTrue(WizardManagerHelper.isDeviceProvisioned(application)); - Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 0); - assertFalse(WizardManagerHelper.isDeviceProvisioned(application)); + assertTrue( + "Intent themes " + unexpectedIntentThemes + " should be light", + unexpectedIntentThemes.isEmpty()); + assertTrue( + "String themes " + unexpectedStringThemes + " should be light", + unexpectedStringThemes.isEmpty()); + } + + @Test + public void isLightTheme_shouldReturnFalse_whenThemeIsNotLight() { + List lightThemes = Arrays.asList("holo", "material", "glif", "glif_v2", "glif_v3"); + ArrayList unexpectedIntentThemes = new ArrayList<>(); + ArrayList unexpectedStringThemes = new ArrayList<>(); + for (final String theme : lightThemes) { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); + if (WizardManagerHelper.isLightTheme(intent, true)) { + unexpectedIntentThemes.add(theme); + } + if (WizardManagerHelper.isLightTheme(theme, true)) { + unexpectedStringThemes.add(theme); + } } + assertTrue( + "Intent themes " + unexpectedIntentThemes + " should not be light", + unexpectedIntentThemes.isEmpty()); + assertTrue( + "String themes " + unexpectedStringThemes + " should not be light", + unexpectedStringThemes.isEmpty()); + } + + @Test + public void getThemeRes_whenOldestSupportedThemeTakeEffect_shouldReturnDefault() { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, "material"); + assertEquals(0, WizardManagerHelper.getThemeRes(intent, 0, WizardManagerHelper.THEME_GLIF_V2)); + } + + @Test + public void getThemeRes_whenOldestSupportedThemeNotTakeEffect_shouldReturnCurrent() { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, "glif_v3"); + assertEquals( + R.style.SuwThemeGlifV3, + WizardManagerHelper.getThemeRes(intent, 0, WizardManagerHelper.THEME_GLIF_V2)); + } + + @Test + public void testIsLightThemeDefault() { + final Intent intent = new Intent(); + intent.putExtra("theme", "abracadabra"); + assertTrue( + "isLightTheme should return default value true", + WizardManagerHelper.isLightTheme(intent, true)); + assertFalse( + "isLightTheme should return default value false", + WizardManagerHelper.isLightTheme(intent, false)); + } + + @Test + public void testIsLightThemeUnspecified() { + final Intent intent = new Intent(); + assertTrue( + "isLightTheme should return default value true", + WizardManagerHelper.isLightTheme(intent, true)); + assertFalse( + "isLightTheme should return default value false", + WizardManagerHelper.isLightTheme(intent, false)); + } + + @Test + public void testGetThemeResGlifV3Light() { + assertEquals(R.style.SuwThemeGlifV3_Light, WizardManagerHelper.getThemeRes("glif_v3_light", 0)); + } + + @Test + public void testGetThemeResGlifV3() { + assertEquals(R.style.SuwThemeGlifV3, WizardManagerHelper.getThemeRes("glif_v3", 0)); + } + + @Test + public void testGetThemeResGlifV2Light() { + assertEquals(R.style.SuwThemeGlifV2_Light, WizardManagerHelper.getThemeRes("glif_v2_light", 0)); + } + + @Test + public void testGetThemeResGlifV2() { + assertEquals(R.style.SuwThemeGlifV2, WizardManagerHelper.getThemeRes("glif_v2", 0)); + } + + @Test + public void testGetThemeResGlifLight() { + assertEquals(R.style.SuwThemeGlif_Light, WizardManagerHelper.getThemeRes("glif_light", 0)); + } + + @Test + public void testGetThemeResGlif() { + assertEquals(R.style.SuwThemeGlif, WizardManagerHelper.getThemeRes("glif", 0)); + } + + @Test + public void testGetThemeResMaterialLight() { + assertEquals( + R.style.SuwThemeMaterial_Light, WizardManagerHelper.getThemeRes("material_light", 0)); + } + + @Test + public void testGetThemeResMaterial() { + assertEquals(R.style.SuwThemeMaterial, WizardManagerHelper.getThemeRes("material", 0)); + } + + @Test + public void testGetThemeResDefault() { + @StyleRes int def = 123; + assertEquals(def, WizardManagerHelper.getThemeRes("abracadabra", def)); + } + + @Test + public void testGetThemeResNull() { + @StyleRes int def = 123; + assertEquals(def, WizardManagerHelper.getThemeRes((String) null, def)); + } + + @Test + public void testGetThemeResFromIntent() { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, "material"); + assertEquals(R.style.SuwThemeMaterial, WizardManagerHelper.getThemeRes(intent, 0)); + } + + @Test + public void testCopyWizardManagerIntent() { + Bundle wizardBundle = new Bundle(); + wizardBundle.putString("foo", "bar"); + Intent originalIntent = + new Intent() + .putExtra(WizardManagerHelper.EXTRA_THEME, "test_theme") + .putExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE, wizardBundle) + .putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true) + .putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true) + .putExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, true) + // Script URI and Action ID are kept for backwards compatibility + .putExtra(WizardManagerHelper.EXTRA_SCRIPT_URI, "test_script_uri") + .putExtra(WizardManagerHelper.EXTRA_ACTION_ID, "test_action_id"); + + Intent intent = new Intent("test.intent.action"); + WizardManagerHelper.copyWizardManagerExtras(originalIntent, intent); + + assertEquals("Intent action should be kept", "test.intent.action", intent.getAction()); + assertEquals( + "EXTRA_THEME should be copied", + "test_theme", + intent.getStringExtra(WizardManagerHelper.EXTRA_THEME)); + Bundle copiedWizardBundle = intent.getParcelableExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE); + assertEquals("Wizard bundle should be copied", "bar", copiedWizardBundle.getString("foo")); + + assertTrue( + "EXTRA_IS_FIRST_RUN should be copied", + intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, false)); + assertTrue( + "EXTRA_IS_DEFERRED_SETUP should be copied", + intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, false)); + assertTrue( + "EXTRA_IS_PRE_DEFERRED_SETUP should be copied", + intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, false)); + + // Script URI and Action ID are replaced by Wizard Bundle in M, but are kept for backwards + // compatibility + assertEquals( + "EXTRA_SCRIPT_URI should be copied", + "test_script_uri", + intent.getStringExtra(WizardManagerHelper.EXTRA_SCRIPT_URI)); + assertEquals( + "EXTRA_ACTION_ID should be copied", + "test_action_id", + intent.getStringExtra(WizardManagerHelper.EXTRA_ACTION_ID)); + } + + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + @Test + public void testIsUserSetupComplete() { + Settings.Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + Settings.Secure.putInt(application.getContentResolver(), "user_setup_complete", 1); + assertTrue(WizardManagerHelper.isUserSetupComplete(application)); + + Settings.Secure.putInt(application.getContentResolver(), "user_setup_complete", 0); + assertFalse(WizardManagerHelper.isUserSetupComplete(application)); + } + + @Test + @Config(sdk = VERSION_CODES.JELLY_BEAN) + public void testIsUserSetupCompleteCompat() { + Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 1); + assertTrue(WizardManagerHelper.isUserSetupComplete(application)); + + Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 0); + assertFalse(WizardManagerHelper.isUserSetupComplete(application)); + } + + @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) + @Test + public void testIsDeviceProvisioned() { + Settings.Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + assertTrue(WizardManagerHelper.isDeviceProvisioned(application)); + Settings.Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + assertFalse(WizardManagerHelper.isDeviceProvisioned(application)); + } + + @Test + @Config(sdk = VERSION_CODES.JELLY_BEAN) + public void testIsDeviceProvisionedCompat() { + Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 1); + assertTrue(WizardManagerHelper.isDeviceProvisioned(application)); + Settings.Secure.putInt(application.getContentResolver(), Secure.DEVICE_PROVISIONED, 0); + assertFalse(WizardManagerHelper.isDeviceProvisioned(application)); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java index ae4f3d1..af302f0 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java @@ -21,9 +21,7 @@ import static org.robolectric.RuntimeEnvironment.application; import android.view.View; import android.view.View.MeasureSpec; - import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; - import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; @@ -33,55 +31,58 @@ import org.robolectric.annotation.Config; @Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class FillContentLayoutTest { - @Test - public void testMeasureMinSize() { - FillContentLayout layout = new FillContentLayout( - application, - Robolectric.buildAttributeSet() - .addAttribute(android.R.attr.minWidth, "123dp") - .addAttribute(android.R.attr.minHeight, "123dp") - .build()); - layout.measure( - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + @Test + public void testMeasureMinSize() { + FillContentLayout layout = + new FillContentLayout( + application, + Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.minWidth, "123dp") + .addAttribute(android.R.attr.minHeight, "123dp") + .build()); + layout.measure( + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - assertEquals(123, layout.getMeasuredWidth()); - assertEquals(123, layout.getMeasuredHeight()); - } + assertEquals(123, layout.getMeasuredWidth()); + assertEquals(123, layout.getMeasuredHeight()); + } - @Test - public void testMeasureChildIsSmallerThanMaxSize() { - View child = new View(application); - FillContentLayout layout = new FillContentLayout( - application, - Robolectric.buildAttributeSet() - .addAttribute(android.R.attr.maxWidth, "123dp") - .addAttribute(android.R.attr.maxHeight, "123dp") - .build()); - layout.addView(child); - layout.measure( - MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY)); + @Test + public void testMeasureChildIsSmallerThanMaxSize() { + View child = new View(application); + FillContentLayout layout = + new FillContentLayout( + application, + Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.maxWidth, "123dp") + .addAttribute(android.R.attr.maxHeight, "123dp") + .build()); + layout.addView(child); + layout.measure( + MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY)); - assertEquals(123, child.getMeasuredWidth()); - assertEquals(123, child.getMeasuredHeight()); - } + assertEquals(123, child.getMeasuredWidth()); + assertEquals(123, child.getMeasuredHeight()); + } - @Test - public void testMeasureChildIsSmallerThanParent() { - View child = new View(application); - FillContentLayout layout = new FillContentLayout( - application, - Robolectric.buildAttributeSet() - .addAttribute(android.R.attr.maxWidth, "123dp") - .addAttribute(android.R.attr.maxHeight, "123dp") - .build()); - layout.addView(child); - layout.measure( - MeasureSpec.makeMeasureSpec(88, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(88, MeasureSpec.EXACTLY)); + @Test + public void testMeasureChildIsSmallerThanParent() { + View child = new View(application); + FillContentLayout layout = + new FillContentLayout( + application, + Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.maxWidth, "123dp") + .addAttribute(android.R.attr.maxHeight, "123dp") + .build()); + layout.addView(child); + layout.measure( + MeasureSpec.makeMeasureSpec(88, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(88, MeasureSpec.EXACTLY)); - assertEquals(88, child.getMeasuredWidth()); - assertEquals(88, child.getMeasuredHeight()); - } + assertEquals(88, child.getMeasuredWidth()); + assertEquals(88, child.getMeasuredHeight()); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java index a6c1808..c2f3f35 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java @@ -17,27 +17,17 @@ package com.android.setupwizardlib.view; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock; import static org.robolectric.RuntimeEnvironment.application; -import android.annotation.TargetApi; import android.app.Activity; import android.graphics.SurfaceTexture; import android.net.Uri; -import android.os.Build.VERSION_CODES; -import android.view.Surface; -import android.view.View; - import androidx.annotation.RawRes; - +import android.view.View; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; -import com.android.setupwizardlib.shadow.ShadowLog; -import com.android.setupwizardlib.shadow.ShadowMediaPlayer; -import com.android.setupwizardlib.view.IllustrationVideoViewTest.ShadowSurface; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,268 +36,205 @@ import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.Shadows; import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowMediaPlayer; import org.robolectric.shadows.ShadowMediaPlayer.InvalidStateBehavior; import org.robolectric.shadows.ShadowMediaPlayer.MediaInfo; -import org.robolectric.shadows.ShadowMediaPlayer.State; import org.robolectric.shadows.util.DataSource; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; @RunWith(SuwLibRobolectricTestRunner.class) -@Config( - sdk = Config.NEWEST_SDK, - shadows = { - ShadowLog.class, - ShadowMediaPlayer.class, - ShadowSurface.class - }) +@Config(sdk = Config.NEWEST_SDK) public class IllustrationVideoViewTest { - @Mock - private SurfaceTexture mSurfaceTexture; - - private IllustrationVideoView mView; - - private ShadowMediaPlayer mShadowMediaPlayer; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - addMediaInfo(android.R.color.white); - } - - @Test - public void onVisibilityChanged_notVisible_shouldRelease() { - - createDefaultView(); - mView.onWindowVisibilityChanged(View.GONE); - - assertThat(mShadowMediaPlayer.getState()).isEqualTo(State.END); - assertThat(mView.mSurface).isNull(); - assertThat(mView.mMediaPlayer).isNull(); - } - - @Test - public void onVisibilityChanged_visible_shouldPlay() { - createDefaultView(); - - mView.onWindowVisibilityChanged(View.GONE); - assertThat(mView.mSurface).isNull(); - assertThat(mView.mMediaPlayer).isNull(); - - mView.onWindowVisibilityChanged(View.VISIBLE); - - assertThat(mView.mSurface).isNotNull(); - assertThat(mView.mMediaPlayer).isNotNull(); - } - - @Test - public void testPausedWhenWindowFocusLost() { - createDefaultView(); - Robolectric.flushForegroundThreadScheduler(); - mView.start(); - - assertNotNull(mView.mMediaPlayer); - assertNotNull(mView.mSurface); - - mView.onWindowFocusChanged(false); - assertThat(mShadowMediaPlayer.getState()).isEqualTo(State.PAUSED); - } - - @Test - public void testStartedWhenWindowFocusRegained() { - testPausedWhenWindowFocusLost(); - - mView.onWindowFocusChanged(true); - assertThat(mShadowMediaPlayer.getState()).isEqualTo(State.STARTED); - } - - @Test - public void testSurfaceReleasedWhenTextureDestroyed() { - createDefaultView(); - mView.start(); - - assertNotNull(mView.mMediaPlayer); - assertNotNull(mView.mSurface); - - mView.onSurfaceTextureDestroyed(mSurfaceTexture); - assertThat(mShadowMediaPlayer.getState()).isEqualTo(State.END); - } - - @Test - public void testXmlSetVideoResId() { - createDefaultView(); - assertThat(mShadowMediaPlayer.getSourceUri().toString()) - .isEqualTo("android.resource://com.android.setupwizardlib/" - + android.R.color.white); - } - - @Test - public void testSetVideoResId() { - addMediaInfo(android.R.color.black); - - createDefaultView(); - - @RawRes int black = android.R.color.black; - mView.setVideoResource(black); - - mShadowMediaPlayer = (ShadowMediaPlayer) Shadows.shadowOf(mView.mMediaPlayer); - - assertThat(mShadowMediaPlayer.getSourceUri().toString()) - .isEqualTo("android.resource://com.android.setupwizardlib/" - + android.R.color.black); - } - - @Test - public void prepareVideo_shouldSetAspectRatio() { - createDefaultView(); - - mShadowMediaPlayer.setVideoSize(720, 1280); - - Robolectric.flushForegroundThreadScheduler(); - mView.start(); - - mView.measure(View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY)); - - final float aspectRatio = (float) mView.getMeasuredHeight() / mView.getMeasuredWidth(); - assertThat(aspectRatio).isWithin(0.001f).of(1280f / 720f); - } - - @Test - public void prepareVideo_zeroHeight_shouldSetAspectRatioToZero() { - createDefaultView(); - - mShadowMediaPlayer.setVideoSize(720, 0); - - Robolectric.flushForegroundThreadScheduler(); - mView.start(); - - final float aspectRatio = (float) mView.getHeight() / mView.getWidth(); - assertThat(aspectRatio).isEqualTo(0.0f); - } - - @Test - public void setVideoResId_resetDiffVideoResFromDiffPackage_videoResShouldBeSet() { - // VideoRes default set as android.R.color.white with - // default package(com.android.setupwizardlib) - createDefaultView(); - - // reset different videoRes from different package - String newPackageName = "com.android.fakepackage"; - @RawRes int black = android.R.color.black; - addMediaInfo(black, newPackageName); - mView.setVideoResource(black, newPackageName); - - // should be reset to black with the new package - mShadowMediaPlayer = (ShadowMediaPlayer) Shadows.shadowOf(mView.mMediaPlayer); - assertThat(mShadowMediaPlayer.getSourceUri().toString()) - .isEqualTo("android.resource://" + newPackageName + "/" - + android.R.color.black); - } - - @Test - public void setVideoResId_resetDiffVideoResFromSamePackage_videoResShouldBeSet() { - // VideoRes default set as android.R.color.white with - // default package(com.android.setupwizardlib) - createDefaultView(); - - // reset different videoRes from the same package(default package) - String defaultPackageName = "com.android.setupwizardlib"; - @RawRes int black = android.R.color.black; - addMediaInfo(black); - mView.setVideoResource(black, defaultPackageName); - - // should be reset to black with the same package(default package) - mShadowMediaPlayer = (ShadowMediaPlayer) Shadows.shadowOf(mView.mMediaPlayer); - assertThat(mShadowMediaPlayer.getSourceUri().toString()) - .isEqualTo("android.resource://" + defaultPackageName + "/" - + android.R.color.black); - } - - @Test - public void setVideoResId_resetSameVideoResFromDifferentPackage_videoResShouldBeSet() { - // VideoRes default set as android.R.color.white with - // default package(com.android.setupwizardlib) - createDefaultView(); - - - // reset same videoRes from different package - @RawRes int white = android.R.color.white; - String newPackageName = "com.android.fakepackage"; - addMediaInfo(white, newPackageName); - mView.setVideoResource(white, newPackageName); - - // should be white with the new package - mShadowMediaPlayer = (ShadowMediaPlayer) Shadows.shadowOf(mView.mMediaPlayer); - assertThat(mShadowMediaPlayer.getSourceUri().toString()) - .isEqualTo("android.resource://" + newPackageName + "/" - + android.R.color.white); - } - - private void createDefaultView() { - mView = new IllustrationVideoView( - application, - Robolectric.buildAttributeSet() - // Any resource attribute should work, since the DataSource is fake - .addAttribute(R.attr.suwVideo, "@android:color/white") - .build()); - - Activity activity = Robolectric.setupActivity(Activity.class); - activity.setContentView(mView); - setWindowVisible(); - - mView.setSurfaceTexture(mock(SurfaceTexture.class)); - mView.onSurfaceTextureAvailable(mSurfaceTexture, 500, 500); - mShadowMediaPlayer = (ShadowMediaPlayer) Shadows.shadowOf(mView.mMediaPlayer); - mShadowMediaPlayer.setInvalidStateBehavior(InvalidStateBehavior.EMULATE); - } - - private void setWindowVisible() { - Object viewRootImpl = ReflectionHelpers.callInstanceMethod(mView, "getViewRootImpl"); - ReflectionHelpers.callInstanceMethod( - viewRootImpl, - "handleAppVisibility", - ClassParameter.from(boolean.class, true)); - assertThat(mView.isAttachedToWindow()).isTrue(); - assertThat(mView.getWindowVisibility()).isEqualTo(View.VISIBLE); - } - - private void addMediaInfo(@RawRes int res) { - ShadowMediaPlayer.addMediaInfo( - DataSource.toDataSource( - application, - Uri.parse("android.resource://com.android.setupwizardlib/" + res), - null), - new MediaInfo(5000, 1)); - } - - private void addMediaInfo(@RawRes int res, String packageName) { - ShadowMediaPlayer.addMediaInfo( - DataSource.toDataSource( - application, - Uri.parse("android.resource://" + packageName + "/" + res), - null), - new MediaInfo(5000, 1)); - } - - @Implements(Surface.class) - @TargetApi(VERSION_CODES.HONEYCOMB) - public static class ShadowSurface extends org.robolectric.shadows.ShadowSurface { - - @RealObject - private Surface mRealSurface; - - @Override - public void __constructor__(SurfaceTexture surfaceTexture) { - // Call the constructor on the real object, so that critical fields such as mLock is - // initialized properly. - Shadow.invokeConstructor(Surface.class, mRealSurface, - ReflectionHelpers.ClassParameter.from(SurfaceTexture.class, surfaceTexture)); - super.__constructor__(surfaceTexture); - } - } + @Mock private SurfaceTexture surfaceTexture; + + private IllustrationVideoView view; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowMediaPlayer.addMediaInfo( + DataSource.toDataSource( + "android.resource://" + application.getPackageName() + "/" + android.R.color.white), + new ShadowMediaPlayer.MediaInfo(100, 10)); + ShadowMediaPlayer.addMediaInfo( + DataSource.toDataSource( + "android.resource://" + application.getPackageName() + "/" + android.R.color.black), + new ShadowMediaPlayer.MediaInfo(100, 10)); + } + + @Test + public void testPausedWhenWindowFocusLost() { + createDefaultView(); + Robolectric.flushForegroundThreadScheduler(); + view.start(); + + assertNotNull(view.mMediaPlayer); + assertNotNull(view.surface); + + view.onWindowFocusChanged(false); + assertThat(getShadowMediaPlayer().getState()).isEqualTo(ShadowMediaPlayer.State.PAUSED); + } + + @Test + public void testStartedWhenWindowFocusRegained() { + testPausedWhenWindowFocusLost(); + Robolectric.flushForegroundThreadScheduler(); + + view.onWindowFocusChanged(true); + assertThat(getShadowMediaPlayer().getState()).isEqualTo(ShadowMediaPlayer.State.STARTED); + } + + @Test + public void testSurfaceReleasedWhenTextureDestroyed() { + createDefaultView(); + view.start(); + + assertNotNull(view.mMediaPlayer); + assertNotNull(view.surface); + + // MediaPlayer is set to null after destroy. Retrieve it first before we call destroy. + ShadowMediaPlayer shadowMediaPlayer = getShadowMediaPlayer(); + view.onSurfaceTextureDestroyed(surfaceTexture); + assertThat(shadowMediaPlayer.getState()).isEqualTo(ShadowMediaPlayer.State.END); + } + + @Test + public void testXmlSetVideoResId() { + createDefaultView(); + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://com.android.setupwizardlib/" + android.R.color.white); + } + + @Test + public void testSetVideoResId() { + createDefaultView(); + + @RawRes int black = android.R.color.black; + view.setVideoResource(black); + + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://com.android.setupwizardlib/" + android.R.color.black); + } + + @Test + public void prepareVideo_shouldSetAspectRatio() { + createDefaultView(); + + ReflectionHelpers.setField(getShadowMediaPlayer(), "videoWidth", 720); + ReflectionHelpers.setField(getShadowMediaPlayer(), "videoHeight", 1280); + + Robolectric.flushForegroundThreadScheduler(); + view.start(); + + view.measure( + View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY)); + + final float aspectRatio = (float) view.getMeasuredHeight() / view.getMeasuredWidth(); + assertThat(aspectRatio).isWithin(0.001f).of(1280f / 720f); + } + + @Test + public void prepareVideo_zeroHeight_shouldSetAspectRatioToZero() { + createDefaultView(); + + ReflectionHelpers.setField(getShadowMediaPlayer(), "videoWidth", 720); + ReflectionHelpers.setField(getShadowMediaPlayer(), "videoHeight", 0); + + Robolectric.flushForegroundThreadScheduler(); + view.start(); + + final float aspectRatio = (float) view.getHeight() / view.getWidth(); + assertThat(aspectRatio).isEqualTo(0.0f); + } + + @Test + public void setVideoResId_resetDiffVideoResFromDiffPackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + // reset different videoRes from different package + String newPackageName = "com.android.fakepackage"; + @RawRes int black = android.R.color.black; + addMediaInfo(black, newPackageName); + view.setVideoResource(black, newPackageName); + + // should be reset to black with the new package + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://" + newPackageName + "/" + android.R.color.black); + } + + @Test + public void setVideoResId_resetDiffVideoResFromSamePackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + // reset different videoRes from the same package(default package) + String defaultPackageName = "com.android.setupwizardlib"; + @RawRes int black = android.R.color.black; + addMediaInfo(black, defaultPackageName); + view.setVideoResource(black, defaultPackageName); + + // should be reset to black with the same package(default package) + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://" + defaultPackageName + "/" + android.R.color.black); + } + + @Test + public void setVideoResId_resetSameVideoResFromDifferentPackage_videoResShouldBeSet() { + // VideoRes default set as android.R.color.white with + // default package(com.android.setupwizardlib) + createDefaultView(); + + // reset same videoRes from different package + @RawRes int white = android.R.color.white; + String newPackageName = "com.android.fakepackage"; + addMediaInfo(white, newPackageName); + view.setVideoResource(white, newPackageName); + + // should be white with the new package + assertThat(getShadowMediaPlayer().getSourceUri().toString()) + .isEqualTo("android.resource://" + newPackageName + "/" + android.R.color.white); + } + + private ShadowMediaPlayer getShadowMediaPlayer() { + return Shadows.shadowOf(view.mMediaPlayer); + } + + private void createDefaultView() { + view = + new IllustrationVideoView( + application, + Robolectric.buildAttributeSet() + // Any resource attribute should work, since the data source is fake + .addAttribute(R.attr.suwVideo, "@android:color/white") + .build()); + + Activity activity = Robolectric.setupActivity(Activity.class); + activity.setContentView(view); + setWindowVisible(); + + view.setSurfaceTexture(mock(SurfaceTexture.class)); + view.onSurfaceTextureAvailable(surfaceTexture, 500, 500); + getShadowMediaPlayer().setInvalidStateBehavior(InvalidStateBehavior.EMULATE); + } + + private void setWindowVisible() { + Object viewRootImpl = ReflectionHelpers.callInstanceMethod(view, "getViewRootImpl"); + ReflectionHelpers.callInstanceMethod( + viewRootImpl, "handleAppVisibility", ClassParameter.from(boolean.class, true)); + assertThat(view.isAttachedToWindow()).isTrue(); + assertThat(view.getWindowVisibility()).isEqualTo(View.VISIBLE); + } + + private void addMediaInfo(@RawRes int res, String packageName) { + ShadowMediaPlayer.addMediaInfo( + DataSource.toDataSource( + application, Uri.parse("android.resource://" + packageName + "/" + res), null), + new MediaInfo(5000, 1)); + } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java index f77de68..20d1285 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java @@ -17,7 +17,6 @@ package com.android.setupwizardlib.view; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; @@ -25,7 +24,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; @@ -39,201 +37,197 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.TextAppearanceSpan; import android.view.MotionEvent; - import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod; - +import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; -import java.util.Arrays; - @RunWith(SuwLibRobolectricTestRunner.class) -@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class RichTextViewTest { - @Test - public void testLinkAnnotation() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); - - final CharSequence text = textView.getText(); - assertTrue("Text should be spanned", text instanceof Spanned); - - assertThat(textView.getMovementMethod()).isInstanceOf(TouchableLinkMovementMethod.class); - - Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); - assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); - - spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); - assertTrue("The span should be a LinkSpan", spans[0] instanceof LinkSpan); - assertEquals("The LinkSpan should have id \"foobar\"", - "foobar", ((LinkSpan) spans[0]).getId()); - } - - @Test - public void testOnLinkClickListener() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); - - OnLinkClickListener listener = mock(OnLinkClickListener.class); - textView.setOnLinkClickListener(listener); - - assertSame(listener, textView.getOnLinkClickListener()); - - CharSequence text = textView.getText(); - LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - spans[0].onClick(textView); - - verify(listener).onLinkClick(eq(spans[0])); - } - - @Test - public void testLegacyContextOnClickListener() { - // Click listener implemented by context should still be invoked for compatibility. - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); + @Test + public void testLinkAnnotation() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); - TestContext context = spy(new TestContext(application)); - RichTextView textView = new RichTextView(context); - textView.setText(ssb); + RichTextView textView = new RichTextView(application); + textView.setText(ssb); - CharSequence text = textView.getText(); - LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); - spans[0].onClick(textView); + final CharSequence text = textView.getText(); + assertTrue("Text should be spanned", text instanceof Spanned); - verify(context).onClick(eq(spans[0])); - } - - @Test - public void onTouchEvent_clickOnLinks_shouldReturnTrue() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 0, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); - - TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); - textView.setMovementMethod(mockMovementMethod); - - MotionEvent motionEvent = - MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); - doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); - doReturn(true).when(mockMovementMethod).isLastTouchEventHandled(); - assertThat(textView.onTouchEvent(motionEvent)).isTrue(); - } - - @Test - public void onTouchEvent_clickOutsideLinks_shouldReturnFalse() { - Annotation link = new Annotation("link", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 0, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); - - TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); - textView.setMovementMethod(mockMovementMethod); - - MotionEvent motionEvent = - MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); - doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); - doReturn(false).when(mockMovementMethod).isLastTouchEventHandled(); - assertThat(textView.onTouchEvent(motionEvent)).isFalse(); - } - - @Test - public void testTextStyle() { - Annotation link = new Annotation("textAppearance", "foobar"); - SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); - ssb.setSpan(link, 1, 2, 0 /* flags */); - - RichTextView textView = new RichTextView(application); - textView.setText(ssb); + assertThat(textView.getMovementMethod()).isInstanceOf(TouchableLinkMovementMethod.class); - 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); - 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); - } + spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); + assertTrue("The span should be a LinkSpan", spans[0] instanceof LinkSpan); + assertEquals("The LinkSpan should have id \"foobar\"", "foobar", ((LinkSpan) spans[0]).getId()); + } + + @Test + public void testOnLinkClickListener() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); - @Test - public void testTextContainingLinksAreFocusable() { - Annotation testLink = new Annotation("link", "value"); - SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); - spannableStringBuilder.setSpan(testLink, 0, 3, 0); + RichTextView textView = new RichTextView(application); + textView.setText(ssb); - RichTextView view = new RichTextView(application); - view.setText(spannableStringBuilder); + OnLinkClickListener listener = mock(OnLinkClickListener.class); + textView.setOnLinkClickListener(listener); + + assertSame(listener, textView.getOnLinkClickListener()); - assertTrue("TextView should be focusable since it contains spans", view.isFocusable()); + CharSequence text = textView.getText(); + LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + spans[0].onClick(textView); + + verify(listener).onLinkClick(eq(spans[0])); + } + + @Test + public void testLegacyContextOnClickListener() { + // Click listener implemented by context should still be invoked for compatibility. + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + TestContext context = new TestContext(application); + context.delegate = mock(LinkSpan.OnClickListener.class); + RichTextView textView = new RichTextView(context); + textView.setText(ssb); + + CharSequence text = textView.getText(); + LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); + spans[0].onClick(textView); + + verify(context.delegate).onClick(eq(spans[0])); + } + + @Test + public void onTouchEvent_clickOnLinks_shouldReturnTrue() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 0, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); + textView.setMovementMethod(mockMovementMethod); + + MotionEvent motionEvent = MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); + doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); + doReturn(true).when(mockMovementMethod).isLastTouchEventHandled(); + assertThat(textView.onTouchEvent(motionEvent)).isTrue(); + } + + @Test + public void onTouchEvent_clickOutsideLinks_shouldReturnFalse() { + Annotation link = new Annotation("link", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 0, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); + textView.setMovementMethod(mockMovementMethod); + + MotionEvent motionEvent = MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); + doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); + doReturn(false).when(mockMovementMethod).isLastTouchEventHandled(); + assertThat(textView.onTouchEvent(motionEvent)).isFalse(); + } + + @Test + public void testTextStyle() { + Annotation link = new Annotation("textAppearance", "foobar"); + SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); + ssb.setSpan(link, 1, 2, 0 /* flags */); + + RichTextView textView = new RichTextView(application); + textView.setText(ssb); + + final CharSequence text = textView.getText(); + assertTrue("Text should be spanned", text instanceof Spanned); + + Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); + assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); + + spans = ((Spanned) text).getSpans(0, text.length(), TextAppearanceSpan.class); + assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); + assertTrue("The span should be a TextAppearanceSpan", spans[0] instanceof TextAppearanceSpan); + } + + @Test + public void testTextContainingLinksAreFocusable() { + Annotation testLink = new Annotation("link", "value"); + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); + spannableStringBuilder.setSpan(testLink, 0, 3, 0); + + RichTextView view = new RichTextView(application); + view.setText(spannableStringBuilder); + + assertTrue("TextView should be focusable since it contains spans", view.isFocusable()); + } + + @SuppressLint("SetTextI18n") // It's OK. This is just a test. + @Test + public void testTextContainingNoLinksAreNotFocusable() { + RichTextView textView = new RichTextView(application); + textView.setText("Thou shall not be focusable!"); + + assertFalse( + "TextView should not be focusable since it does not contain any span", + textView.isFocusable()); + } + + // Based on the text contents of the text view, the "focusable" property of the element + // should also be automatically changed. + @SuppressLint("SetTextI18n") // It's OK. This is just a test. + @Test + public void testRichTextViewFocusChangesWithTextChange() { + RichTextView textView = new RichTextView(application); + textView.setText("Thou shall not be focusable!"); + + assertFalse(textView.isFocusable()); + assertFalse(textView.isFocusableInTouchMode()); + + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("I am focusable"); + spannableStringBuilder.setSpan(new Annotation("link", "focus:on_me"), 0, 1, 0); + textView.setText(spannableStringBuilder); + assertTrue(textView.isFocusable()); + if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { + assertTrue(textView.isFocusableInTouchMode()); + assertFalse(textView.getRevealOnFocusHint()); + } else { + assertFalse(textView.isFocusableInTouchMode()); } + } + public static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { - @SuppressLint("SetTextI18n") // It's OK. This is just a test. - @Test - public void testTextContainingNoLinksAreNotFocusable() { - RichTextView textView = new RichTextView(application); - textView.setText("Thou shall not be focusable!"); + LinkSpan.OnClickListener delegate; - assertFalse("TextView should not be focusable since it does not contain any span", - textView.isFocusable()); + public TestContext(Context base) { + super(base); } - - // Based on the text contents of the text view, the "focusable" property of the element - // should also be automatically changed. - @SuppressLint("SetTextI18n") // It's OK. This is just a test. - @Test - public void testRichTextViewFocusChangesWithTextChange() { - RichTextView textView = new RichTextView(application); - textView.setText("Thou shall not be focusable!"); - - assertFalse(textView.isFocusable()); - assertFalse(textView.isFocusableInTouchMode()); - - SpannableStringBuilder spannableStringBuilder = - new SpannableStringBuilder("I am focusable"); - spannableStringBuilder.setSpan(new Annotation("link", "focus:on_me"), 0, 1, 0); - textView.setText(spannableStringBuilder); - assertTrue(textView.isFocusable()); - if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { - assertTrue(textView.isFocusableInTouchMode()); - assertFalse(textView.getRevealOnFocusHint()); - } else { - assertFalse(textView.isFocusableInTouchMode()); - } - } - - public static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { - - public TestContext(Context base) { - super(base); - } - - @Override - public void onClick(LinkSpan span) { - // Ignore. Can be verified using Mockito - } + @Override + public void onClick(LinkSpan span) { + if (delegate != null) { + delegate.onClick(span); + } } + } } -- cgit v1.2.3 From f48bfd7ec2e5d19775a35b187c6a30b5712199d7 Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Fri, 24 Aug 2018 14:16:17 -0700 Subject: Remove style checks and upgrade Robolectric Bug: 112048937 Test: ./gradlew test Change-Id: I4aa4bda4d5b422c49bada65f9e27bffc931bac15 --- PREUPLOAD.cfg | 3 --- library/self.gradle | 4 ++-- tools/checkstyle/checkstyle.xml | 20 -------------------- tools/checkstyle/checkstyle_suppression.xml | 14 -------------- 4 files changed, 2 insertions(+), 39 deletions(-) delete mode 100644 tools/checkstyle/checkstyle.xml delete mode 100644 tools/checkstyle/checkstyle_suppression.xml diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index b45eaff..e6fe0c4 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,7 +1,4 @@ [Hook Scripts] -checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py - --sha ${PREUPLOAD_COMMIT} - --config_xml tools/checkstyle/checkstyle.xml [Builtin Hooks] commit_msg_test_field = true diff --git a/library/self.gradle b/library/self.gradle index 78e5011..f649581 100644 --- a/library/self.gradle +++ b/library/self.gradle @@ -51,8 +51,8 @@ android.sourceSets { java.srcDirs = ['test/robotest/src'] dependencies { - testImplementation 'org.robolectric:robolectric:3.6.1' - testImplementation 'org.robolectric:shadows-framework:3.6.1' + testImplementation 'org.robolectric:robolectric:4.0-alpha-3' + testImplementation 'org.robolectric:shadows-framework:4.0-alpha-3' testImplementation 'junit:junit:4.+' testImplementation 'com.google.truth:truth:0.31' testImplementation 'org.mockito:mockito-core:1.9.5' diff --git a/tools/checkstyle/checkstyle.xml b/tools/checkstyle/checkstyle.xml deleted file mode 100644 index 0dbccae..0000000 --- a/tools/checkstyle/checkstyle.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -]> - - - &defaultModuleChecks; - &defaultCopyrightCheck; - - &defaultJavadocChecks; - &defaultTreewalkerChecks; - - - - - - diff --git a/tools/checkstyle/checkstyle_suppression.xml b/tools/checkstyle/checkstyle_suppression.xml deleted file mode 100644 index 6bf7b21..0000000 --- a/tools/checkstyle/checkstyle_suppression.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - -- cgit v1.2.3 From 4f3639cb9e0edefcd933954fc5d188d538e4c378 Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Wed, 10 Oct 2018 19:00:34 -0700 Subject: Import updated Android Setup Wizard Library 216631487 PiperOrigin-RevId: 216631487 Change-Id: I99d2441dff841fa14517e8c53368a0ce40f6ee2d --- library/gingerbread/AndroidManifest.xml | 23 +++ library/grandfathered_lint_checks.txt | 0 library/main/res/values-night/styles.xml | 26 +++ library/main/res/values/styles.xml | 6 + .../android/setupwizardlib/util/ThemeResolver.java | 219 +++++++++++++++++++++ .../setupwizardlib/util/WizardManagerHelper.java | 136 ++++++------- library/platform/AndroidManifest.xml | 23 +++ .../setupwizardlib/util/ThemeResolverTest.java | 216 ++++++++++++++++++++ .../util/WizardManagerHelperTest.java | 19 ++ 9 files changed, 597 insertions(+), 71 deletions(-) create mode 100644 library/gingerbread/AndroidManifest.xml create mode 100644 library/grandfathered_lint_checks.txt create mode 100644 library/main/res/values-night/styles.xml create mode 100644 library/main/src/com/android/setupwizardlib/util/ThemeResolver.java create mode 100644 library/platform/AndroidManifest.xml create mode 100644 library/test/robotest/src/com/android/setupwizardlib/util/ThemeResolverTest.java diff --git a/library/gingerbread/AndroidManifest.xml b/library/gingerbread/AndroidManifest.xml new file mode 100644 index 0000000..bf7d42f --- /dev/null +++ b/library/gingerbread/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/library/grandfathered_lint_checks.txt b/library/grandfathered_lint_checks.txt new file mode 100644 index 0000000..e69de29 diff --git a/library/main/res/values-night/styles.xml b/library/main/res/values-night/styles.xml new file mode 100644 index 0000000..912e149 --- /dev/null +++ b/library/main/res/values-night/styles.xml @@ -0,0 +1,26 @@ + + + + + + + + + - - - - - - - - diff --git a/navigationbar/src/com/android/setupwizard/navigationbar/SetupWizardNavBar.java b/navigationbar/src/com/android/setupwizard/navigationbar/SetupWizardNavBar.java deleted file mode 100644 index e34ad6c..0000000 --- a/navigationbar/src/com/android/setupwizard/navigationbar/SetupWizardNavBar.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.setupwizard.navigationbar; - -import android.app.Activity; -import android.app.Fragment; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.os.Bundle; -import android.util.AttributeSet; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnPreDrawListener; -import android.widget.Button; - -/** - * Fragment class for controlling the custom navigation bar shown during setup wizard. Apps in the - * Android tree can use this by including the common.mk makefile. Apps outside of the tree can - * create a library project out of the source. - */ -public class SetupWizardNavBar extends Fragment implements OnPreDrawListener, OnClickListener { - private static final String TAG = "SetupWizardNavBar"; - private static final int IMMERSIVE_FLAGS = - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - private int mSystemUiFlags = IMMERSIVE_FLAGS | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - - private ViewGroup mNavigationBarView; - private Button mNextButton; - private Button mBackButton; - private NavigationBarListener mCallback; - - public interface NavigationBarListener { - public void onNavigationBarCreated(SetupWizardNavBar bar); - public void onNavigateBack(); - public void onNavigateNext(); - } - - public SetupWizardNavBar() { - // no-arg constructor for fragments - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mCallback = (NavigationBarListener) activity; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Context context = new ContextThemeWrapper(getActivity(), getNavbarTheme()); - inflater = LayoutInflater.from(context); - mNavigationBarView = (ViewGroup) inflater.inflate(R.layout.setup_wizard_navbar_layout, - container, false); - mNextButton = (Button) mNavigationBarView.findViewById(R.id.setup_wizard_navbar_next); - mBackButton = (Button) mNavigationBarView.findViewById(R.id.setup_wizard_navbar_back); - mNextButton.setOnClickListener(this); - mBackButton.setOnClickListener(this); - return mNavigationBarView; - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mCallback.onNavigationBarCreated(this); - mNavigationBarView.setSystemUiVisibility(mSystemUiFlags); - - // Set the UI flags before draw because the visibility might change in unexpected / - // undetectable times, like transitioning from a finishing activity that had a keyboard - ViewTreeObserver viewTreeObserver = mNavigationBarView.getViewTreeObserver(); - viewTreeObserver.addOnPreDrawListener(this); - } - - @Override - public boolean onPreDraw() { - // View.setSystemUiVisibility checks if the visibility changes before applying them - // so the performance impact is contained - mNavigationBarView.setSystemUiVisibility(mSystemUiFlags); - return true; - } - - /** - * Sets whether system navigation bar should be hidden. - * @param useImmersiveMode True to activate immersive mode and hide the system navigation bar - */ - public void setUseImmersiveMode(boolean useImmersiveMode) { - // By default, enable layoutHideNavigation if immersive mode is used - setUseImmersiveMode(useImmersiveMode, useImmersiveMode); - } - - public void setUseImmersiveMode(boolean useImmersiveMode, boolean layoutHideNavigation) { - if (useImmersiveMode) { - mSystemUiFlags |= IMMERSIVE_FLAGS; - if (layoutHideNavigation) { - mSystemUiFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - } - } else { - mSystemUiFlags &= ~(IMMERSIVE_FLAGS | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - } - mNavigationBarView.setSystemUiVisibility(mSystemUiFlags); - } - - private int getNavbarTheme() { - // Normally we can automatically guess the theme by comparing the foreground color against - // the background color. But we also allow specifying explicitly using - // setup_wizard_navbar_theme. - TypedArray attributes = getActivity().obtainStyledAttributes( - new int[] { - R.attr.setup_wizard_navbar_theme, - android.R.attr.colorForeground, - android.R.attr.colorBackground }); - int theme = attributes.getResourceId(0, 0); - if (theme == 0) { - // Compare the value of the foreground against the background color to see if current - // theme is light-on-dark or dark-on-light. - float[] foregroundHsv = new float[3]; - float[] backgroundHsv = new float[3]; - Color.colorToHSV(attributes.getColor(1, 0), foregroundHsv); - Color.colorToHSV(attributes.getColor(2, 0), backgroundHsv); - boolean isDarkBg = foregroundHsv[2] > backgroundHsv[2]; - theme = isDarkBg ? R.style.setup_wizard_navbar_theme_dark : - R.style.setup_wizard_navbar_theme_light; - } - attributes.recycle(); - return theme; - } - - @Override - public void onClick(View v) { - if (v == mBackButton) { - mCallback.onNavigateBack(); - } else if (v == mNextButton) { - mCallback.onNavigateNext(); - } - } - - public Button getBackButton() { - return mBackButton; - } - - public Button getNextButton() { - return mNextButton; - } - - public static class NavButton extends Button { - - public NavButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public NavButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public NavButton(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public NavButton(Context context) { - super(context); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - // The color of the button is #de000000 / #deffffff when enabled. When disabled, apply - // additional 23% alpha, so the overall opacity is 20%. - setAlpha(enabled ? 1.0f : 0.23f); - } - } - -} -- cgit v1.2.3 From 4b8e9591a7732092a4bcdde83a3b5c7033632227 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 11 Dec 2018 10:33:29 -0800 Subject: Import translations. DO NOT MERGE Change-Id: Iee0c1c9e69166c75a1ff38921bef6cd2b0983278 Auto-generated-cl: translation import --- library/main/res/values-mr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/main/res/values-mr/strings.xml b/library/main/res/values-mr/strings.xml index a529655..5c5b6c2 100644 --- a/library/main/res/values-mr/strings.xml +++ b/library/main/res/values-mr/strings.xml @@ -17,7 +17,7 @@ - "पुढील" + "पुढे जा" "मागे" "अधिक" -- cgit v1.2.3 From 73cd9206623c06fa65fda9902f8be9497fffce6b Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 25 Dec 2018 17:59:51 -0800 Subject: Import translations. DO NOT MERGE Change-Id: Iaf1bf87c6b64b5192cc81c87432cc7ee66ed99df Auto-generated-cl: translation import --- library/main/res/values-hi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/main/res/values-hi/strings.xml b/library/main/res/values-hi/strings.xml index 3fb41d3..ec2cd77 100644 --- a/library/main/res/values-hi/strings.xml +++ b/library/main/res/values-hi/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "आगे बढ़ें" "पीछे" - "अधिक" + "ज़्यादा" -- cgit v1.2.3 From 83ee4b509d4620e63fdf7939f8770131335b839f Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Sat, 2 Feb 2019 00:49:49 -0800 Subject: Import translations. DO NOT MERGE Change-Id: I7c658a65c5299b6ace02a478289a0a7e910874e7 Auto-generated-cl: translation import --- library/main/res/values-en-rXC/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/main/res/values-en-rXC/strings.xml b/library/main/res/values-en-rXC/strings.xml index 693af6b..7a7836b 100644 --- a/library/main/res/values-en-rXC/strings.xml +++ b/library/main/res/values-en-rXC/strings.xml @@ -17,7 +17,7 @@ - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‏‎‏‎‎Next‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎Back‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‎More‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‏‎‏‎‎Next‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎Back‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‎More‎‏‎‎‏‎" -- cgit v1.2.3 From f2abf71d833633b5c78249164b471e07d12dcfb7 Mon Sep 17 00:00:00 2001 From: Cyril Lee Date: Thu, 28 Mar 2019 22:41:29 +0800 Subject: Support isSetupFlow extra in the deprecated setupwizardlib library The isSetupFlow extra is introduced in SetupCompat/SetupDesign library. However, the client use this setupwizardlib library will lead WizardScriptHelper#isAnySetupWizard() of SetupCompat not work from Android Q since isSetupFlow extra will be truncated. Success log for after patching setupwizardlib and replacing FactoryOta http://gpaste/5367975093731328 Bug: b/129445834 Test: mma and manual test Change-Id: Ia5ba16d2bf3d3b3e489a52369f234819165d5de7 --- .../src/com/android/setupwizardlib/util/WizardManagerHelper.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java index 2342fa5..4d75c78 100644 --- a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java +++ b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java @@ -47,6 +47,7 @@ public class WizardManagerHelper { @VisibleForTesting static final String EXTRA_IS_FIRST_RUN = "firstRun"; @VisibleForTesting static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup"; @VisibleForTesting static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup"; + @VisibleForTesting public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow"; public static final String EXTRA_THEME = "theme"; public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode"; @@ -143,7 +144,11 @@ public class WizardManagerHelper { public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) { dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE)); for (String key : - Arrays.asList(EXTRA_IS_FIRST_RUN, EXTRA_IS_DEFERRED_SETUP, EXTRA_IS_PRE_DEFERRED_SETUP)) { + Arrays.asList( + EXTRA_IS_FIRST_RUN, + EXTRA_IS_DEFERRED_SETUP, + EXTRA_IS_PRE_DEFERRED_SETUP, + EXTRA_IS_SETUP_FLOW)) { dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false)); } -- cgit v1.2.3