diff options
89 files changed, 2087 insertions, 843 deletions
@@ -0,0 +1,4 @@ +russellbrenner@google.com +ajayns@google.com +iofir@google.com +yukl@google.com diff --git a/library/Android.mk b/library/Android.mk index b0fa9f0..ff92194 100644 --- a/library/Android.mk +++ b/library/Android.mk @@ -26,11 +26,9 @@ include $(BUILD_STATIC_JAVA_LIBRARY) include $(CLEAR_VARS) -ifeq ($(TARGET_BUILD_APPS),) -# Use AAPT2 only when TARGET_BUILD_APPS is empty because AAPT2 is not compatible with the current -# setup of prebuilt support libs used in unbundled builds. b/29836407 LOCAL_USE_AAPT2 := true -endif + +LOCAL_AAPT2_ONLY := true LOCAL_MANIFEST_FILE := main/AndroidManifest.xml LOCAL_MODULE := setup-wizard-lib-gingerbread-compat @@ -41,8 +39,6 @@ LOCAL_RESOURCE_DIR := \ LOCAL_SDK_VERSION := current LOCAL_SRC_FILES := $(call all-java-files-under, main/src gingerbread/src recyclerview/src) -ifdef LOCAL_USE_AAPT2 - LOCAL_JAVA_LIBRARIES := \ android-support-annotations @@ -52,25 +48,4 @@ LOCAL_SHARED_ANDROID_LIBRARIES := \ android-support-v7-appcompat \ android-support-v7-recyclerview -else - -LOCAL_AAPT_FLAGS := --auto-add-overlay \ - --extra-packages android.support.compat \ - --extra-packages android.support.v7.appcompat \ - --extra-packages android.support.v7.recyclerview - -LOCAL_RESOURCE_DIR += \ - prebuilts/sdk/current/supportcompat/res \ - prebuilts/sdk/current/supportv7/appcompat/res \ - prebuilts/sdk/current/supportv7/recyclerview/res - -LOCAL_JAVA_LIBRARIES := \ - android-support-annotations \ - android-support-compat \ - android-support-core-ui \ - android-support-v7-appcompat \ - android-support-v7-recyclerview - -endif - include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/library/common-gingerbread.mk b/library/common-gingerbread.mk index 02642f2..70231df 100644 --- a/library/common-gingerbread.mk +++ b/library/common-gingerbread.mk @@ -15,46 +15,6 @@ # Path to directory of setup wizard lib (e.g. frameworks/opt/setupwizard/library) suwlib_dir := $(dir $(lastword $(MAKEFILE_LIST))) -ifneq ($(LOCAL_USE_AAPT2),true) - -# Check that LOCAL_RESOURCE_DIR is defined -ifeq (,$(LOCAL_RESOURCE_DIR)) -$(error LOCAL_RESOURCE_DIR must be defined) -endif - -# Add --auto-add-overlay flag if not present -ifeq (,$(findstring --auto-add-overlay, $(LOCAL_AAPT_FLAGS))) -LOCAL_AAPT_FLAGS += --auto-add-overlay -endif - -# Include setup wizard library, if not already included -ifeq (,$(findstring setup-wizard-lib-gingerbread-compat,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += \ - $(suwlib_dir)/main/res \ - $(suwlib_dir)/gingerbread/res \ - $(suwlib_dir)/recyclerview/res -LOCAL_AAPT_FLAGS += --extra-packages com.android.setupwizardlib -LOCAL_STATIC_JAVA_LIBRARIES += setup-wizard-lib-gingerbread-compat -endif - -## Include transitive dependencies below - -# Include support-v7-appcompat, if not already included -ifeq (,$(findstring android-support-v7-appcompat,$(LOCAL_STATIC_JAVA_LIBRARIES))) -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 += 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 - -else # LOCAL_USE_AAPT2 := true - ifeq (,$(findstring setup-wizard-lib-gingerbread-compat,$(LOCAL_STATIC_ANDROID_LIBRARIES))) LOCAL_STATIC_ANDROID_LIBRARIES += setup-wizard-lib-gingerbread-compat endif @@ -66,5 +26,3 @@ endif ifeq (,$(findstring android-support-v7-recyclerview,$(LOCAL_STATIC_ANDROID_LIBRARIES))) LOCAL_STATIC_ANDROID_LIBRARIES += android-support-v7-recyclerview endif - -endif # LOCAL_USE_AAPT2 diff --git a/library/common.mk b/library/common-platform-deprecated.mk index 3372cf2..bc190d0 100644 --- a/library/common.mk +++ b/library/common-platform-deprecated.mk @@ -1,3 +1,4 @@ +# DEPRECATED: This variant is no longer maintained. Use common-gingerbread instead # # Include this make file to build your application against this module. # @@ -9,7 +10,7 @@ # LOCAL_RESOURCE_DIR := \ # $(LOCAL_PATH)/res # -# include frameworks/opt/setupwizard/library/common.mk +# include frameworks/opt/setupwizard/library/common-platform-deprecated.mk # # Path to directory of setup wizard lib (e.g. frameworks/opt/setupwizard/library) diff --git a/library/gingerbread/res/layout/suw_items_expandable_switch.xml b/library/gingerbread/res/layout/suw_items_expandable_switch.xml index 2b98a9f..21c2c22 100644 --- a/library/gingerbread/res/layout/suw_items_expandable_switch.xml +++ b/library/gingerbread/res/layout/suw_items_expandable_switch.xml @@ -20,7 +20,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?android:colorBackground" - android:descendantFocusability="blocksDescendants" android:orientation="horizontal" android:tag="noBackground"> diff --git a/library/gingerbread/res/layout/suw_items_switch.xml b/library/gingerbread/res/layout/suw_items_switch.xml index af326b2..3f101d7 100644 --- a/library/gingerbread/res/layout/suw_items_switch.xml +++ b/library/gingerbread/res/layout/suw_items_switch.xml @@ -20,7 +20,6 @@ style="@style/SuwItemContainer.Verbose" android:layout_width="match_parent" android:layout_height="wrap_content" - android:descendantFocusability="blocksDescendants" android:orientation="horizontal"> <FrameLayout diff --git a/library/gingerbread/res/values-v27/styles.xml b/library/gingerbread/res/values-v27/styles.xml new file mode 100644 index 0000000..305a55e --- /dev/null +++ b/library/gingerbread/res/values-v27/styles.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:tools="http://schemas.android.com/tools"> + + <!-- Not needed for dark theme, as default nav bar bg color is black. We need a separate style + override here since windowLightNavigationBar is new in v27, and these two styles need to be + applied together as a unit. --> + <style name="SuwThemeGlifV3.Light" parent="SuwBaseThemeGlifV3.Light"> + <item name="android:navigationBarColor">@color/suw_glif_v3_nav_bar_color_light</item> + <!-- Ignore NewApi: For some reason lint seems to think this API is new in v28 (b/73514594) --> + <item name="android:navigationBarDividerColor" tools:ignore="NewApi">@color/suw_glif_v3_nav_bar_divider_color_light</item> + <!-- Ignore NewApi: For some reason lint seems to think this API is new in v28 (b/73514594) --> + <item name="android:windowLightNavigationBar" tools:ignore="NewApi">true</item> + </style> +</resources> diff --git a/library/gingerbread/res/values/styles.xml b/library/gingerbread/res/values/styles.xml index 6e525ef..241f037 100644 --- a/library/gingerbread/res/values/styles.xml +++ b/library/gingerbread/res/values/styles.xml @@ -20,6 +20,7 @@ <!-- General styles --> <style name="SuwThemeMaterial" parent="Theme.AppCompat.NoActionBar"> + <item name="android:colorBackground">@color/suw_color_background_dark</item> <item name="android:indeterminateTint" tools:ignore="NewApi">@color/suw_progress_bar_color_dark</item> <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> @@ -38,6 +39,8 @@ <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="listPreferredItemPaddingLeft">?attr/suwMarginSides</item> <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> + <item name="suwButtonAllCaps">true</item> + <item name="suwButtonFontFamily">sans-serif</item> <item name="suwCardBackground">@drawable/suw_card_bg_dark</item> <item name="suwDividerInsetEnd">0dp</item> <item name="suwDividerInsetStart">@dimen/suw_items_icon_divider_inset</item> @@ -51,6 +54,7 @@ </style> <style name="SuwThemeMaterial.Light" parent="Theme.AppCompat.Light.NoActionBar"> + <item name="android:colorBackground">@color/suw_color_background_light</item> <item name="android:indeterminateTint" tools:ignore="NewApi">@color/suw_progress_bar_color_light</item> <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item> @@ -69,6 +73,8 @@ <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="listPreferredItemPaddingLeft">?attr/suwMarginSides</item> <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> + <item name="suwButtonAllCaps">true</item> + <item name="suwButtonFontFamily">sans-serif</item> <item name="suwCardBackground">@drawable/suw_card_bg_light</item> <item name="suwDividerInsetEnd">0dp</item> <item name="suwDividerInsetStart">@dimen/suw_items_icon_divider_inset</item> @@ -98,15 +104,19 @@ <item name="android:windowSoftInputMode">adjustResize</item> <item name="colorAccent">@color/suw_color_accent_glif_dark</item> - <item name="colorPrimary">@color/suw_color_accent_glif_dark</item> + <item name="colorPrimary">?attr/colorAccent</item> <item name="listPreferredItemPaddingLeft">?attr/suwMarginSides</item> <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> + <item name="suwButtonAllCaps">true</item> + <item name="suwButtonCornerRadius">@dimen/suw_glif_button_corner_radius</item> + <item name="suwButtonFontFamily">sans-serif</item> <item name="suwColorPrimary">?attr/colorPrimary</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> <item name="suwDividerInsetStart">@dimen/suw_items_glif_icon_divider_inset</item> <item name="suwDividerInsetStartNoIcon">@dimen/suw_items_glif_text_divider_inset</item> <item name="suwGlifHeaderGravity">start</item> + <item name="suwGlifIconStyle">@style/SuwGlifIcon</item> <item name="suwItemDescriptionStyle">@style/SuwItemContainer.Description.Glif</item> <item name="suwItemDescriptionTitleStyle">@style/SuwItemTitle.GlifDescription</item> <item name="suwListItemIconColor">@color/suw_list_item_icon_color_dark</item> @@ -133,15 +143,19 @@ <item name="android:windowSoftInputMode">adjustResize</item> <item name="colorAccent">@color/suw_color_accent_glif_light</item> - <item name="colorPrimary">@color/suw_color_accent_glif_light</item> + <item name="colorPrimary">?attr/colorAccent</item> <item name="listPreferredItemPaddingLeft">?attr/suwMarginSides</item> <item name="listPreferredItemPaddingRight">?attr/suwMarginSides</item> + <item name="suwButtonAllCaps">true</item> + <item name="suwButtonCornerRadius">@dimen/suw_glif_button_corner_radius</item> + <item name="suwButtonFontFamily">sans-serif</item> <item name="suwColorPrimary">?attr/colorPrimary</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> <item name="suwDividerInsetStart">@dimen/suw_items_glif_icon_divider_inset</item> <item name="suwDividerInsetStartNoIcon">@dimen/suw_items_glif_text_divider_inset</item> <item name="suwGlifHeaderGravity">start</item> + <item name="suwGlifIconStyle">@style/SuwGlifIcon</item> <item name="suwItemDescriptionStyle">@style/SuwItemContainer.Description.Glif</item> <item name="suwItemDescriptionTitleStyle">@style/SuwItemTitle.GlifDescription</item> <item name="suwListItemIconColor">@color/suw_list_item_icon_color_light</item> @@ -151,6 +165,21 @@ <item name="textAppearanceListItemSmall">@style/TextAppearance.SuwGlifItemSummary</item> </style> + <style name="SuwThemeGlifV3" parent="SuwThemeGlifV2"> + <item name="colorAccent">@color/suw_color_accent_glif_v3</item> + <item name="suwButtonAllCaps">false</item> + <item name="suwButtonCornerRadius">@dimen/suw_glif_v3_button_corner_radius</item> + <item name="suwButtonFontFamily">@string/suwFontSecondaryMedium</item> + </style> + + <style name="SuwBaseThemeGlifV3.Light" parent="SuwThemeGlifV2.Light"> + <item name="colorAccent">@color/suw_color_accent_glif_v3</item> + <item name="suwButtonAllCaps">false</item> + <item name="suwButtonCornerRadius">@dimen/suw_glif_v3_button_corner_radius</item> + <item name="suwButtonFontFamily">@string/suwFontSecondaryMedium</item> + </style> + <style name="SuwThemeGlifV3.Light" parent="SuwBaseThemeGlifV3.Light" /> + <!-- Content styles --> <style name="TextAppearance.SuwDescription" parent="TextAppearance.AppCompat.Medium"> @@ -197,11 +226,18 @@ ContextThemeWrapper. These self-referencing attributes make sure this is applied as both to the button. --> <item name="android:buttonStyle">@style/SuwGlifButton.Primary</item> + <item name="android:theme">@style/SuwGlifButton.Primary</item> <item name="buttonStyle">@style/SuwGlifButton.Primary</item> <!-- Values used in styles --> + <item name="android:fontFamily" tools:targetApi="jelly_bean">?attr/suwButtonFontFamily</item> <item name="android:paddingLeft">@dimen/suw_glif_button_padding</item> <item name="android:paddingRight">@dimen/suw_glif_button_padding</item> + <item name="android:textAllCaps" tools:targetApi="ice_cream_sandwich">?attr/suwButtonAllCaps</item> + <item name="textAllCaps">?attr/suwButtonAllCaps</item> + + <!-- Values used in themes --> + <item name="android:buttonCornerRadius" tools:ignore="NewApi">?attr/suwButtonCornerRadius</item> </style> <style name="SuwGlifButton.Secondary" parent="Widget.AppCompat.Button.Borderless.Colored"> @@ -213,11 +249,15 @@ <item name="buttonStyle">@style/SuwGlifButton.Secondary</item> <!-- Values used in styles --> + <item name="android:fontFamily" tools:targetApi="jelly_bean">?attr/suwButtonFontFamily</item> <item name="android:minWidth">0dp</item> <item name="android:paddingLeft">@dimen/suw_glif_button_padding</item> <item name="android:paddingRight">@dimen/suw_glif_button_padding</item> + <item name="android:textAllCaps" tools:targetApi="ice_cream_sandwich">?attr/suwButtonAllCaps</item> + <item name="textAllCaps">?attr/suwButtonAllCaps</item> <!-- Values used in themes --> + <item name="android:buttonCornerRadius" tools:ignore="NewApi">?attr/suwButtonCornerRadius</item> <item name="android:colorControlHighlight" tools:targetApi="lollipop">@color/suw_flat_button_highlight</item> <item name="colorControlHighlight">@color/suw_flat_button_highlight</item> </style> @@ -234,6 +274,13 @@ <item name="android:background">?attr/colorPrimary</item> </style> + <style name="SuwBase.ProgressBarLarge" parent="@android:style/Widget.ProgressBar.Large" /> + + <style name="SuwFourColorIndeterminateProgressBar" parent="SuwBase.ProgressBarLarge"> + <item name="android:layout_gravity">center</item> + <item name="android:indeterminate">true</item> + </style> + <!-- Navigation bar styles --> <style name="SuwNavBarButtonStyle" parent="@android:style/Widget.Button"> @@ -258,4 +305,8 @@ <item name="suwNavBarButtonBackground">@drawable/suw_navbar_btn_bg_light</item> </style> + + <style name="SuwAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert" /> + + <style name="SuwAlertDialogTheme.Light" parent="Theme.AppCompat.Light.Dialog.Alert" /> </resources> diff --git a/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java b/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java index be9916e..71d1bb6 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java +++ b/library/gingerbread/src/com/android/setupwizardlib/items/ExpandableSwitchItem.java @@ -138,6 +138,10 @@ public class ExpandableSwitchItem extends SwitchItem } 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 diff --git a/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java index 1e663d6..9965aa0 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java +++ b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java @@ -19,6 +19,8 @@ package com.android.setupwizardlib.util; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import android.support.v4.view.AccessibilityDelegateCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; @@ -38,12 +40,11 @@ import java.util.List; /** * An accessibility delegate that allows {@link android.text.style.ClickableSpan} to be focused and * clicked by accessibility services. - * <p> - * <strong>Note: </strong> From Android O on, there is native support for ClickableSpan - * accessibility, so this class is not needed (and indeed has no effect.) - * </p> * - * <p />Sample usage: + * <p><strong>Note:</strong> This class is a no-op on Android O or above since there is native + * support for ClickableSpan accessibility. + * + * <p>Sample usage: * <pre> * LinkAccessibilityHelper mAccessibilityHelper; * @@ -68,294 +69,260 @@ public class LinkAccessibilityHelper extends AccessibilityDelegateCompat { private static final String TAG = "LinkAccessibilityHelper"; - private final TextView mView; - private final Rect mTempRect = new Rect(); - private final ExploreByTouchHelper mExploreByTouchHelper; + private final AccessibilityDelegateCompat mDelegate; public LinkAccessibilityHelper(TextView view) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { - // Pre-O, we essentially extend ExploreByTouchHelper to expose a virtual view hierarchy - mExploreByTouchHelper = new ExploreByTouchHelper(view) { - @Override - protected int getVirtualViewAt(float x, float y) { - return LinkAccessibilityHelper.this.getVirtualViewAt(x, y); - } - - @Override - protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { - LinkAccessibilityHelper.this.getVisibleVirtualViews(virtualViewIds); - } - - @Override - protected void onPopulateEventForVirtualView(int virtualViewId, - AccessibilityEvent event) { - LinkAccessibilityHelper - .this.onPopulateEventForVirtualView(virtualViewId, event); - } - - @Override - protected void onPopulateNodeForVirtualView(int virtualViewId, - AccessibilityNodeInfoCompat infoCompat) { - LinkAccessibilityHelper - .this.onPopulateNodeForVirtualView(virtualViewId, infoCompat); - - } + 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)); + } - @Override - protected boolean onPerformActionForVirtualView(int virtualViewId, int action, - Bundle arguments) { - return LinkAccessibilityHelper.this - .onPerformActionForVirtualView(virtualViewId, action, arguments); - } - }; - } else { - mExploreByTouchHelper = null; - } - mView = view; + @VisibleForTesting + LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) { + mDelegate = delegate; } @Override public void sendAccessibilityEvent(View host, int eventType) { - if (mExploreByTouchHelper != null) { - mExploreByTouchHelper.sendAccessibilityEvent(host, eventType); - } else { - super.sendAccessibilityEvent(host, eventType); - } + mDelegate.sendAccessibilityEvent(host, eventType); } @Override public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { - if (mExploreByTouchHelper != null) { - mExploreByTouchHelper.sendAccessibilityEventUnchecked(host, event); - } else { - super.sendAccessibilityEventUnchecked(host, event); - } + mDelegate.sendAccessibilityEventUnchecked(host, event); } @Override public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { - return (mExploreByTouchHelper != null) - ? mExploreByTouchHelper.dispatchPopulateAccessibilityEvent(host, event) - : super.dispatchPopulateAccessibilityEvent(host, event); + return mDelegate.dispatchPopulateAccessibilityEvent(host, event); } @Override public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { - if (mExploreByTouchHelper != null) { - mExploreByTouchHelper.onPopulateAccessibilityEvent(host, event); - } else { - super.onPopulateAccessibilityEvent(host, event); - } + mDelegate.onPopulateAccessibilityEvent(host, event); } @Override public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - if (mExploreByTouchHelper != null) { - mExploreByTouchHelper.onInitializeAccessibilityEvent(host, event); - } else { - super.onInitializeAccessibilityEvent(host, event); - } + mDelegate.onInitializeAccessibilityEvent(host, event); } @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - if (mExploreByTouchHelper != null) { - mExploreByTouchHelper.onInitializeAccessibilityNodeInfo(host, info); - } else { - super.onInitializeAccessibilityNodeInfo(host, info); - } + mDelegate.onInitializeAccessibilityNodeInfo(host, info); } @Override public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) { - return (mExploreByTouchHelper != null) - ? mExploreByTouchHelper.onRequestSendAccessibilityEvent(host, child, event) - : super.onRequestSendAccessibilityEvent(host, child, event); + return mDelegate.onRequestSendAccessibilityEvent(host, child, event); } @Override public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) { - return (mExploreByTouchHelper != null) - ? mExploreByTouchHelper.getAccessibilityNodeProvider(host) - : super.getAccessibilityNodeProvider(host); + return mDelegate.getAccessibilityNodeProvider(host); } @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { - return (mExploreByTouchHelper != null) - ? mExploreByTouchHelper.performAccessibilityAction(host, action, args) - : super.performAccessibilityAction(host, action, args); + return mDelegate.performAccessibilityAction(host, action, args); } /** - * Delegated to {@link ExploreByTouchHelper} + * Dispatches hover event to the virtual view hierarchy. This method should be called in + * {@link View#dispatchHoverEvent(MotionEvent)}. + * + * @see ExploreByTouchHelper#dispatchHoverEvent(MotionEvent) */ public final boolean dispatchHoverEvent(MotionEvent event) { - return (mExploreByTouchHelper != null) ? mExploreByTouchHelper.dispatchHoverEvent(event) - : false; + return mDelegate instanceof ExploreByTouchHelper + && ((ExploreByTouchHelper) mDelegate).dispatchHoverEvent(event); } - 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); - } + @VisibleForTesting + static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper { + + private final Rect mTempRect = new Rect(); + private final TextView mView; + + PreOLinkAccessibilityHelper(TextView view) { + super(view); + mView = view; } - return ExploreByTouchHelper.INVALID_ID; - } - protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { - final CharSequence text = mView.getText(); - if (text instanceof Spanned) { - final Spanned spannedText = (Spanned) text; - ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(), - ClickableSpan.class); - for (ClickableSpan span : linkSpans) { - virtualViewIds.add(spannedText.getSpanStart(span)); + @Override + protected 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; } - } - protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { - final ClickableSpan span = getSpanForOffset(virtualViewId); - if (span != null) { - event.setContentDescription(getTextForSpan(span)); - } else { - Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); - event.setContentDescription(mView.getText()); + @Override + protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { + final CharSequence text = mView.getText(); + if (text instanceof Spanned) { + final Spanned spannedText = (Spanned) text; + ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(), + ClickableSpan.class); + for (ClickableSpan span : linkSpans) { + virtualViewIds.add(spannedText.getSpanStart(span)); + } + } } - } - 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); + @Override + protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { + final ClickableSpan span = getSpanForOffset(virtualViewId); + if (span != null) { + event.setContentDescription(getTextForSpan(span)); + } else { + Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); + event.setContentDescription(mView.getText()); + } } - info.setBoundsInParent(mTempRect); - info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); - } - protected boolean onPerformActionForVirtualView(int virtualViewId, int action, - Bundle arguments) { - if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { - ClickableSpan span = getSpanForOffset(virtualViewId); + @Override + protected void onPopulateNodeForVirtualView( + int virtualViewId, + AccessibilityNodeInfoCompat info) { + final ClickableSpan span = getSpanForOffset(virtualViewId); if (span != null) { - span.onClick(mView); - return true; + 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); } - 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]; + @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; } - 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)); + 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; } - 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) { + private CharSequence getTextForSpan(ClickableSpan span) { + CharSequence text = mView.getText(); + if (text instanceof Spanned) { 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; + 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 { - outRect.left = (int) xStart; + // 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()); + // Offset for padding + outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop()); + } } + return outRect; } - 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 5172c47..d7a3c2e 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java +++ b/library/gingerbread/src/com/android/setupwizardlib/view/NavigationBarButton.java @@ -16,6 +16,7 @@ package com.android.setupwizardlib.view; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.PorterDuff; @@ -30,6 +31,7 @@ import android.widget.Button; * Button for navigation bar, which includes tinting of its compound drawables to be used for dark * and light themes. */ +@SuppressLint("AppCompatCustomView") public class NavigationBarButton extends Button { public NavigationBarButton(Context context) { diff --git a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java index e6bc9da..1b1f82e 100644 --- a/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java +++ b/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java @@ -25,7 +25,7 @@ import android.support.v7.widget.AppCompatTextView; import android.text.Annotation; import android.text.SpannableString; import android.text.Spanned; -import android.text.method.LinkMovementMethod; +import android.text.method.MovementMethod; import android.text.style.ClickableSpan; import android.text.style.TextAppearanceSpan; import android.util.AttributeSet; @@ -36,6 +36,7 @@ import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; import com.android.setupwizardlib.span.SpanHelper; 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 @@ -121,7 +122,7 @@ public class RichTextView extends AppCompatTextView implements OnLinkClickListen // nullifying any return values of MovementMethod.onTouchEvent. // To still allow propagating touch events to the parent when this view doesn't have // links, we only set the movement method here if the text contains links. - setMovementMethod(LinkMovementMethod.getInstance()); + setMovementMethod(TouchableLinkMovementMethod.getInstance()); } else { setMovementMethod(null); } @@ -130,6 +131,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) { @@ -142,6 +154,25 @@ public class RichTextView extends AppCompatTextView implements OnLinkClickListen } @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; 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 74d3be6..b97905c 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,6 +18,7 @@ package com.android.setupwizardlib.items; import static org.junit.Assert.assertTrue; +import android.support.annotation.StyleRes; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.rule.UiThreadTestRule; @@ -29,7 +30,6 @@ import android.widget.LinearLayout; import com.android.setupwizardlib.R; import com.android.setupwizardlib.test.util.DrawingTestHelper; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,40 +38,61 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ButtonItemDrawingTest { - private static final int GOOGLE_BLUE = 0xff4285f4; + 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(); - private ViewGroup mParent; + @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); - @Before - public void setUp() throws Exception { - mParent = new LinearLayout( - DrawingTestHelper.createCanvasActivity(R.style.SuwThemeGlif_Light)); + int accentPixelCount = + countPixelsWithColor(drawingTestHelper.getPixels(), GLIF_ACCENT_COLOR); + assertTrue("> 10 pixels should be #4285f4. Found " + accentPixelCount, + accentPixelCount > 10); } @Test @UiThreadTest - public void testColoredButtonTheme() { + 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"); - final Button button = item.createButton(mParent); - - DrawingTestHelper drawingTestHelper = new DrawingTestHelper(50, 50); - drawingTestHelper.drawView(button); + return item.createButton(parent); + } - int googleBluePixelCount = 0; - for (int pixel : drawingTestHelper.getPixels()) { - if (pixel == GOOGLE_BLUE) { - googleBluePixelCount++; + private int countPixelsWithColor(int[] pixels, int color) { + int count = 0; + for (int pixel : pixels) { + if (pixel == color) { + count++; } } - assertTrue("> 10 pixels should be Google blue. Found " + googleBluePixelCount, - googleBluePixelCount > 10); + return count; } private static class TestButtonItem extends ButtonItem { diff --git a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java index 844e73e..6228e6f 100644 --- a/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java +++ b/library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java @@ -14,29 +14,35 @@ * limitations under the License. */ -package com.android.setupwizardlib.test; +package com.android.setupwizardlib.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.graphics.Rect; -import android.os.Build; import android.os.Bundle; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.v4.text.BidiFormatter; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; import android.support.v4.widget.ExploreByTouchHelper; import android.text.SpannableStringBuilder; import android.util.DisplayMetrics; import android.util.TypedValue; +import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.widget.FrameLayout; import android.widget.TextView; import com.android.setupwizardlib.span.LinkSpan; -import com.android.setupwizardlib.util.LinkAccessibilityHelper; +import com.android.setupwizardlib.util.LinkAccessibilityHelper.PreOLinkAccessibilityHelper; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,13 +58,12 @@ public class LinkAccessibilityHelperTest { private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); private TextView mTextView; - private TestLinkAccessibilityHelper mHelper; + private TestPreOLinkAccessibilityHelper mHelper; private DisplayMetrics mDisplayMetrics; @Test public void testGetVirtualViewAt() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; initTextView(); final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10)); assertEquals("Virtual view ID should be 1", 1, virtualViewId); @@ -66,7 +71,6 @@ public class LinkAccessibilityHelperTest { @Test public void testGetVirtualViewAtHost() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; initTextView(); final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100)); assertEquals("Virtual view ID should be INVALID_ID", @@ -75,7 +79,6 @@ public class LinkAccessibilityHelperTest { @Test public void testGetVisibleVirtualViews() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; initTextView(); List<Integer> virtualViewIds = new ArrayList<>(); mHelper.getVisibleVirtualViews(virtualViewIds); @@ -86,7 +89,6 @@ public class LinkAccessibilityHelperTest { @Test public void testOnPopulateEventForVirtualView() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; initTextView(); AccessibilityEvent event = AccessibilityEvent.obtain(); mHelper.onPopulateEventForVirtualView(1, event); @@ -100,7 +102,6 @@ public class LinkAccessibilityHelperTest { @Test public void testOnPopulateEventForVirtualViewHost() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; initTextView(); AccessibilityEvent event = AccessibilityEvent.obtain(); mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event); @@ -113,7 +114,6 @@ public class LinkAccessibilityHelperTest { @Test public void testOnPopulateNodeForVirtualView() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; initTextView(); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mHelper.onPopulateNodeForVirtualView(1, info); @@ -132,7 +132,6 @@ public class LinkAccessibilityHelperTest { @Test public void testNullLayout() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; initTextView(); // Setting the padding will cause the layout to be null-ed out. mTextView.setPadding(1, 1, 1, 1); @@ -150,7 +149,6 @@ public class LinkAccessibilityHelperTest { @Test public void testRtlLayout() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום"); ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); initTextView(ssb); @@ -170,7 +168,6 @@ public class LinkAccessibilityHelperTest { @Test public void testMultilineLink() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; SpannableStringBuilder ssb = new SpannableStringBuilder( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Praesent accumsan efficitur eros eu porttitor."); @@ -192,7 +189,6 @@ public class LinkAccessibilityHelperTest { @Test public void testRtlMultilineLink() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + "דפים המחשב מיזמים ב."; @@ -216,7 +212,6 @@ public class LinkAccessibilityHelperTest { @Test public void testBidiMultilineLink() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return; String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + "דפים המחשב מיזמים ב."; @@ -243,6 +238,70 @@ public class LinkAccessibilityHelperTest { info.recycle(); } + @Test + public void testMethodDelegation() { + initTextView(); + ExploreByTouchHelper delegate = mock(TestPreOLinkAccessibilityHelper.class); + LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate); + + AccessibilityEvent accessibilityEvent = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED); + + helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED); + verify(delegate).sendAccessibilityEvent( + same(mTextView), + eq(AccessibilityEvent.TYPE_VIEW_CLICKED)); + + helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent); + verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent)); + + helper.performAccessibilityAction( + mTextView, + AccessibilityActionCompat.ACTION_CLICK.getId(), + Bundle.EMPTY); + verify(delegate).performAccessibilityAction( + same(mTextView), + eq(AccessibilityActionCompat.ACTION_CLICK.getId()), + eq(Bundle.EMPTY)); + + helper.dispatchPopulateAccessibilityEvent( + mTextView, + accessibilityEvent); + verify(delegate).dispatchPopulateAccessibilityEvent( + same(mTextView), + same(accessibilityEvent)); + + MotionEvent motionEvent = MotionEvent.obtain(0, 0, 0, 0, 0, 0); + helper.dispatchHoverEvent(motionEvent); + verify(delegate).dispatchHoverEvent(eq(motionEvent)); + + helper.getAccessibilityNodeProvider(mTextView); + verify(delegate).getAccessibilityNodeProvider(same(mTextView)); + + helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent); + verify(delegate).onInitializeAccessibilityEvent( + same(mTextView), + eq(accessibilityEvent)); + + AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain(); + helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo); + verify(delegate).onInitializeAccessibilityNodeInfo( + same(mTextView), + same(accessibilityNodeInfo)); + + helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent); + verify(delegate).onPopulateAccessibilityEvent( + same(mTextView), + same(accessibilityEvent)); + + FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext()); + helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent); + verify(delegate).onRequestSendAccessibilityEvent( + same(parent), + same(mTextView), + same(accessibilityEvent)); + } + private void initTextView() { SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); @@ -254,7 +313,7 @@ public class LinkAccessibilityHelperTest { mTextView.setSingleLine(false); mTextView.setText(text); mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - mHelper = new TestLinkAccessibilityHelper(mTextView); + mHelper = new TestPreOLinkAccessibilityHelper(mTextView); int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500), View.MeasureSpec.EXACTLY); @@ -270,9 +329,9 @@ public class LinkAccessibilityHelperTest { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics); } - private static class TestLinkAccessibilityHelper extends LinkAccessibilityHelper { + public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper { - TestLinkAccessibilityHelper(TextView view) { + TestPreOLinkAccessibilityHelper(TextView view) { super(view); } 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 0ae0737..6192061 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 @@ -16,35 +16,29 @@ package com.android.setupwizardlib.items; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.not; +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.assertThat; import static org.junit.Assert.assertTrue; import static org.robolectric.RuntimeEnvironment.application; -import android.support.v7.widget.SwitchCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.TextView; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; +import com.android.setupwizardlib.view.CheckableLinearLayout; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; -import java.util.ArrayList; - @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) public class ExpandableSwitchItemTest { private TextView mSummaryView; @@ -71,6 +65,14 @@ public class ExpandableSwitchItemTest { 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 @@ -132,57 +134,25 @@ public class ExpandableSwitchItemTest { mItem.onBindView(view); final View titleView = view.findViewById(R.id.suw_items_title); - assertThat("state_checked should not be set initially", - toArrayList(titleView.getDrawableState()), - not(hasItem(android.R.attr.state_checked))); + assertThat(titleView.getDrawableState()).asList().named("Drawable state") + .doesNotContain(android.R.attr.state_checked); mItem.setExpanded(true); mItem.onBindView(view); - assertThat("state_checked should not be set initially", - toArrayList(titleView.getDrawableState()), - hasItem(android.R.attr.state_checked)); + assertThat(titleView.getDrawableState()).asList().named("Drawable state") + .contains(android.R.attr.state_checked); mItem.setExpanded(false); mItem.onBindView(view); - assertThat("state_checked should not be set initially", - toArrayList(titleView.getDrawableState()), - not(hasItem(android.R.attr.state_checked))); - } - - private ArrayList<Integer> toArrayList(int[] array) { - ArrayList<Integer> arrayList = new ArrayList<>(array.length); - for (int i : array) { - arrayList.add(i); - } - return arrayList; + assertThat(titleView.getDrawableState()).asList().named("Drawable state") + .doesNotContain(android.R.attr.state_checked); } private ViewGroup createLayout() { - ViewGroup root = new FrameLayout(application); - - ViewGroup content = new FrameLayout(application); - content.setId(R.id.suw_items_expandable_switch_content); - root.addView(content); - - TextView titleView = new TextView(application); - titleView.setId(R.id.suw_items_title); - content.addView(titleView); - - mSummaryView = new TextView(application); - mSummaryView.setId(R.id.suw_items_summary); - content.addView(mSummaryView); - - FrameLayout iconContainer = new FrameLayout(application); - iconContainer.setId(R.id.suw_items_icon_container); - content.addView(iconContainer); - - ImageView iconView = new ImageView(application); - iconView.setId(R.id.suw_items_icon); - iconContainer.addView(iconView); - - SwitchCompat switchView = new SwitchCompat(application); - switchView.setId(R.id.suw_items_switch); - root.addView(switchView); + 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 d391d80..fa5bbba 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 @@ -31,7 +31,6 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; @@ -40,7 +39,7 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) public class SwitchItemTest { private SwitchCompat mSwitch; 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 43e7f03..7a08235 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 @@ -25,7 +25,6 @@ import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.ContextThemeWrapper; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; @@ -35,7 +34,7 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = Config.ALL_SDKS) +@Config(sdk = Config.ALL_SDKS) public class DimensionConsistencyTest { // Visual height of the framework switch widget diff --git a/library/lint.xml b/library/lint.xml index ca22c65..625b20d 100644 --- a/library/lint.xml +++ b/library/lint.xml @@ -1,7 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <lint> + <!-- SUW lib prefixes styleable resources --> + <issue id="CustomViewStyleable" severity="ignore" /> <issue id="ExtraTranslation" severity="ignore" /> <issue id="GradleDependency" severity="ignore" /> <issue id="MissingTranslation" severity="ignore" /> + <!-- Stop lint from complaining about SDK version checks in the "platform" variant --> + <issue id="ObsoleteSdkInt" severity="ignore" /> <issue id="RtlEnabled" severity="ignore" /> </lint> diff --git a/library/main/res/drawable-v21/suw_edit_text_bg_shape.xml b/library/main/res/drawable-v21/suw_edit_text_bg_shape.xml new file mode 100644 index 0000000..ad55ec6 --- /dev/null +++ b/library/main/res/drawable-v21/suw_edit_text_bg_shape.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/> + <solid android:color="?attr/suwEditTextBackgroundColor"/> +</shape>
\ No newline at end of file diff --git a/library/main/res/drawable-v21/suw_edittext_bg.xml b/library/main/res/drawable-v21/suw_edittext_bg.xml new file mode 100644 index 0000000..b69c10b --- /dev/null +++ b/library/main/res/drawable-v21/suw_edittext_bg.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false"> + <layer-list> + <item android:drawable="@drawable/suw_edit_text_bg_shape" android:bottom="1dp"/> + <item android:gravity="bottom"> + <shape> + <size android:height="1dp"/> + <solid android:color="?android:attr/textColorSecondary"/> + </shape> + </item> + </layer-list> + </item> + <item android:state_focused="false" android:state_pressed="false"> + <layer-list> + <item android:drawable="@drawable/suw_edit_text_bg_shape" android:bottom="1dp" /> + <item android:gravity="bottom"> + <shape> + <size android:height="1dp"/> + <solid android:color="?android:attr/textColorSecondary"/> + </shape> + </item> + </layer-list> + </item> + <item> + <layer-list> + <item android:drawable="@drawable/suw_edit_text_bg_shape" android:bottom="2dp" /> + <item android:gravity="bottom"> + <shape> + <size android:height="2dp"/> + <solid android:color="?android:attr/colorAccent"/> + </shape> + </item> + </layer-list> + </item> +</selector> diff --git a/library/main/res/drawable-v21/suw_fourcolor_progress_bar.xml b/library/main/res/drawable-v21/suw_fourcolor_progress_bar.xml new file mode 100644 index 0000000..2ac35ee --- /dev/null +++ b/library/main/res/drawable-v21/suw_fourcolor_progress_bar.xml @@ -0,0 +1,194 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + + +<!-- Asset for 4 color indeterminate progress bar, which is a ring with 4 shades of blue --> +<animated-vector xmlns:aapt="http://schemas.android.com/aapt" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:ignore="MissingPrefix"> + <!-- Ignore MissingPrefix: aapt:attr tags take the name attribute without "android:" prefix --> + <!-- TODO(yukl): Update to a newer version of lint which properly handles this case --> + <aapt:attr name="android:drawable"> + <vector android:width="823dp" android:height="823dp" android:viewportHeight="823" + android:viewportWidth="823"> + <group android:name="blue1" android:translateX="411.5" android:translateY="411.5"> + <path android:name="blue1_path" + android:pathData="M0 -395 C218,-395 395,-218 395,0 C395,218 218,395 0,395 C-218,395 -395,218 -395,0 C-395,-218 -218,-395 0,-395c " + android:strokeAlpha="1" android:strokeColor="#4688f1" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="27" /> + </group> + <group android:name="blue2" android:translateX="411.5" android:translateY="411.5"> + <path android:name="blue2_path" + android:pathData=" M0 -395 C218,-395 395,-218 395,0 C395,218 218,395 0,395 C-218,395 -395,218 -395,0 C-395,-218 -218,-395 0,-395c " + android:strokeAlpha="1" android:strokeColor="#7dacf4" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="28" /> + </group> + <group android:name="blue3" android:translateX="411.5" android:translateY="411.5"> + <path android:name="blue3_path" + android:pathData=" M0 -395 C218,-395 395,-218 395,0 C395,218 218,395 0,395 C-218,395 -395,218 -395,0 C-395,-218 -218,-395 0,-395c " + android:strokeAlpha="1" android:strokeColor="#c7dbfb" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="29" /> + </group> + <group android:name="blue4" android:translateX="411.5" android:translateY="411.5"> + <path android:name="blue4_path" + android:pathData=" M0 -395 C218,-395 395,-218 395,0 C395,218 218,395 0,395 C-218,395 -395,218 -395,0 C-395,-218 -218,-395 0,-395c " + android:strokeAlpha="1" android:strokeColor="#e8f0fd" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="30" /> + </group> + </vector> + </aapt:attr> + <target android:name="blue1_path"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="trimPathStart" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 l.21,0 c.571,0 .194,.755 .79,1" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue1_path"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="trimPathEnd" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 c0.606,0.315 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue1"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="rotation" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="355" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 c0.829,0.228 0.2,0.915 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue2_path"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="trimPathStart" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 l.21,0 c.571,0 .145,.831 .79,1" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue2_path"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="trimPathEnd" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 c0.606,0.315 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue2"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="rotation" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="355" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 c0.792,0.233 0.2,0.915 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue3_path"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="trimPathStart" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 l.21,0 c.6138,0 .007,.883 .79,1" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue3_path"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="trimPathEnd" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 c0.606,0.315 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue3"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:duration="1983" android:propertyName="rotation" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="355" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator + android:pathData="M0,0 c0.762,0.225 0.2,0.915 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="blue4_path"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="trimPathStart" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 l.21,0 c.572,0 0,1 .79,1" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue4_path"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="trimPathEnd" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 c0.606,0.315 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> + <target android:name="blue4"> + <aapt:attr name="android:animation"> + <objectAnimator android:duration="1983" android:propertyName="rotation" + android:repeatCount="infinite" android:valueFrom="0" android:valueTo="355" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M0,0 c0.606,0.172 0.2,0.915 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </aapt:attr> + </target> +</animated-vector> diff --git a/library/main/res/layout/suw_glif_blank_template_content.xml b/library/main/res/layout/suw_glif_blank_template_content.xml index ed81126..6d864cd 100644 --- a/library/main/res/layout/suw_glif_blank_template_content.xml +++ b/library/main/res/layout/suw_glif_blank_template_content.xml @@ -21,6 +21,11 @@ android:layout_height="match_parent" android:orientation="vertical"> + <ViewStub + android:id="@+id/suw_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <FrameLayout android:id="@+id/suw_layout_content" android:layout_width="match_parent" diff --git a/library/main/res/layout/suw_glif_header.xml b/library/main/res/layout/suw_glif_header.xml index b090f79..420e989 100644 --- a/library/main/res/layout/suw_glif_header.xml +++ b/library/main/res/layout/suw_glif_header.xml @@ -23,10 +23,11 @@ <ImageView android:id="@+id/suw_layout_icon" - style="@style/SuwGlifIcon" + style="?attr/suwGlifIconStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:contentDescription="@null" /> + android:contentDescription="@null" + android:visibility="gone" /> <TextView android:id="@+id/suw_layout_title" diff --git a/library/main/res/layout/suw_glif_list_template_content.xml b/library/main/res/layout/suw_glif_list_template_content.xml index d0c5cc4..58ca178 100644 --- a/library/main/res/layout/suw_glif_list_template_content.xml +++ b/library/main/res/layout/suw_glif_list_template_content.xml @@ -22,6 +22,11 @@ android:layout_height="match_parent" android:orientation="vertical"> + <ViewStub + android:id="@+id/suw_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <!-- Ignore UnusedAttribute: scrollIndicators is new in M. Default to no indicators in older versions. --> <com.android.setupwizardlib.view.StickyHeaderListView diff --git a/library/main/res/layout/suw_glif_loading_screen.xml b/library/main/res/layout/suw_glif_loading_screen.xml new file mode 100644 index 0000000..676ab34 --- /dev/null +++ b/library/main/res/layout/suw_glif_loading_screen.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.setupwizardlib.GlifLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/setup_wizard_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:ignore="UnusedResources"> + <!-- Ignore UnusedResources: can be used by clients --> + + <com.android.setupwizardlib.view.FillContentLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"> + + <ProgressBar + android:id="@+id/suw_large_progress_bar" + style="@style/SuwFourColorIndeterminateProgressBar" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + </com.android.setupwizardlib.view.FillContentLayout> + +</com.android.setupwizardlib.GlifLayout> diff --git a/library/main/res/layout/suw_glif_template_content.xml b/library/main/res/layout/suw_glif_template_content.xml index 0fe35a0..5226f63 100644 --- a/library/main/res/layout/suw_glif_template_content.xml +++ b/library/main/res/layout/suw_glif_template_content.xml @@ -22,6 +22,11 @@ android:layout_height="match_parent" android:orientation="vertical"> + <ViewStub + android:id="@+id/suw_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <!-- Ignore UnusedAttribute: scrollIndicators is new in M. Default to no indicators in older versions. --> <com.android.setupwizardlib.view.BottomScrollView diff --git a/library/main/res/values-en-rCA/strings.xml b/library/main/res/values-en-rCA/strings.xml new file mode 100644 index 0000000..5260500 --- /dev/null +++ b/library/main/res/values-en-rCA/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="suw_next_button_label" msgid="7269625133873553978">"Next"</string> + <string name="suw_back_button_label" msgid="1460929053642711025">"Back"</string> + <string name="suw_more_button_label" msgid="7769076059705546563">"More"</string> +</resources> diff --git a/library/main/res/values-en-rXC/strings.xml b/library/main/res/values-en-rXC/strings.xml new file mode 100644 index 0000000..693af6b --- /dev/null +++ b/library/main/res/values-en-rXC/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="suw_next_button_label" msgid="7269625133873553978">"Next"</string> + <string name="suw_back_button_label" msgid="1460929053642711025">"Back"</string> + <string name="suw_more_button_label" msgid="7769076059705546563">"More"</string> +</resources> diff --git a/library/main/res/values-v11/styles.xml b/library/main/res/values-v11/styles.xml new file mode 100644 index 0000000..6903577 --- /dev/null +++ b/library/main/res/values-v11/styles.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + + <style name="SuwBase.ProgressBarLarge" parent="@android:style/Widget.Holo.ProgressBar.Large" /> + +</resources> diff --git a/library/main/res/values-v21/styles.xml b/library/main/res/values-v21/styles.xml index ab6f887..d2c27f6 100644 --- a/library/main/res/values-v21/styles.xml +++ b/library/main/res/values-v21/styles.xml @@ -42,6 +42,16 @@ <item name="android:fontFamily">sans-serif-medium</item> </style> + <style name="SuwBase.ProgressBarLarge" parent="@android:style/Widget.Material.ProgressBar.Large" /> + + <style name="SuwFourColorIndeterminateProgressBar" parent="SuwBase.ProgressBarLarge"> + <item name="android:layout_gravity">center</item> + <item name="android:indeterminate">true</item> + <item name="android:indeterminateDrawable">@drawable/suw_fourcolor_progress_bar</item> + <item name="android:indeterminateTint">@null</item> + <item name="android:indeterminateTintMode">@null</item> + </style> + <!-- Items styles --> <style name="SuwItemContainer"> @@ -83,4 +93,11 @@ <item name="suwNavBarButtonBackground">@drawable/suw_navbar_btn_bg</item> </style> + <style name="SuwEditText" parent="@android:style/Widget.Material.EditText"> + <item name="android:background">@drawable/suw_edittext_bg</item> + <item name="android:minHeight">@dimen/suw_edit_text_min_height</item> + <item name="android:paddingLeft">@dimen/suw_edit_text_padding_horizontal</item> + <item name="android:paddingRight">@dimen/suw_edit_text_padding_horizontal</item> + </style> + </resources> diff --git a/library/main/res/values-v22/styles.xml b/library/main/res/values-v22/styles.xml new file mode 100644 index 0000000..7d2d219 --- /dev/null +++ b/library/main/res/values-v22/styles.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + + <style name="SuwAlertDialogTheme" parent="android:Theme.DeviceDefault.Dialog.Alert" /> + + <style name="SuwAlertDialogTheme.Light" parent="android:Theme.DeviceDefault.Light.Dialog.Alert" /> +</resources>
\ No newline at end of file diff --git a/library/main/res/values/attrs.xml b/library/main/res/values/attrs.xml index 36d5fb7..b3fcfe9 100644 --- a/library/main/res/values/attrs.xml +++ b/library/main/res/values/attrs.xml @@ -20,6 +20,7 @@ <!-- Theme attributes --> <attr name="suwLayoutTheme" format="reference" /> <attr name="suwMarginSides" format="dimension|reference" /> + <attr name="suwEditTextBackgroundColor" format="color" /> <!-- Subset of values in "gravity" in frameworks/base/core/res/res/values/attrs.xml. Only horizontal values are listed here as the header does not support vertical gravity. --> @@ -37,13 +38,17 @@ <!-- Push object to the end of its container, not changing its size. --> <flag name="end" value="0x00800005" /> </attr> + <attr name="suwGlifIconStyle" format="reference" /> + <attr name="suwButtonAllCaps" format="boolean" /> + <attr name="suwButtonCornerRadius" format="dimension" /> + <attr name="suwButtonFontFamily" format="string|reference" /> <attr name="suwCardBackground" format="color|reference" /> - <attr name="suwFillContentLayoutStyle" format="reference" /> <attr name="suwDividerCondition"> <enum name="either" value="0" /> <enum name="both" value="1" /> </attr> + <attr name="suwFillContentLayoutStyle" format="reference" /> <attr name="suwListItemIconColor" format="color" /> <attr name="suwNavBarBackgroundColor" format="color" /> <attr name="suwNavBarButtonBackground" format="color|reference" /> @@ -102,6 +107,8 @@ <attr name="suwBackgroundBaseColor" format="color" /> <attr name="suwColorPrimary" /> <attr name="suwFooter" format="reference" /> + <attr name="suwLayoutFullscreen" format="boolean" /> + <attr name="suwStickyHeader" format="reference" /> </declare-styleable> <declare-styleable name="SuwStatusBarBackgroundLayout"> diff --git a/library/main/res/values/colors.xml b/library/main/res/values/colors.xml index cd57a8a..005d1c6 100644 --- a/library/main/res/values/colors.xml +++ b/library/main/res/values/colors.xml @@ -21,6 +21,8 @@ <color name="suw_color_accent_dark">#ff448aff</color> <color name="suw_color_accent_light">#ff3367d6</color> + <color name="suw_color_background_dark">#ff303030</color> + <color name="suw_color_background_light">#fffafafa</color> <color name="suw_link_color_dark">#ff448aff</color> <color name="suw_link_color_light">#ff3367d6</color> <color name="suw_list_item_icon_color_dark">#b3ffffff</color> @@ -40,7 +42,11 @@ <!-- GLIF colors --> <color name="suw_color_accent_glif_dark">#ff4285f4</color> <color name="suw_color_accent_glif_light">#ff4285f4</color> + <color name="suw_color_accent_glif_v3">#ff1a73e8</color> <color name="suw_glif_background_color_dark">#ff000000</color> <color name="suw_glif_background_color_light">#ffffffff</color> + <color name="suw_glif_edit_text_bg_light_color">#fff1f3f4</color> + <color name="suw_glif_v3_nav_bar_color_light">#ffffffff</color> + <color name="suw_glif_v3_nav_bar_divider_color_light">#1f000000</color> </resources> diff --git a/library/main/res/values/config.xml b/library/main/res/values/config.xml index a81b177..c11bf41 100644 --- a/library/main/res/values/config.xml +++ b/library/main/res/values/config.xml @@ -27,4 +27,8 @@ ButtonBarLayout --> <item name="suw_original_weight" type="id" /> + <!-- Secondary font for use with headings, title, and other non-body text --> + <string name="suwFontSecondary" translatable="false">google-sans</string> + <string name="suwFontSecondaryMedium" translatable="false">google-sans-medium</string> + </resources> diff --git a/library/main/res/values/dimens.xml b/library/main/res/values/dimens.xml index f29484a..1a8b516 100644 --- a/library/main/res/values/dimens.xml +++ b/library/main/res/values/dimens.xml @@ -20,16 +20,21 @@ <!-- General --> <dimen name="suw_layout_margin_sides">40dp</dimen> + <dimen name="suw_glif_button_corner_radius">2dp</dimen> <!-- Calculated by (suw_glif_margin_sides - 4dp internal padding of button) --> <dimen name="suw_glif_button_margin_end">20dp</dimen> <!-- Calculated by (suw_glif_margin_sides - suw_glif_button_padding) --> <dimen name="suw_glif_button_margin_start">8dp</dimen> <dimen name="suw_glif_button_padding">16dp</dimen> + <!-- Negative of suw_glif_button_padding --> + <dimen name="suw_glif_negative_button_padding">-16dp</dimen> <dimen name="suw_glif_footer_padding_vertical">8dp</dimen> - <dimen name="suw_glif_footer_min_height">80dp</dimen> + <dimen name="suw_glif_footer_min_height">72dp</dimen> <dimen name="suw_glif_margin_sides">24dp</dimen> <dimen name="suw_glif_margin_top">48dp</dimen> + <dimen name="suw_glif_v3_button_corner_radius">4dp</dimen> + <!-- Content styles --> <dimen name="suw_check_box_line_spacing_extra">4sp</dimen> <dimen name="suw_check_box_margin_bottom">12dp</dimen> @@ -135,4 +140,8 @@ <dimen name="suw_progress_bar_margin_vertical">-7dp</dimen> <dimen name="suw_glif_progress_bar_margin_vertical">7dp</dimen> + <!-- Edit Text dimensions --> + <dimen name="suw_edit_text_min_height">56dp</dimen> + <dimen name="suw_edit_text_padding_horizontal">12dp</dimen> + </resources> diff --git a/library/main/res/values/styles.xml b/library/main/res/values/styles.xml index bd3b60f..fa2a080 100644 --- a/library/main/res/values/styles.xml +++ b/library/main/res/values/styles.xml @@ -30,11 +30,11 @@ <item name="suwDividerInsetStartNoIcon">?attr/suwMarginSides</item> <item name="suwGlifHeaderGravity">center_horizontal</item> <item name="suwScrollIndicators">top|bottom</item> + <item name="suwEditTextBackgroundColor">@color/suw_glif_edit_text_bg_light_color</item> <!-- TODO: Change color --> + <item name="android:editTextStyle">@style/SuwEditText</item> + <item name="android:alertDialogTheme" tools:targetApi="honeycomb">@style/SuwAlertDialogTheme</item> </style> - <!-- Deprecated. Use SuwThemeGlifV2 instead --> - <style name="SuwThemeGlifPixel" parent="SuwThemeGlifV2" /> - <style name="SuwThemeGlifV2.Light" parent="SuwThemeGlif.Light"> <item name="android:colorBackground">@color/suw_glif_background_color_light</item> <item name="android:windowLightStatusBar" tools:targetApi="m">true</item> @@ -46,11 +46,11 @@ <item name="suwDividerInsetStartNoIcon">?attr/suwMarginSides</item> <item name="suwGlifHeaderGravity">center_horizontal</item> <item name="suwScrollIndicators">top|bottom</item> + <item name="suwEditTextBackgroundColor">@color/suw_glif_edit_text_bg_light_color</item> + <item name="android:editTextStyle">@style/SuwEditText</item> + <item name="android:alertDialogTheme" tools:targetApi="honeycomb">@style/SuwAlertDialogTheme.Light</item> </style> - <!-- Deprecated. Use SuwThemeGlifV2.Light instead --> - <style name="SuwThemeGlifPixel.Light" parent="SuwThemeGlifV2.Light" /> - <style name="Animation.SuwWindowAnimation" parent="@android:style/Animation.Activity"> <item name="android:activityOpenEnterAnimation">@anim/suw_slide_next_in</item> <item name="android:activityOpenExitAnimation">@anim/suw_slide_next_out</item> @@ -86,11 +86,10 @@ <item name="android:textAlignment" tools:targetApi="jelly_bean_mr1">gravity</item> </style> - <style name="TextAppearance.SuwDescription.Light" parent="TextAppearance.SuwDescription"> - <item name="android:fontFamily" tools:ignore="NewApi">sans-serif-light</item> - </style> - - <style name="TextAppearance.SuwDescription.Secondary" parent="TextAppearance.SuwDescription"> + <!-- Ignore UnusedResources: Used by clients --> + <style name="TextAppearance.SuwDescription.Secondary" + parent="TextAppearance.SuwDescription" + tools:ignore="UnusedResources"> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> @@ -196,14 +195,19 @@ <item name="android:buttonStyle">@style/SuwGlifButton.Tertiary</item> <item name="android:theme">@style/SuwGlifButton.Tertiary</item> - <item name="android:background">@null</item> <item name="android:fontFamily" tools:targetApi="jelly_bean">sans-serif</item> <item name="android:layout_gravity">?attr/suwGlifHeaderGravity</item> - <item name="android:padding">0dp</item> + <item name="android:layout_marginLeft">@dimen/suw_glif_negative_button_padding</item> + <item name="android:layout_marginRight">@dimen/suw_glif_negative_button_padding</item> + <!-- Always lowercase instead of reading attr/suwButtonAllCaps, since this is a tertiary + button --> <item name="android:textAllCaps" tools:targetApi="ice_cream_sandwich">false</item> </style> - <style name="SuwGlifButton.Tertiary" parent="SuwGlifButton.BaseTertiary" /> + <!-- Ignore UnusedResources: used by clients --> + <style name="SuwGlifButton.Tertiary" + parent="SuwGlifButton.BaseTertiary" + tools:ignore="UnusedResources" /> <!-- The start and end paddings are asymmetric because start buttons are borderless buttons which aligns the text label. --> @@ -266,7 +270,7 @@ <item name="android:layout_marginLeft">?attr/suwMarginSides</item> <item name="android:layout_marginRight">?attr/suwMarginSides</item> <item name="android:layout_marginTop">@dimen/suw_glif_header_title_margin_top</item> - <item name="android:fontFamily" tools:targetApi="jelly_bean">google-sans</item> + <item name="android:fontFamily" tools:targetApi="jelly_bean">@string/suwFontSecondary</item> <item name="android:textAlignment" tools:targetApi="jelly_bean_mr1">gravity</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> @@ -309,4 +313,9 @@ <item name="suwNavBarTextColor">?android:attr/textColorPrimary</item> </style> + + <style name="SuwEditText" parent="@android:style/Widget.EditText"> + <item name="android:minHeight">@dimen/suw_edit_text_min_height</item> + </style> + </resources> diff --git a/library/main/src/com/android/setupwizardlib/GlifLayout.java b/library/main/src/com/android/setupwizardlib/GlifLayout.java index f4d52a5..e1d9d70 100644 --- a/library/main/src/com/android/setupwizardlib/GlifLayout.java +++ b/library/main/src/com/android/setupwizardlib/GlifLayout.java @@ -77,6 +77,8 @@ public class GlifLayout extends TemplateLayout { @Nullable private ColorStateList mBackgroundBaseColor; + private boolean mLayoutFullscreen = true; + public GlifLayout(Context context) { this(context, 0, 0); } @@ -139,7 +141,18 @@ public class GlifLayout extends TemplateLayout { 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); + } } @Override @@ -160,17 +173,31 @@ public class GlifLayout extends TemplateLayout { /** * 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 layout. + * 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 = (ViewStub) findManagedViewById(R.id.suw_layout_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; @@ -280,9 +307,6 @@ public class GlifLayout extends TemplateLayout { patternBg.setBackgroundDrawable(background); } } - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } } public boolean isProgressBarShown() { diff --git a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java index 51c1a49..c1d968a 100644 --- a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java +++ b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java @@ -23,7 +23,6 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; @@ -96,7 +95,6 @@ public class GlifPatternDrawable extends Drawable { private int mColor; private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private ColorFilter mColorFilter; public GlifPatternDrawable(int color) { setColor(color); @@ -140,17 +138,10 @@ public class GlifPatternDrawable extends Drawable { canvas.clipRect(bounds); scaleCanvasToBounds(canvas, bitmap, bounds); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB - && canvas.isHardwareAccelerated()) { - mTempPaint.setColorFilter(mColorFilter); - canvas.drawBitmap(bitmap, 0, 0, mTempPaint); - } else { - // Software renderer doesn't work properly with ColorMatrix filter on ALPHA_8 bitmaps. - canvas.drawColor(Color.BLACK); - mTempPaint.setColor(Color.WHITE); - canvas.drawBitmap(bitmap, 0, 0, mTempPaint); - canvas.drawColor(mColor); - } + canvas.drawColor(Color.BLACK); + mTempPaint.setColor(Color.WHITE); + canvas.drawBitmap(bitmap, 0, 0, mTempPaint); + canvas.drawColor(mColor); canvas.restore(); } @@ -299,12 +290,6 @@ public class GlifPatternDrawable extends Drawable { final int g = Color.green(color); final int b = Color.blue(color); mColor = Color.argb(COLOR_ALPHA_INT, r, g, b); - mColorFilter = new ColorMatrixColorFilter(new float[] { - 0, 0, 0, 1 - COLOR_ALPHA, r * COLOR_ALPHA, - 0, 0, 0, 1 - COLOR_ALPHA, g * COLOR_ALPHA, - 0, 0, 0, 1 - COLOR_ALPHA, b * COLOR_ALPHA, - 0, 0, 0, 0, 255 - }); invalidateSelf(); } diff --git a/library/main/src/com/android/setupwizardlib/TemplateLayout.java b/library/main/src/com/android/setupwizardlib/TemplateLayout.java index 771592f..d270091 100644 --- a/library/main/src/com/android/setupwizardlib/TemplateLayout.java +++ b/library/main/src/com/android/setupwizardlib/TemplateLayout.java @@ -103,7 +103,9 @@ public class TemplateLayout extends FrameLayout { * by this view but not currently added to the view hierarchy. e.g. recycler view or list view * headers that are not currently shown. */ - public View findManagedViewById(int id) { + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public <T extends View> T findManagedViewById(int id) { return findViewById(id); } diff --git a/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java b/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java index 8325232..f438691 100644 --- a/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java +++ b/library/main/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetector.java @@ -39,6 +39,7 @@ public final class ConsecutiveTapsGestureDetector { private final View mView; private final OnConsecutiveTapsListener mListener; private final int mConsecutiveTapTouchSlopSquare; + private final int mConsecutiveTapTimeout; private int mConsecutiveTapsCounter = 0; private MotionEvent mPreviousTapEvent; @@ -54,6 +55,7 @@ public final class ConsecutiveTapsGestureDetector { mView = view; int doubleTapSlop = ViewConfiguration.get(mView.getContext()).getScaledDoubleTapSlop(); mConsecutiveTapTouchSlopSquare = doubleTapSlop * doubleTapSlop; + mConsecutiveTapTimeout = ViewConfiguration.getDoubleTapTimeout(); } /** @@ -109,6 +111,8 @@ public final class ConsecutiveTapsGestureDetector { double deltaX = mPreviousTapEvent.getX() - currentTapEvent.getX(); double deltaY = mPreviousTapEvent.getY() - currentTapEvent.getY(); - return (deltaX * deltaX + deltaY * deltaY <= mConsecutiveTapTouchSlopSquare); + long deltaTime = currentTapEvent.getEventTime() - mPreviousTapEvent.getEventTime(); + return (deltaX * deltaX + deltaY * deltaY <= mConsecutiveTapTouchSlopSquare) + && deltaTime < mConsecutiveTapTimeout; } } diff --git a/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java b/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java index 55bbe75..06ce4ac 100644 --- a/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java +++ b/library/main/src/com/android/setupwizardlib/items/ButtonBarItem.java @@ -83,6 +83,7 @@ public class ButtonBarItem extends AbstractItem implements ItemInflater.ItemPare return mVisible; } + @Override public int getViewId() { return getId(); } 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/main/src/com/android/setupwizardlib/template/IconMixin.java b/library/main/src/com/android/setupwizardlib/template/IconMixin.java index 46c23f0..5386c92 100644 --- a/library/main/src/com/android/setupwizardlib/template/IconMixin.java +++ b/library/main/src/com/android/setupwizardlib/template/IconMixin.java @@ -19,7 +19,9 @@ package com.android.setupwizardlib.template; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; import android.util.AttributeSet; +import android.view.View; import android.widget.ImageView; import com.android.setupwizardlib.R; @@ -44,8 +46,8 @@ public class IconMixin implements Mixin { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuwIconMixin, defStyleAttr, 0); - final Drawable icon = a.getDrawable(R.styleable.SuwIconMixin_android_icon); - if (icon != null) { + final @DrawableRes int icon = a.getResourceId(R.styleable.SuwIconMixin_android_icon, 0); + if (icon != 0) { setIcon(icon); } @@ -61,6 +63,22 @@ public class IconMixin implements Mixin { final ImageView iconView = getView(); if (iconView != null) { iconView.setImageDrawable(icon); + iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE); + } + } + + /** + * 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); } } @@ -73,6 +91,24 @@ public class IconMixin implements Mixin { } /** + * 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() { diff --git a/library/main/src/com/android/setupwizardlib/util/Partner.java b/library/main/src/com/android/setupwizardlib/util/Partner.java index 67f5546..3a603ee 100644 --- a/library/main/src/com/android/setupwizardlib/util/Partner.java +++ b/library/main/src/com/android/setupwizardlib/util/Partner.java @@ -27,6 +27,7 @@ import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.support.annotation.AnyRes; +import android.support.annotation.ColorRes; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; import android.support.annotation.VisibleForTesting; @@ -76,6 +77,15 @@ public class Partner { } /** + * 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. */ diff --git a/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java b/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java index 1c5f3d3..b31e82e 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 android.support.annotation.RequiresPermission; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -183,12 +184,25 @@ public class SystemBarHelper { } } + /** + * Sets whether the back button on the software navigation bar is visible. This only works if + * you have the STATUS_BAR permission. Otherwise framework will filter out this flag and this + * method call will not have any effect. + * + * <p>IMPORTANT: Do not assume that users have no way to go back when the back button is hidden. + * Many devices have physical back buttons, and accessibility services like TalkBack may have + * gestures mapped to back. Please use onBackPressed, onKeyDown, or other similar ways to + * make sure back button events are still handled (or ignored) properly. + */ + @RequiresPermission("android.permission.STATUS_BAR") public static void setBackButtonVisible(final Window window, final boolean visible) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { if (visible) { removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + removeImmersiveFlagsFromDecorView(window, STATUS_BAR_DISABLE_BACK); } else { addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + addImmersiveFlagsToDecorView(window, STATUS_BAR_DISABLE_BACK); } } } @@ -217,7 +231,7 @@ public class SystemBarHelper { * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} only takes effect when it is added to a view * instead of the window. */ - @TargetApi(VERSION_CODES.LOLLIPOP) + @TargetApi(VERSION_CODES.HONEYCOMB) private static void addImmersiveFlagsToDecorView(final Window window, final int vis) { getDecorView(window, new OnDecorViewInstalledListener() { @Override @@ -227,7 +241,7 @@ public class SystemBarHelper { }); } - @TargetApi(VERSION_CODES.LOLLIPOP) + @TargetApi(VERSION_CODES.HONEYCOMB) private static void removeImmersiveFlagsFromDecorView(final Window window, final int vis) { getDecorView(window, new OnDecorViewInstalledListener() { @Override diff --git a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java index a93694c..cf9ddac 100644 --- a/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java +++ b/library/main/src/com/android/setupwizardlib/util/WizardManagerHelper.java @@ -27,6 +27,8 @@ import android.support.annotation.VisibleForTesting; import com.android.setupwizardlib.R; +import java.util.Arrays; + public class WizardManagerHelper { private static final String ACTION_NEXT = "com.android.wizard.NEXT"; @@ -45,6 +47,8 @@ public class WizardManagerHelper { 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"; @@ -76,22 +80,22 @@ public class WizardManagerHelper { public static final String THEME_GLIF_V2 = "glif_v2"; /** - * @deprecated Use {@link #THEME_GLIF_V2} instead. - */ - @Deprecated - public static final String THEME_GLIF_PIXEL = THEME_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"; /** - * @deprecated Use {@link #THEME_GLIF_V2_LIGHT} instead. + * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the + * theme used in setup wizard for P. */ - @Deprecated - public static final String THEME_GLIF_PIXEL_LIGHT = THEME_GLIF_V2_LIGHT; + 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. @@ -140,13 +144,14 @@ public class WizardManagerHelper { */ public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) { dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE)); - dstIntent.putExtra(EXTRA_THEME, srcIntent.getStringExtra(EXTRA_THEME)); - dstIntent.putExtra(EXTRA_IS_FIRST_RUN, - srcIntent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false)); - dstIntent.putExtra(EXTRA_IS_DEFERRED_SETUP, - srcIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false)); - dstIntent.putExtra(EXTRA_SCRIPT_URI, srcIntent.getStringExtra(EXTRA_SCRIPT_URI)); - dstIntent.putExtra(EXTRA_ACTION_ID, srcIntent.getStringExtra(EXTRA_ACTION_ID)); + 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)); + } } /** @@ -213,6 +218,18 @@ public class WizardManagerHelper { } /** + * 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. @@ -236,10 +253,12 @@ public class WizardManagerHelper { */ 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_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.equals(theme) || THEME_GLIF_V2.equals(theme) + || THEME_GLIF_V3.equals(theme)) { return false; } else { return def; @@ -284,6 +303,10 @@ public class WizardManagerHelper { public static @StyleRes int getThemeRes(String theme, @StyleRes int defaultTheme) { if (theme != null) { switch (theme) { + case THEME_GLIF_V3_LIGHT: + return R.style.SuwThemeGlifV3_Light; + case THEME_GLIF_V3: + return R.style.SuwThemeGlifV3; case THEME_GLIF_V2_LIGHT: return R.style.SuwThemeGlifV2_Light; case THEME_GLIF_V2: diff --git a/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java b/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java index 3c678f8..bd0aead 100644 --- a/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java +++ b/library/main/src/com/android/setupwizardlib/view/CheckableLinearLayout.java @@ -58,6 +58,10 @@ public class CheckableLinearLayout extends LinearLayout implements Checkable { super(context, attrs, defStyleAttr, defStyleRes); } + { + setFocusable(true); + } + @Override protected int[] onCreateDrawableState(int extraSpace) { if (mChecked) { diff --git a/library/main/src/com/android/setupwizardlib/view/Illustration.java b/library/main/src/com/android/setupwizardlib/view/Illustration.java index b576174..c6968f8 100644 --- a/library/main/src/com/android/setupwizardlib/view/Illustration.java +++ b/library/main/src/com/android/setupwizardlib/view/Illustration.java @@ -137,7 +137,8 @@ public class Illustration extends FrameLayout { if (mAspectRatio != 0.0f) { int parentWidth = MeasureSpec.getSize(widthMeasureSpec); int illustrationHeight = (int) (parentWidth / mAspectRatio); - illustrationHeight -= illustrationHeight % mBaselineGridSize; + illustrationHeight = + (int) (illustrationHeight - (illustrationHeight % mBaselineGridSize)); setPadding(0, illustrationHeight, 0, 0); } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { diff --git a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java index 9c79eb5..e5c2fb1 100644 --- a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java +++ b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java @@ -23,9 +23,11 @@ import android.graphics.SurfaceTexture; import android.graphics.drawable.Animatable; import android.media.MediaPlayer; import android.os.Build.VERSION_CODES; +import android.support.annotation.Nullable; import android.support.annotation.RawRes; import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; +import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; @@ -51,8 +53,11 @@ public class IllustrationVideoView extends TextureView implements Animatable, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnInfoListener { + private static final String TAG = "IllustrationVideoView"; + protected float mAspectRatio = 1.0f; // initial guess until we know + @Nullable // Can be null when media player fails to initialize protected MediaPlayer mMediaPlayer; private @RawRes int mVideoResId = 0; @@ -129,15 +134,20 @@ public class IllustrationVideoView extends TextureView implements Animatable, mMediaPlayer = MediaPlayer.create(getContext(), mVideoResId); - mMediaPlayer.setSurface(mSurface); - mMediaPlayer.setOnPreparedListener(this); - mMediaPlayer.setOnSeekCompleteListener(this); - mMediaPlayer.setOnInfoListener(this); - - float aspectRatio = (float) mMediaPlayer.getVideoHeight() / mMediaPlayer.getVideoWidth(); - if (mAspectRatio != aspectRatio) { - mAspectRatio = aspectRatio; - requestLayout(); + if (mMediaPlayer != null) { + mMediaPlayer.setSurface(mSurface); + mMediaPlayer.setOnPreparedListener(this); + mMediaPlayer.setOnSeekCompleteListener(this); + mMediaPlayer.setOnInfoListener(this); + + float aspectRatio = + (float) mMediaPlayer.getVideoHeight() / mMediaPlayer.getVideoWidth(); + if (mAspectRatio != aspectRatio) { + mAspectRatio = aspectRatio; + requestLayout(); + } + } else { + Log.wtf(TAG, "Unable to initialize media player for video view"); } if (getWindowVisibility() == View.VISIBLE) { start(); diff --git a/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java b/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java new file mode 100644 index 0000000..10e91f4 --- /dev/null +++ b/library/main/src/com/android/setupwizardlib/view/TouchableMovementMethod.java @@ -0,0 +1,83 @@ +/* + * 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.view; + +import android.text.Selection; +import android.text.Spannable; +import android.text.method.LinkMovementMethod; +import android.text.method.MovementMethod; +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. + */ +public interface TouchableMovementMethod { + + /** + * @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(); + + /** + * 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(); + } + + boolean mLastEventResult = false; + MotionEvent mLastEvent; + + @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 MotionEvent getLastTouchEvent() { + return mLastEvent; + } + + @Override + public boolean isLastTouchEventHandled() { + return mLastEventResult; + } + } +} diff --git a/library/platform/res/values-v23/styles.xml b/library/platform/res/values-v27/styles.xml index 9fff5f1..6e36919 100644 --- a/library/platform/res/values-v23/styles.xml +++ b/library/platform/res/values-v27/styles.xml @@ -15,9 +15,10 @@ limitations under the License. --> +<!-- TODO(yukl): Bump this file to v28 once we can properly test that --> <!-- These styles are only included in the platform build, to make sure that they do not override the corresponding styles in the compatibility build. --> -<resources> +<resources xmlns:tools="http://schemas.android.com/tools"> <!-- General styles --> @@ -26,6 +27,7 @@ Theme.AppCompat. --> <style name="SuwThemeMaterial" parent="android:Theme.Material.NoActionBar"> <item name="android:colorAccent">@color/suw_color_accent_dark</item> + <item name="android:colorBackground">@color/suw_color_background_dark</item> <item name="android:indeterminateTint">@color/suw_progress_bar_color_dark</item> <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode">src_in</item> @@ -34,12 +36,14 @@ <item name="android:listPreferredItemPaddingStart">?attr/suwMarginSides</item> <item name="android:navigationBarColor">@android:color/black</item> <item name="android:statusBarColor">@android:color/black</item> - <item name="android:textAppearanceListItemSmall">@android:style/TextAppearance.Material.Body1</item> + <item name="android:textAppearanceListItemSmall">@style/TextAppearance.SuwItemSummary</item> <item name="android:textColorLink">@color/suw_link_color_dark</item> <item name="android:windowAnimationStyle">@style/Animation.SuwWindowAnimation</item> <item name="android:windowDisablePreview">true</item> <item name="android:windowSoftInputMode">adjustResize</item> + <item name="suwButtonAllCaps">true</item> + <item name="suwButtonFontFamily">sans-serif</item> <item name="suwCardBackground">@drawable/suw_card_bg</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> @@ -54,6 +58,7 @@ <style name="SuwThemeMaterial.Light" parent="android:Theme.Material.Light.NoActionBar"> <item name="android:colorAccent">@color/suw_color_accent_light</item> + <item name="android:colorBackground">@color/suw_color_background_light</item> <item name="android:indeterminateTint">@color/suw_progress_bar_color_light</item> <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode">src_in</item> @@ -62,12 +67,14 @@ <item name="android:listPreferredItemPaddingStart">?attr/suwMarginSides</item> <item name="android:navigationBarColor">@android:color/black</item> <item name="android:statusBarColor">@android:color/black</item> - <item name="android:textAppearanceListItemSmall">@android:style/TextAppearance.Material.Body1</item> + <item name="android:textAppearanceListItemSmall">@style/TextAppearance.SuwItemSummary</item> <item name="android:textColorLink">@color/suw_link_color_light</item> <item name="android:windowAnimationStyle">@style/Animation.SuwWindowAnimation</item> <item name="android:windowDisablePreview">true</item> <item name="android:windowSoftInputMode">adjustResize</item> + <item name="suwButtonAllCaps">true</item> + <item name="suwButtonFontFamily">sans-serif</item> <item name="suwCardBackground">@drawable/suw_card_bg</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> @@ -84,7 +91,7 @@ <style name="SuwThemeGlif" parent="android:Theme.Material.NoActionBar"> <item name="android:colorAccent">@color/suw_color_accent_glif_dark</item> <item name="android:colorBackground">@color/suw_glif_background_color_dark</item> - <item name="android:colorPrimary">@color/suw_color_accent_glif_dark</item> + <item name="android:colorPrimary">?android:attr/colorAccent</item> <item name="android:indeterminateTint">?android:attr/colorPrimary</item> <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode">src_in</item> @@ -100,12 +107,16 @@ <item name="android:windowDisablePreview">true</item> <item name="android:windowSoftInputMode">adjustResize</item> + <item name="suwButtonAllCaps">true</item> + <item name="suwButtonCornerRadius">@dimen/suw_glif_button_corner_radius</item> + <item name="suwButtonFontFamily">sans-serif</item> <item name="suwColorPrimary">?android:attr/colorPrimary</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> <item name="suwDividerInsetStart">@dimen/suw_items_glif_icon_divider_inset</item> <item name="suwDividerInsetStartNoIcon">@dimen/suw_items_glif_text_divider_inset</item> <item name="suwGlifHeaderGravity">start</item> + <item name="suwGlifIconStyle">@style/SuwGlifIcon</item> <item name="suwItemDescriptionStyle">@style/SuwItemContainer.Description.Glif</item> <item name="suwItemDescriptionTitleStyle">@style/SuwItemTitle.GlifDescription</item> <item name="suwListItemIconColor">@color/suw_list_item_icon_color_dark</item> @@ -116,7 +127,7 @@ <style name="SuwThemeGlif.Light" parent="android:Theme.Material.Light.NoActionBar"> <item name="android:colorAccent">@color/suw_color_accent_glif_light</item> <item name="android:colorBackground">@color/suw_glif_background_color_light</item> - <item name="android:colorPrimary">@color/suw_color_accent_glif_light</item> + <item name="android:colorPrimary">?android:attr/colorAccent</item> <item name="android:indeterminateTint">?android:attr/colorPrimary</item> <!-- Specify the indeterminateTintMode to work around a bug in Lollipop --> <item name="android:indeterminateTintMode">src_in</item> @@ -132,12 +143,16 @@ <item name="android:windowDisablePreview">true</item> <item name="android:windowSoftInputMode">adjustResize</item> + <item name="suwButtonAllCaps">true</item> + <item name="suwButtonCornerRadius">@dimen/suw_glif_button_corner_radius</item> + <item name="suwButtonFontFamily">sans-serif</item> <item name="suwColorPrimary">?android:attr/colorPrimary</item> <item name="suwFillContentLayoutStyle">@style/SuwFillContentLayout</item> <item name="suwDividerInsetEnd">0dp</item> <item name="suwDividerInsetStart">@dimen/suw_items_glif_icon_divider_inset</item> <item name="suwDividerInsetStartNoIcon">@dimen/suw_items_glif_text_divider_inset</item> <item name="suwGlifHeaderGravity">start</item> + <item name="suwGlifIconStyle">@style/SuwGlifIcon</item> <item name="suwItemDescriptionStyle">@style/SuwItemContainer.Description.Glif</item> <item name="suwItemDescriptionTitleStyle">@style/SuwItemTitle.GlifDescription</item> <item name="suwListItemIconColor">@color/suw_list_item_icon_color_light</item> @@ -145,6 +160,27 @@ <item name="suwScrollIndicators">bottom</item> </style> + <style name="SuwThemeGlifV3" parent="SuwThemeGlifV2"> + <item name="android:colorAccent">@color/suw_color_accent_glif_v3</item> + + <item name="suwButtonAllCaps">false</item> + <item name="suwButtonCornerRadius">@dimen/suw_glif_v3_button_corner_radius</item> + <item name="suwButtonFontFamily">@string/suwFontSecondaryMedium</item> + </style> + + <style name="SuwThemeGlifV3.Light" parent="SuwThemeGlifV2.Light"> + <item name="android:colorAccent">@color/suw_color_accent_glif_v3</item> + <item name="android:navigationBarColor">@color/suw_glif_v3_nav_bar_color_light</item> + <!-- Ignore NewApi: For some reason lint seems to think this API is new in v28 (b/73514594) --> + <item name="android:navigationBarDividerColor" tools:ignore="NewApi">@color/suw_glif_v3_nav_bar_divider_color_light</item> + <!-- Ignore NewApi: For some reason lint seems to think this API is new in v28 (b/73514594) --> + <item name="android:windowLightNavigationBar" tools:ignore="NewApi">true</item> + + <item name="suwButtonAllCaps">false</item> + <item name="suwButtonCornerRadius">@dimen/suw_glif_v3_button_corner_radius</item> + <item name="suwButtonFontFamily">@string/suwFontSecondaryMedium</item> + </style> + <!-- Button styles --> <style name="SuwGlifButton.Primary" parent="android:Widget.Material.Button.Colored"> @@ -154,8 +190,13 @@ <item name="android:buttonStyle">@style/SuwGlifButton.Primary</item> <!-- Values used in styles --> + <item name="android:fontFamily">?attr/suwButtonFontFamily</item> <item name="android:paddingLeft">@dimen/suw_glif_button_padding</item> <item name="android:paddingRight">@dimen/suw_glif_button_padding</item> + <item name="android:textAllCaps">?attr/suwButtonAllCaps</item> + + <!-- Values used in themes --> + <item name="android:buttonCornerRadius" tools:ignore="NewApi">?attr/suwButtonCornerRadius</item> </style> <style name="SuwGlifButton.Secondary" parent="android:Widget.Material.Button.Borderless.Colored"> @@ -166,11 +207,14 @@ <item name="android:theme">@style/SuwGlifButton.Secondary</item> <!-- Values used in styles --> + <item name="android:fontFamily">?attr/suwButtonFontFamily</item> <item name="android:minWidth">0dp</item> <item name="android:paddingLeft">@dimen/suw_glif_button_padding</item> <item name="android:paddingRight">@dimen/suw_glif_button_padding</item> + <item name="android:textAllCaps">?attr/suwButtonAllCaps</item> <!-- Values used in themes --> + <item name="android:buttonCornerRadius" tools:ignore="NewApi">?attr/suwButtonCornerRadius</item> <item name="android:colorControlHighlight">@color/suw_flat_button_highlight</item> </style> diff --git a/library/platform/src/com/android/setupwizardlib/view/RichTextView.java b/library/platform/src/com/android/setupwizardlib/view/RichTextView.java index 5a78561..fa68a68 100644 --- a/library/platform/src/com/android/setupwizardlib/view/RichTextView.java +++ b/library/platform/src/com/android/setupwizardlib/view/RichTextView.java @@ -20,16 +20,18 @@ import android.content.Context; import android.text.Annotation; import android.text.SpannableString; import android.text.Spanned; -import android.text.method.LinkMovementMethod; +import android.text.method.MovementMethod; import android.text.style.ClickableSpan; import android.text.style.TextAppearanceSpan; import android.util.AttributeSet; import android.util.Log; +import android.view.MotionEvent; import android.widget.TextView; import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.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 @@ -112,7 +114,7 @@ public class RichTextView extends TextView implements OnLinkClickListener { // nullifying any return values of MovementMethod.onTouchEvent. // To still allow propagating touch events to the parent when this view doesn't have // links, we only set the movement method here if the text contains links. - setMovementMethod(LinkMovementMethod.getInstance()); + setMovementMethod(TouchableLinkMovementMethod.getInstance()); } else { setMovementMethod(null); } @@ -121,6 +123,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) { @@ -132,6 +139,25 @@ public class RichTextView extends TextView implements OnLinkClickListener { 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; } diff --git a/library/recyclerview/res/layout/suw_glif_recycler_template_content.xml b/library/recyclerview/res/layout/suw_glif_recycler_template_content.xml index e8d209b..c16f85a 100644 --- a/library/recyclerview/res/layout/suw_glif_recycler_template_content.xml +++ b/library/recyclerview/res/layout/suw_glif_recycler_template_content.xml @@ -23,6 +23,11 @@ android:layout_height="match_parent" android:orientation="vertical"> + <ViewStub + android:id="@+id/suw_layout_sticky_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <!-- Ignore UnusedAttribute: scrollIndicators is new in M. Default to no indicators in older versions. --> <com.android.setupwizardlib.view.HeaderRecyclerView diff --git a/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java b/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java index 75b1c7a..e8a021d 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/GlifRecyclerLayout.java @@ -107,10 +107,12 @@ public class GlifRecyclerLayout extends GlifLayout { } @Override - public View findManagedViewById(int id) { + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public <T extends View> T findManagedViewById(int id) { final View header = mRecyclerMixin.getHeader(); if (header != null) { - final View view = header.findViewById(id); + final T view = header.findViewById(id); if (view != null) { return view; } diff --git a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java index 5ff825d..6f3aed4 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java +++ b/library/recyclerview/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java @@ -127,10 +127,12 @@ public class SetupWizardRecyclerLayout extends SetupWizardLayout { } @Override - public View findManagedViewById(int id) { + // Returning generic type is the common pattern used for findViewBy* methods + @SuppressWarnings("TypeParameterUnusedInFormals") + public <T extends View> T findManagedViewById(int id) { final View header = mRecyclerMixin.getHeader(); if (header != null) { - final View view = header.findViewById(id); + final T view = header.findViewById(id); if (view != null) { return view; } diff --git a/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java b/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java index 78280a6..0703c17 100644 --- a/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java +++ b/library/recyclerview/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java @@ -143,9 +143,9 @@ public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder> @Override public void onBindViewHolder(ItemViewHolder holder, int position) { final IItem item = getItem(position); - item.onBindView(holder.itemView); holder.setEnabled(item.isEnabled()); holder.setItem(item); + item.onBindView(holder.itemView); } @Override 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 b509389..5912f7f 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 @@ -27,7 +27,6 @@ import static org.robolectric.RuntimeEnvironment.application; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.OnScrollListener; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import org.junit.Before; @@ -38,7 +37,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) @RunWith(SuwLibRobolectricTestRunner.class) public class RecyclerViewScrollHandlingDelegateTest { diff --git a/library/rules.gradle b/library/rules.gradle index 6b0acce..9baa390 100644 --- a/library/rules.gradle +++ b/library/rules.gradle @@ -13,6 +13,23 @@ android { publishNonDefault true + flavorDimensions 'compat' + + productFlavors { + // DEPRECATED: Platform version that will not include the compatibility libraries + platformDeprecated { + dimension 'compat' + // TODO(yukl): Bump this file to v28 once we can properly test that + minSdkVersion 27 + } + + // Provides backwards compatibility for Gingerbread or above, using support libraries. + gingerbreadCompat { + dimension 'compat' + minSdkVersion 9 + } + } + sourceSets { main { manifest.srcFile 'main/AndroidManifest.xml' @@ -21,43 +38,7 @@ android { res.srcDirs = ['main/res'] } - productFlavors { - // Platform version that will not include the compatibility libraries - platform { - minSdkVersion 23 - - dependencies { - // Read the dependencies from the "deps" map in the extra properties. - // - // For builds in the Android tree we want to build the dependencies from source - // for reproducible builds, for example in build.gradle define something like - // this: - // ext { - // deps = ['project-name': project(':project-path')] - // } - // - // For standalone project clients, since the source may not be available, we - // fetch the dependencies from maven. For example in standalone.gradle define - // something like this: - // ext { - // deps = ['project-name': 'com.example.group:project-name:1.0.0'] - // } - // - platformCompile deps['support-annotations'] - } - } - - // Provides backwards compatibility for Gingerbread or above, using support libraries. - gingerbreadCompat { - minSdkVersion 9 - dependencies { - gingerbreadCompatCompile deps['support-appcompat-v7'] - gingerbreadCompatCompile deps['support-recyclerview-v7'] - } - } - } - - platform { + platformDeprecated { java.srcDirs = ['platform/src'] res.srcDirs = ['platform/res'] } @@ -68,3 +49,26 @@ android { } } } + +dependencies { + // Read the dependencies from the "deps" map in the extra properties. + // + // For builds in the Android tree we want to build the dependencies from source + // for reproducible builds, for example in build.gradle define something like + // this: + // ext { + // deps = ['project-name': project(':project-path')] + // } + // + // For standalone project clients, since the source may not be available, we + // fetch the dependencies from maven. For example in standalone.gradle define + // something like this: + // ext { + // deps = ['project-name': 'com.example.group:project-name:1.0.0'] + // } + // + platformDeprecatedCompile deps['support-annotations'] + + gingerbreadCompatCompile deps['support-appcompat-v7'] + gingerbreadCompatCompile deps['support-recyclerview-v7'] +} diff --git a/library/self.gradle b/library/self.gradle index 6a405e2..a4bff4e 100644 --- a/library/self.gradle +++ b/library/self.gradle @@ -5,6 +5,18 @@ apply from: 'standalone-rules.gradle' apply from: '../tools/gradle/dist-library-instrumentation-tests.gradle' apply from: '../tools/gradle/dist-unit-tests.gradle' +apply plugin: 'net.ltgt.errorprone' + +buildscript { + repositories { + maven { url "$rootDir/prebuilts/tools/common/m2/repository" } + } + + dependencies { + classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13" + } +} + // Add targets for tests android.sourceSets { androidTest { @@ -13,16 +25,17 @@ android.sourceSets { res.srcDirs = ['test/instrumentation/res'] dependencies { - androidTestCompile 'com.android.support.test:rules:0.5' - androidTestCompile 'com.android.support.test:runner:0.5' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestCompile 'junit:junit:4.+' - androidTestCompile 'org.mockito:mockito-core:1.9.5' + androidTestImplementation 'com.android.support.test:rules:1.0.0' + androidTestImplementation 'com.android.support.test:runner:1.0.0' + androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' + androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestImplementation 'com.google.truth:truth:0.31' + androidTestImplementation 'junit:junit:4.+' + androidTestImplementation 'org.mockito:mockito-core:1.9.5' } } - androidTestPlatform { + androidTestPlatformDeprecated { java.srcDirs = ['platform/test/src'] } @@ -38,12 +51,13 @@ android.sourceSets { java.srcDirs = ['test/robotest/src'] dependencies { - testCompile 'org.robolectric:robolectric:3.+' - testCompile 'org.robolectric:shadows-core:3.+' - testCompile 'junit:junit:4.+' - testCompile 'org.mockito:mockito-core:1.9.5' + testImplementation 'org.robolectric:robolectric:3.6.1' + testImplementation 'org.robolectric:shadows-framework:3.6.1' + testImplementation 'junit:junit:4.+' + testImplementation 'com.google.truth:truth:0.31' + testImplementation 'org.mockito:mockito-core:1.9.5' // Workaround for https://github.com/robolectric/robolectric/issues/2566 - testCompile 'org.khronos:opengl-api:gl1.1-android-2.1_r1' + testImplementation 'org.khronos:opengl-api:gl1.1-android-2.1_r1' } } @@ -51,6 +65,9 @@ android.sourceSets { java.srcDirs = ['gingerbread/test/robotest/src', 'recyclerview/test/robotest/src'] } } + +android.testOptions.unitTests.includeAndroidResources = true + android.defaultConfig.testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" android.lintOptions { abortOnError true @@ -64,14 +81,3 @@ android.lintOptions { android.libraryVariants.all { variant -> variant.assemble.dependsOn(tasks.findByName('lint')) } - -// For compatibility with existing continuous test configurations, copy the file to -// setup-wizard-libTest.apk -// TODO: Remove this once continuous test configurations are updated to handle the new file name -task createLegacyTestApk(type: Copy) { - from "${project.ext.distDir}/setup-wizard-lib-gingerbreadCompat-debug-androidTest.apk" - into "${project.ext.distDir}" - rename ('setup-wizard-lib-gingerbreadCompat-debug-androidTest.apk', 'setup-wizard-libTest.apk') -} - -tasks.dist.finalizedBy createLegacyTestApk diff --git a/library/standalone.gradle b/library/standalone.gradle index a2119ac..132b908 100644 --- a/library/standalone.gradle +++ b/library/standalone.gradle @@ -16,5 +16,5 @@ apply from: 'standalone-rules.gradle' -android.compileSdkVersion 25 -android.buildToolsVersion '25.0.0' +android.compileSdkVersion 26 +android.buildToolsVersion '26.0.0' diff --git a/library/test/instrumentation/AndroidManifest.xml b/library/test/instrumentation/AndroidManifest.xml index 44d923f..0cbc9c1 100644 --- a/library/test/instrumentation/AndroidManifest.xml +++ b/library/test/instrumentation/AndroidManifest.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.android.setupwizardlib.test"> - <uses-sdk tools:overrideLibrary="android.support.test,android.app,android.support.test.rule" /> + <uses-sdk tools:overrideLibrary="android.support.test.rules,android.support.test.runner" /> <application> <activity android:name=".util.DrawingTestActivity" /> 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 a1f2b54..5a36f4a 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/template/IconMixinTest.java @@ -16,6 +16,8 @@ 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; @@ -27,10 +29,12 @@ import android.content.res.XmlResourceParser; 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 com.android.setupwizardlib.TemplateLayout; @@ -74,6 +78,26 @@ public class IconMixinTest { 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 @@ -101,5 +125,20 @@ public class IconMixinTest { .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/test/GlifPatternDrawableTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java index 99c87fb..37ac41a 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java @@ -137,7 +137,7 @@ public class GlifPatternDrawableTest { assertEquals("Matrices should match", expected, canvas.getMatrix()); } - @SmallTest + @Test public void testScaleToCanvasMaxSize() { final Canvas canvas = new Canvas(); final Matrix expected = new Matrix(canvas.getMatrix()); 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 0ebf7cb..98c28f6 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java +++ b/library/test/instrumentation/src/com/android/setupwizardlib/test/SystemBarHelperTest.java @@ -16,6 +16,8 @@ package com.android.setupwizardlib.test; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import android.annotation.SuppressLint; @@ -31,6 +33,7 @@ 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; @@ -131,7 +134,9 @@ public class SystemBarHelperTest { @Test public void testShowSystemBarsWindow() { final Window window = createWindowWithSystemUiVisibility(0x456); - SystemBarHelper.showSystemBars(window, InstrumentationRegistry.getContext()); + 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", @@ -191,11 +196,15 @@ public class SystemBarHelperTest { @UiThreadTest @Test public void testSetBackButtonVisibleTrue() { - final Window window = createWindowWithSystemUiVisibility(0x456); + final Window window = createWindowWithSystemUiVisibility(STATUS_BAR_DISABLE_BACK | 0x456); SystemBarHelper.setBackButtonVisible(window, true); if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - assertEquals("View visibility should be 0x456", 0x456, - window.getAttributes().systemUiVisibility); + assertThat(window.getAttributes().systemUiVisibility) + .named("window sysUiVisibility") + .isEqualTo(0x456); + assertThat(window.getDecorView().getSystemUiVisibility()) + .named("decor view sysUiVisibility") + .isEqualTo(0x456); } } @@ -205,8 +214,12 @@ public class SystemBarHelperTest { final Window window = createWindowWithSystemUiVisibility(0x456); SystemBarHelper.setBackButtonVisible(window, false); if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { - assertEquals("STATUS_BAR_DISABLE_BACK should be added to systemUiVisibility", - 0x456 | STATUS_BAR_DISABLE_BACK, window.getAttributes().systemUiVisibility); + 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); } } diff --git a/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java b/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java index d46409d..d2d1ee0 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/GlifLayoutTest.java @@ -32,6 +32,8 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.support.annotation.IdRes; import android.view.ContextThemeWrapper; import android.view.View; @@ -53,7 +55,7 @@ import org.robolectric.Robolectric; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) public class GlifLayoutTest { private Context mContext; @@ -266,6 +268,74 @@ public class GlifLayoutTest { 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); diff --git a/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java b/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java new file mode 100644 index 0000000..aa2cce3 --- /dev/null +++ b/library/test/robotest/src/com/android/setupwizardlib/gesture/ConsecutiveTapsGestureDetectorTest.java @@ -0,0 +1,118 @@ +/* + * 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.gesture; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.robolectric.RuntimeEnvironment.application; + +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@RunWith(SuwLibRobolectricTestRunner.class) +public class ConsecutiveTapsGestureDetectorTest { + + @Mock + private ConsecutiveTapsGestureDetector.OnConsecutiveTapsListener mListener; + + private ConsecutiveTapsGestureDetector mDetector; + private int mSlop; + private int mTapTimeout; + + @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); + + mSlop = ViewConfiguration.get(application).getScaledDoubleTapSlop(); + mTapTimeout = ViewConfiguration.getDoubleTapTimeout(); + } + + @Test + public void onTouchEvent_shouldTriggerCallbackOnFourTaps() { + InOrder inOrder = inOrder(mListener); + + tap(0, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + + tap(100, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + + tap(200, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(3)); + + tap(300, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(4)); + } + + @Test + public void onTouchEvent_tapOnDifferentLocation_shouldResetCounter() { + InOrder inOrder = inOrder(mListener); + + tap(0, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + + tap(100, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + + tap(200, 25f + mSlop * 2, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + + tap(300, 25f + mSlop * 2, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + } + + @Test + public void onTouchEvent_tapAfterTimeout_shouldResetCounter() { + InOrder inOrder = inOrder(mListener); + + tap(0, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + + tap(100, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(2)); + + tap(200 + mTapTimeout, 25f, 25f); + inOrder.verify(mListener).onConsecutiveTaps(eq(1)); + + tap(300 + mTapTimeout, 25f, 25f); + inOrder.verify(mListener).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)); + } +} 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 93b9a6d..40e5da8 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/items/ButtonItemTest.java @@ -39,7 +39,6 @@ import android.widget.Button; import android.widget.FrameLayout; import android.widget.LinearLayout; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.R; import com.android.setupwizardlib.items.ButtonItem.OnClickListener; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; @@ -50,9 +49,7 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config( - constants = BuildConfig.class, - sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) public class ButtonItemTest { private ViewGroup mParent; 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 a61b750..ecaec71 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/items/ItemGroupTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import org.junit.Before; @@ -36,9 +35,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config( - constants = BuildConfig.class, - 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"); diff --git a/library/test/robotest/src/com/android/setupwizardlib/robolectric/PatchedGradleManifestFactory.java b/library/test/robotest/src/com/android/setupwizardlib/robolectric/PatchedGradleManifestFactory.java deleted file mode 100644 index 64c63e7..0000000 --- a/library/test/robotest/src/com/android/setupwizardlib/robolectric/PatchedGradleManifestFactory.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.setupwizardlib.robolectric; - -import org.robolectric.annotation.Config; -import org.robolectric.internal.GradleManifestFactory; -import org.robolectric.internal.ManifestIdentifier; -import org.robolectric.res.FileFsFile; -import org.robolectric.util.Logger; -import org.robolectric.util.ReflectionHelpers; - -import java.io.File; -import java.net.URL; - -/** - * Modified GradleManifestFactory to patch an issue where some build variants have merged - * resources under res/merged/variant/type while others have it under bundles/variant/type/res. - * - * The change is that in the .exists() checks below we check for specific folders for the build - * variant rather than checking existence at the parent directory. - */ -class PatchedGradleManifestFactory extends GradleManifestFactory { - - @Override - public ManifestIdentifier identify(Config config) { - if (config.constants() == Void.class) { - Logger.error("Field 'constants' not specified in @Config annotation"); - Logger.error("This is required when using Robolectric with Gradle!"); - throw new RuntimeException("No 'constants' field in @Config annotation!"); - } - - final String buildOutputDir = getBuildOutputDir(config); - final String type = getType(config); - final String flavor = getFlavor(config); - final String abiSplit = getAbiSplit(config); - final String packageName = config.packageName().isEmpty() - ? config.constants().getPackage().getName() - : config.packageName(); - - final FileFsFile res; - final FileFsFile assets; - final FileFsFile manifest; - - if (FileFsFile.from(buildOutputDir, "data-binding-layout-out", flavor, type).exists()) { - // Android gradle plugin 1.5.0+ puts the merged layouts in data-binding-layout-out. - // https://github.com/robolectric/robolectric/issues/2143 - res = FileFsFile.from(buildOutputDir, "data-binding-layout-out", flavor, type); - } else if (FileFsFile.from(buildOutputDir, "res", "merged", flavor, type).exists()) { - // res/merged added in Android Gradle plugin 1.3-beta1 - res = FileFsFile.from(buildOutputDir, "res", "merged", flavor, type); - } else if (FileFsFile.from(buildOutputDir, "res", flavor, type).exists()) { - res = FileFsFile.from(buildOutputDir, "res", flavor, type); - } else { - res = FileFsFile.from(buildOutputDir, "bundles", flavor, type, "res"); - } - - if (FileFsFile.from(buildOutputDir, "assets", flavor, type).exists()) { - assets = FileFsFile.from(buildOutputDir, "assets", flavor, type); - } else { - assets = FileFsFile.from(buildOutputDir, "bundles", flavor, type, "assets"); - } - - String manifestName = config.manifest(); - URL manifestUrl = getClass().getClassLoader().getResource(manifestName); - if (manifestUrl != null && manifestUrl.getProtocol().equals("file")) { - manifest = FileFsFile.from(manifestUrl.getPath()); - } else if (FileFsFile.from(buildOutputDir, "manifests", "full", flavor, abiSplit, type, - manifestName).exists()) { - manifest = FileFsFile.from( - buildOutputDir, "manifests", "full", flavor, abiSplit, type, manifestName); - } else if (FileFsFile.from(buildOutputDir, "manifests", "aapt", flavor, abiSplit, type, - manifestName).exists()) { - // Android gradle plugin 2.2.0+ can put library manifest files inside of "aapt" - // instead of "full" - manifest = FileFsFile.from(buildOutputDir, "manifests", "aapt", flavor, abiSplit, - type, manifestName); - } else { - manifest = FileFsFile.from(buildOutputDir, "bundles", flavor, abiSplit, type, - manifestName); - } - - return new ManifestIdentifier(manifest, res, assets, packageName, null); - } - - private static String getBuildOutputDir(Config config) { - return config.buildDir() + File.separator + "intermediates"; - } - - private static String getType(Config config) { - try { - return ReflectionHelpers.getStaticField(config.constants(), "BUILD_TYPE"); - } catch (Throwable e) { - return null; - } - } - - private static String getFlavor(Config config) { - try { - return ReflectionHelpers.getStaticField(config.constants(), "FLAVOR"); - } catch (Throwable e) { - return null; - } - } - - private static String getAbiSplit(Config config) { - try { - return config.abiSplit(); - } catch (Throwable e) { - return null; - } - } -} 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 509201a..61baa23 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java +++ b/library/test/robotest/src/com/android/setupwizardlib/robolectric/SuwLibRobolectricTestRunner.java @@ -20,42 +20,13 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.robolectric.internal.ManifestFactory; public class SuwLibRobolectricTestRunner extends RobolectricTestRunner { - private String mModuleRootPath; - public SuwLibRobolectricTestRunner(Class<?> testClass) throws InitializationError { super(testClass); } - // Hack to determine the module root path in the build folder (e.g. out/gradle/setup-wizard-lib) - private void updateModuleRootPath(Config config) { - String moduleRoot = config.constants().getResource("").toString() - .replace("file:", "").replace("jar:", ""); - mModuleRootPath = - moduleRoot.substring(0, moduleRoot.lastIndexOf("/build")) + "/setup-wizard-lib"; - } - - /** - * Return the default config used to run Robolectric tests. - */ - @Override - protected Config buildGlobalConfig() { - Config parent = super.buildGlobalConfig(); - updateModuleRootPath(parent); - return new Config.Builder(parent) - .setBuildDir(mModuleRootPath + "/build") - .build(); - } - - @Override - protected ManifestFactory getManifestFactory(Config config) { - return new PatchedGradleManifestFactory(); - } - @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { System.out.println("===== Running " + method + " ====="); diff --git a/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java b/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java new file mode 100644 index 0000000..f1d37c8 --- /dev/null +++ b/library/test/robotest/src/com/android/setupwizardlib/shadow/ShadowLog.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.shadow; + +import android.util.Log; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(Log.class) +public class ShadowLog extends org.robolectric.shadows.ShadowLog { + + public static boolean sWtfIsFatal = true; + + public static class TerribleFailure extends RuntimeException { + + public TerribleFailure(String msg, Throwable cause) { + super(msg, cause); + } + } + + @Implementation + public static void wtf(String tag, String msg) { + org.robolectric.shadows.ShadowLog.wtf(tag, msg); + if (sWtfIsFatal) { + throw new TerribleFailure(msg, null); + } + } + + @Implementation + public static void wtf(String tag, String msg, Throwable throwable) { + org.robolectric.shadows.ShadowLog.wtf(tag, msg, throwable); + if (sWtfIsFatal) { + throw new TerribleFailure(msg, throwable); + } + } +} diff --git a/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java b/library/test/robotest/src/com/android/setupwizardlib/span/LinkSpanTest.java index f86e057..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,26 +16,28 @@ 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.BuildConfig; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class) 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"); @@ -46,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"); @@ -57,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); @@ -68,6 +70,28 @@ 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 { public LinkSpan clickedSpan = null; 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 fa81dc0..ec3622d 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java @@ -31,7 +31,6 @@ import android.widget.AbsListView.OnScrollListener; import android.widget.BaseAdapter; import android.widget.ListView; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import org.junit.Before; @@ -42,7 +41,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) @RunWith(SuwLibRobolectricTestRunner.class) public class ListViewScrollHandlingDelegateTest { 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 8e39c43..c641449 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java @@ -34,7 +34,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.TemplateLayout; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.template.RequireScrollMixin.OnRequireScrollStateChangedListener; @@ -48,7 +47,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) @RunWith(SuwLibRobolectricTestRunner.class) public class RequireScrollMixinTest { 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 f77e256..429445c 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import com.android.setupwizardlib.view.BottomScrollView; import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener; @@ -36,7 +35,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) @RunWith(SuwLibRobolectricTestRunner.class) public class ScrollViewScrollHandlingDelegateTest { 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 2be64e1..c10c122 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifDimensionTest.java @@ -26,7 +26,6 @@ import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.ContextThemeWrapper; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; @@ -36,7 +35,7 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = Config.ALL_SDKS) +@Config(sdk = Config.ALL_SDKS) public class GlifDimensionTest { private Context mContext; 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 aea2c03..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,10 @@ 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; import android.annotation.TargetApi; @@ -29,8 +31,8 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.view.ContextThemeWrapper; import android.widget.Button; +import android.widget.ProgressBar; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; @@ -41,7 +43,7 @@ import org.robolectric.Robolectric; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class GlifStyleTest { private Context mContext; @@ -58,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 @@ -76,6 +77,15 @@ public class GlifStyleTest { assertEquals(0x00000000, activity.getWindow().getStatusBarColor()); } + @Test + public void glifLoadingScreen_shouldHaveProgressBar() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + activity.setContentView(R.layout.suw_glif_loading_screen); + + assertTrue("Progress bar should exist", + activity.findViewById(R.id.suw_large_progress_bar) instanceof ProgressBar); + } + private static class GlifThemeActivity extends Activity { @Override diff --git a/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java b/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java new file mode 100644 index 0000000..613f2aa --- /dev/null +++ b/library/test/robotest/src/com/android/setupwizardlib/util/GlifV3StyleTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.setupwizardlib.util; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.View; +import android.widget.Button; + +import com.android.setupwizardlib.R; +import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; + +@RunWith(SuwLibRobolectricTestRunner.class) +@Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.NEWEST_SDK) +public class GlifV3StyleTest { + + @Test + public void activityWithGlifV3Theme_shouldUseLightNavBarOnV27OrAbove() { + GlifThemeActivity activity = Robolectric.setupActivity(GlifThemeActivity.class); + if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) { + assertEquals( + activity.getWindow().getNavigationBarColor(), + Color.WHITE); + int vis = activity.getWindow().getDecorView().getSystemUiVisibility(); + assertTrue((vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0); + } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + assertEquals( + activity.getWindow().getNavigationBarColor(), + Color.BLACK); + } + // Nav bar color is not customizable pre-L + } + + @Test + public void 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 { + + @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 f47eef1..f8e71be 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/PartnerTest.java @@ -31,29 +31,33 @@ 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.BuildConfig; import com.android.setupwizardlib.R; 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.RuntimeEnvironment; +import org.robolectric.Shadows; import org.robolectric.annotation.Config; -import org.robolectric.res.builder.DefaultPackageManager; +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(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) +@Config( + sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }, + shadows = ShadowApplicationPackageManager.class) public class PartnerTest { private static final String ACTION_PARTNER_CUSTOMIZATION = @@ -62,7 +66,7 @@ public class PartnerTest { private Context mContext; private Resources mPartnerResources; - private TestPackageManager mPackageManager; + private ShadowApplicationPackageManager mPackageManager; @Before public void setUp() throws Exception { @@ -71,8 +75,9 @@ public class PartnerTest { mContext = spy(application); mPartnerResources = spy(ShadowResources.getSystem()); - mPackageManager = new TestPackageManager(); - RuntimeEnvironment.setRobolectricPackageManager(mPackageManager); + mPackageManager = + (ShadowApplicationPackageManager) Shadows.shadowOf(application.getPackageManager()); + mPackageManager.partnerResources = mPartnerResources; } @Test @@ -127,6 +132,20 @@ public class PartnerTest { } @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 testLoadDefaultValue() { mPackageManager.addResolveInfoForIntent( new Intent(ACTION_PARTNER_CUSTOMIZATION), @@ -173,13 +192,18 @@ public class PartnerTest { return info; } - private class TestPackageManager extends DefaultPackageManager { + @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 mPartnerResources; + return partnerResources; } else { return super.getResourcesForApplication(app); } 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 4c460c8..0d15ef4 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/util/WizardManagerHelperTest.java @@ -31,7 +31,6 @@ import android.provider.Settings.Global; import android.provider.Settings.Secure; import android.support.annotation.StyleRes; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; @@ -39,8 +38,12 @@ 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; + @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = Config.NEWEST_SDK) +@Config(sdk = Config.NEWEST_SDK) public class WizardManagerHelperTest { @Test @@ -88,83 +91,73 @@ public class WizardManagerHelperTest { } @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 testHoloIsNotLightTheme() { - final Intent intent = new Intent(); - intent.putExtra("theme", "holo"); - assertFalse("Theme holo should not be light theme", - WizardManagerHelper.isLightTheme(intent, true)); - } - - @Test - public void testHoloLightIsLightTheme() { - final Intent intent = new Intent(); - intent.putExtra("theme", "holo_light"); - assertTrue("Theme holo_light should be light theme", - WizardManagerHelper.isLightTheme(intent, false)); - } - - @Test - public void testMaterialIsNotLightTheme() { - final Intent intent = new Intent(); - intent.putExtra("theme", "material"); - assertFalse("Theme material should not be light theme", - WizardManagerHelper.isLightTheme(intent, true)); - } - - @Test - public void testMaterialLightIsLightTheme() { + public void testIsPreDeferredSetupTrue() { final Intent intent = new Intent(); - intent.putExtra("theme", "material_light"); - assertTrue("Theme material_light should be light theme", - WizardManagerHelper.isLightTheme(intent, false)); - } - - @Test - public void testGlifIsDarkTheme() { - final Intent intent = new Intent(); - intent.putExtra("theme", "glif"); - assertFalse("Theme glif should be dark theme", - WizardManagerHelper.isLightTheme(intent, false)); - assertFalse("Theme glif should be dark theme", - WizardManagerHelper.isLightTheme(intent, true)); + intent.putExtra("preDeferredSetup", true); + assertTrue("Is pre-deferred setup wizard should be true", + WizardManagerHelper.isPreDeferredSetupWizard(intent)); } @Test - public void testGlifLightIsLightTheme() { + public void testIsSetupWizardFalse() { final Intent intent = new Intent(); - intent.putExtra("theme", "glif_light"); - assertTrue("Theme glif_light should be light theme", - WizardManagerHelper.isLightTheme(intent, false)); - assertTrue("Theme glif_light should be light theme", - WizardManagerHelper.isLightTheme(intent, true)); + intent.putExtra("firstRun", false); + assertFalse("Is setup wizard should be true", + WizardManagerHelper.isSetupWizardIntent(intent)); } @Test - public void testGlifV2IsDarkTheme() { - final Intent intent = new Intent(); - intent.putExtra("theme", "glif_v2"); - assertFalse("Theme glif_v2 should be dark theme", - WizardManagerHelper.isLightTheme(intent, false)); - assertFalse("Theme glif_v2 should be dark theme", - WizardManagerHelper.isLightTheme(intent, true)); + public void isLightTheme_shouldReturnTrue_whenThemeIsLight() { + List<String> lightThemes = Arrays.asList( + "holo_light", + "material_light", + "glif_light", + "glif_v2_light", + "glif_v3_light" + ); + ArrayList<String> unexpectedIntentThemes = new ArrayList<>(); + ArrayList<String> unexpectedStringThemes = new ArrayList<>(); + for (final String theme : lightThemes) { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); + if (!WizardManagerHelper.isLightTheme(intent, false)) { + unexpectedIntentThemes.add(theme); + } + if (!WizardManagerHelper.isLightTheme(theme, false)) { + unexpectedStringThemes.add(theme); + } + } + assertTrue("Intent themes " + unexpectedIntentThemes + " should be light", + unexpectedIntentThemes.isEmpty()); + assertTrue("String themes " + unexpectedStringThemes + " should be light", + unexpectedStringThemes.isEmpty()); } @Test - public void testGlifV2LightIsLightTheme() { - final Intent intent = new Intent(); - intent.putExtra("theme", "glif_v2_light"); - assertTrue("Theme glif_v2_light should be light theme", - WizardManagerHelper.isLightTheme(intent, false)); - assertTrue("Theme glif_v2_light should be light theme", - WizardManagerHelper.isLightTheme(intent, true)); + public void isLightTheme_shouldReturnFalse_whenThemeIsNotLight() { + List<String> lightThemes = Arrays.asList( + "holo", + "material", + "glif", + "glif_v2", + "glif_v3" + ); + ArrayList<String> unexpectedIntentThemes = new ArrayList<>(); + ArrayList<String> unexpectedStringThemes = new ArrayList<>(); + for (final String theme : lightThemes) { + Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); + if (WizardManagerHelper.isLightTheme(intent, true)) { + unexpectedIntentThemes.add(theme); + } + if (WizardManagerHelper.isLightTheme(theme, true)) { + unexpectedStringThemes.add(theme); + } + } + assertTrue("Intent themes " + unexpectedIntentThemes + " should not be light", + unexpectedIntentThemes.isEmpty()); + assertTrue("String themes " + unexpectedStringThemes + " should not be light", + unexpectedStringThemes.isEmpty()); } @Test @@ -187,19 +180,15 @@ public class WizardManagerHelperTest { } @Test - public void testIsLightThemeString() { - assertTrue("isLightTheme should return true for material_light", - WizardManagerHelper.isLightTheme("material_light", false)); - assertFalse("isLightTheme should return false for material", - WizardManagerHelper.isLightTheme("material", false)); - assertTrue("isLightTheme should return true for holo_light", - WizardManagerHelper.isLightTheme("holo_light", false)); - assertFalse("isLightTheme should return false for holo", - WizardManagerHelper.isLightTheme("holo", false)); - assertTrue("isLightTheme should return default value true", - WizardManagerHelper.isLightTheme("abracadabra", true)); - assertFalse("isLightTheme should return default value false", - WizardManagerHelper.isLightTheme("abracadabra", false)); + 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 @@ -266,6 +255,7 @@ public class WizardManagerHelperTest { .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"); @@ -284,6 +274,8 @@ public class WizardManagerHelperTest { 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 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 f1332c0..ae4f3d1 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/FillContentLayoutTest.java @@ -22,7 +22,6 @@ import static org.robolectric.RuntimeEnvironment.application; import android.view.View; import android.view.View.MeasureSpec; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; import org.junit.Test; @@ -31,7 +30,7 @@ import org.robolectric.Robolectric; import org.robolectric.annotation.Config; @RunWith(SuwLibRobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) +@Config(sdk = {Config.OLDEST_SDK, Config.NEWEST_SDK}) public class FillContentLayoutTest { @Test 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 ffa228d..21822a4 100644 --- a/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/IllustrationVideoViewTest.java @@ -18,6 +18,7 @@ package com.android.setupwizardlib.view; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -31,9 +32,10 @@ import android.os.Build.VERSION_CODES; import android.support.annotation.RawRes; import android.view.Surface; -import com.android.setupwizardlib.BuildConfig; import com.android.setupwizardlib.R; import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; +import com.android.setupwizardlib.shadow.ShadowLog; +import com.android.setupwizardlib.shadow.ShadowLog.TerribleFailure; import com.android.setupwizardlib.view.IllustrationVideoViewTest.ShadowMockMediaPlayer; import com.android.setupwizardlib.view.IllustrationVideoViewTest.ShadowSurface; @@ -48,15 +50,15 @@ import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; -import org.robolectric.internal.Shadow; +import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowMediaPlayer; import org.robolectric.util.ReflectionHelpers; @RunWith(SuwLibRobolectricTestRunner.class) @Config( - constants = BuildConfig.class, sdk = Config.NEWEST_SDK, shadows = { + ShadowLog.class, ShadowMockMediaPlayer.class, ShadowSurface.class }) @@ -78,6 +80,17 @@ public class IllustrationVideoViewTest { } @Test + public void nullMediaPlayer_shouldThrowWtf() { + ShadowMockMediaPlayer.sMediaPlayer = null; + try { + createDefaultView(); + fail("WTF should be thrown for null media player"); + } catch (TerribleFailure e) { + // pass + } + } + + @Test public void testPausedWhenWindowFocusLost() { createDefaultView(); mView.start(); diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java b/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java index 5f3eb9f..f77de68 100644 --- a/library/test/instrumentation/src/com/android/setupwizardlib/test/RichTextViewTest.java +++ b/library/test/robotest/src/com/android/setupwizardlib/view/RichTextViewTest.java @@ -14,39 +14,45 @@ * limitations under the License. */ -package com.android.setupwizardlib.test; +package com.android.setupwizardlib.view; + +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static 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; 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.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 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.RichTextView; +import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; import java.util.Arrays; -@RunWith(AndroidJUnit4.class) -@SmallTest +@RunWith(SuwLibRobolectricTestRunner.class) +@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) public class RichTextViewTest { @Test @@ -55,12 +61,14 @@ public class RichTextViewTest { SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); ssb.setSpan(link, 1, 2, 0 /* flags */); - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); + 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); @@ -77,7 +85,7 @@ public class RichTextViewTest { SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); ssb.setSpan(link, 1, 2, 0 /* flags */); - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); + RichTextView textView = new RichTextView(application); textView.setText(ssb); OnLinkClickListener listener = mock(OnLinkClickListener.class); @@ -99,7 +107,7 @@ public class RichTextViewTest { SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); ssb.setSpan(link, 1, 2, 0 /* flags */); - TestContext context = spy(new TestContext(InstrumentationRegistry.getTargetContext())); + TestContext context = spy(new TestContext(application)); RichTextView textView = new RichTextView(context); textView.setText(ssb); @@ -111,12 +119,50 @@ public class RichTextViewTest { } @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(InstrumentationRegistry.getContext()); + RichTextView textView = new RichTextView(application); textView.setText(ssb); final CharSequence text = textView.getText(); @@ -137,7 +183,7 @@ public class RichTextViewTest { SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); spannableStringBuilder.setSpan(testLink, 0, 3, 0); - RichTextView view = new RichTextView(InstrumentationRegistry.getContext()); + RichTextView view = new RichTextView(application); view.setText(spannableStringBuilder); assertTrue("TextView should be focusable since it contains spans", view.isFocusable()); @@ -147,7 +193,7 @@ public class RichTextViewTest { @SuppressLint("SetTextI18n") // It's OK. This is just a test. @Test public void testTextContainingNoLinksAreNotFocusable() { - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); + 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", @@ -160,16 +206,23 @@ public class RichTextViewTest { @SuppressLint("SetTextI18n") // It's OK. This is just a test. @Test public void testRichTextViewFocusChangesWithTextChange() { - RichTextView textView = new RichTextView(InstrumentationRegistry.getContext()); + 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 { diff --git a/navigationbar/res/values-en-rCA/strings.xml b/navigationbar/res/values-en-rCA/strings.xml new file mode 100644 index 0000000..b06dc86 --- /dev/null +++ b/navigationbar/res/values-en-rCA/strings.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Next"</string> + <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Back"</string> +</resources> diff --git a/navigationbar/res/values-en-rXC/strings.xml b/navigationbar/res/values-en-rXC/strings.xml new file mode 100644 index 0000000..5c7c658 --- /dev/null +++ b/navigationbar/res/values-en-rXC/strings.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="setup_wizard_next_button_label" msgid="6681282266022780599">"Next"</string> + <string name="setup_wizard_back_button_label" msgid="2863826823307023546">"Back"</string> +</resources> diff --git a/tools/build_for_build_server.sh b/tools/build_for_build_server.sh index a90ae67..7a8c942 100755 --- a/tools/build_for_build_server.sh +++ b/tools/build_for_build_server.sh @@ -6,4 +6,4 @@ export TARGET_BUILD_DENSITY="alldpi" export TARGET_BUILD_TYPE="release" export TARGET_BUILD_APPS="setup-wizard-lib" -./gradlew buildProjectFull test +./gradlew buildProjectFull test coverage diff --git a/tools/gradle/android.properties b/tools/gradle/android.properties index 75de660..f62de66 100644 --- a/tools/gradle/android.properties +++ b/tools/gradle/android.properties @@ -1,6 +1,6 @@ // Set the default SDK and build tools version for all apps -compileSdkVersion 25 -buildToolsVersion = '25.0.0' +compileSdkVersion 28 +buildToolsVersion = '28.0.0' // enable Java7 compileOptions.sourceCompatibility JavaVersion.VERSION_1_7 diff --git a/tools/gradle/dist-unit-tests.gradle b/tools/gradle/dist-unit-tests.gradle index faae260..aaecd35 100644 --- a/tools/gradle/dist-unit-tests.gradle +++ b/tools/gradle/dist-unit-tests.gradle @@ -10,6 +10,7 @@ */ apply plugin: 'dist' +apply plugin: 'jacoco' // If unit tests are run as part of the build, dist the test XML reports to host-test-reports/*.zip android.unitTestVariants.all { variant -> @@ -28,11 +29,80 @@ android.unitTestVariants.all { variant -> archiveName = task.name + 'Result.zip' destinationDir = junitReport.destination.parentFile } - zipTask.mustRunAfter task + task.finalizedBy zipTask // Copy the test reports to dist/host-test-reports // The file path and format should match GradleHostBasedTest class in TradeFed. - tasks.dist.dependsOn zipTask + tasks.dist.mustRunAfter zipTask dist.file zipTask.archivePath.path, "host-test-reports/${zipTask.archiveName}" } } + +/* + * The section below adds code coverage to all the unitTest targets. By default, the jacoco plugin + * only adds to the 'java' plugin and not the Android ones. + * + * For each unitTest task "fooUnitTest", a new target "fooUnitTestCoverage" will be generated for + * to generate the jacoco report. + */ +android.testOptions.unitTests.all { + // Fix robolectric tests reporting 0 coverage on newer versions of the plugin. + jacoco { + includeNoLocationClasses = true + } +} + +// Define the main coverage task if it does not exist. This task generates coverage report for all +// unit tests. +def coverageTask = tasks.findByName('coverage') ?: tasks.create('coverage') { + group = "Reporting" + description = "Generate Jacoco coverage reports" +} + +android.unitTestVariants.all { variant -> + def testTaskName = "test${variant.name.capitalize()}" + def testTask = tasks.findByName(testTaskName) + + // Create coverage task of form 'testFlavorCoverageUnitTestCoverage' depending on + // 'testFlavorCoverageUnitTest' + def jacocoTask = tasks.create("${testTaskName}Coverage", JacocoReport) { + group = "Reporting" + description = "Generate a Jacoco coverage report for robolectric tests on ${variant.name}." + + classDirectories = fileTree( + dir: "${project.buildDir}/intermediates/classes/" + + "${variant.productFlavors[0].name}/${variant.buildType.name}", + excludes: ['**/R.class', + '**/R$*.class', + '**/BuildConfig.*', + '**/Manifest*.*'] + ) + + sourceDirectories = files(variant.testedVariant.sourceSets.collect { it.java.srcDirs }) + executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec") + + reports { + xml.enabled = true + html.enabled = true + } + } + jacocoTask.dependsOn testTask + + // Create a zip file of the HTML coverage reports + def zipTask = tasks.create("zipResultsOf${jacocoTask.name.capitalize()}", Zip) { + from jacocoTask.reports.html.destination + archiveName = "${testTaskName}HtmlCoverage.zip" + destinationDir = jacocoTask.reports.html.destination.parentFile + } + jacocoTask.finalizedBy zipTask + + // Copy the coverage reports to dist/host-test-coverage + // The file path and format should match JacocoLogForwarder class in TradeFed. + tasks.dist.mustRunAfter jacocoTask + dist.file jacocoTask.reports.xml.destination.path, "host-test-coverage/${jacocoTask.name}.xml" + + tasks.dist.mustRunAfter zipTask + dist.file zipTask.archivePath.path, "host-test-coverage/${zipTask.archiveName}" + + coverageTask.dependsOn(jacocoTask) +} diff --git a/tools/root.mk b/tools/root.mk new file mode 100644 index 0000000..11597ea --- /dev/null +++ b/tools/root.mk @@ -0,0 +1,2 @@ +default: + TARGET_BUILD_APPS=setup-wizard-lib ./gradlew |