/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.setupwizardlib.util; import 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.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.PreOLinkAccessibilityHelper; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Collections; import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class LinkAccessibilityHelperTest { private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); private TextView mTextView; private TestPreOLinkAccessibilityHelper mHelper; private DisplayMetrics mDisplayMetrics; @Test public void testGetVirtualViewAt() { initTextView(); final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10)); assertEquals("Virtual view ID should be 1", 1, virtualViewId); } @Test public void testGetVirtualViewAtHost() { initTextView(); final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100)); assertEquals("Virtual view ID should be INVALID_ID", ExploreByTouchHelper.INVALID_ID, virtualViewId); } @Test public void testGetVisibleVirtualViews() { initTextView(); List virtualViewIds = new ArrayList<>(); mHelper.getVisibleVirtualViews(virtualViewIds); assertEquals("VisibleVirtualViews should be [1]", Collections.singletonList(1), virtualViewIds); } @Test public void testOnPopulateEventForVirtualView() { initTextView(); AccessibilityEvent event = AccessibilityEvent.obtain(); mHelper.onPopulateEventForVirtualView(1, event); // LinkSpan is set on substring(1, 2) of "Hello world" --> "e" assertEquals("LinkSpan description should be \"e\"", "e", event.getContentDescription().toString()); event.recycle(); } @Test public void testOnPopulateEventForVirtualViewHost() { initTextView(); AccessibilityEvent event = AccessibilityEvent.obtain(); mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event); assertEquals("Host view description should be \"Hello world\"", "Hello world", event.getContentDescription().toString()); event.recycle(); } @Test public void testOnPopulateNodeForVirtualView() { initTextView(); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mHelper.onPopulateNodeForVirtualView(1, info); assertEquals("LinkSpan description should be \"e\"", "e", info.getContentDescription().toString()); assertTrue("LinkSpan should be focusable", info.isFocusable()); assertTrue("LinkSpan should be clickable", info.isClickable()); Rect bounds = new Rect(); info.getBoundsInParent(bounds); assertEquals("LinkSpan bounds should be (10.5dp, 0dp, 18.5dp, 20.5dp)", new Rect(dp2Px(10.5f), dp2Px(0f), dp2Px(18.5f), dp2Px(20.5f)), bounds); info.recycle(); } @Test public void testNullLayout() { initTextView(); // Setting the padding will cause the layout to be null-ed out. mTextView.setPadding(1, 1, 1, 1); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mHelper.onPopulateNodeForVirtualView(0, info); Rect bounds = new Rect(); info.getBoundsInParent(bounds); assertEquals("LinkSpan bounds should be (0, 0, 1, 1)", new Rect(0, 0, 1, 1), bounds); info.recycle(); } @Test public void testRtlLayout() { SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום"); ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); initTextView(ssb); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mHelper.onPopulateNodeForVirtualView(1, info); assertEquals("LinkSpan description should be \"כ\"", "כ", info.getContentDescription().toString()); Rect bounds = new Rect(); info.getBoundsInParent(bounds); assertEquals("LinkSpan bounds should be (481.5dp, 0dp, 489.5dp, 20.5dp)", new Rect(dp2Px(481.5f), dp2Px(0f), dp2Px(489.5f), dp2Px(20.5f)), bounds); info.recycle(); } @Test public void testMultilineLink() { SpannableStringBuilder ssb = new SpannableStringBuilder( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Praesent accumsan efficitur eros eu porttitor."); ssb.setSpan(LINK_SPAN, 51, 74, 0 /* flags */); initTextView(ssb); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mHelper.onPopulateNodeForVirtualView(51, info); assertEquals("LinkSpan description should match the span", "elit. Praesent accumsan", info.getContentDescription().toString()); Rect bounds = new Rect(); info.getBoundsInParent(bounds); assertEquals("LinkSpan bounds should match first line of the span", new Rect(dp2Px(343f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), bounds); info.recycle(); } @Test public void testRtlMultilineLink() { String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + "דפים המחשב מיזמים ב."; SpannableStringBuilder ssb = new SpannableStringBuilder(iwLoremIpsum); ssb.setSpan(LINK_SPAN, 50, 100, 0 /* flags */); initTextView(ssb); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mHelper.onPopulateNodeForVirtualView(50, info); assertEquals("LinkSpan description should match the span", iwLoremIpsum.substring(50, 100), info.getContentDescription().toString()); Rect bounds = new Rect(); info.getBoundsInParent(bounds); assertEquals("LinkSpan bounds should match the first line of the span", new Rect(dp2Px(0f), dp2Px(0f), dp2Px(150f), dp2Px(19.5f)), bounds); info.recycle(); } @Test public void testBidiMultilineLink() { String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + "דפים המחשב מיזמים ב."; BidiFormatter formatter = BidiFormatter.getInstance(false /* rtlContext */); SpannableStringBuilder ssb = new SpannableStringBuilder(); ssb.append("hello ").append(formatter.unicodeWrap(iwLoremIpsum)).append(" world"); ssb.setSpan(LINK_SPAN, "hello ".length() + 2, // Add two for the characters added by BidiFormatter "hello ".length() + 2 + iwLoremIpsum.length(), 0 /* flags */); initTextView(ssb); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mHelper.onPopulateNodeForVirtualView("hello ".length() + 2, info); assertEquals("LinkSpan description should match the span", iwLoremIpsum, info.getContentDescription().toString()); Rect bounds = new Rect(); info.getBoundsInParent(bounds); assertEquals("LinkSpan bounds should match the first line of the span", new Rect(dp2Px(491.5f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), bounds); info.recycle(); } @Test public void testMethodDelegation() { initTextView(); 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 */); initTextView(ssb); } private void initTextView(CharSequence text) { mTextView = new TextView(InstrumentationRegistry.getContext()); mTextView.setSingleLine(false); mTextView.setText(text); mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); mHelper = new TestPreOLinkAccessibilityHelper(mTextView); int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500), View.MeasureSpec.EXACTLY); mTextView.measure(measureExactly500dp, measureExactly500dp); mTextView.layout(dp2Px(0), dp2Px(0), dp2Px(500), dp2Px(500)); } private int dp2Px(float dp) { if (mDisplayMetrics == null) { mDisplayMetrics = InstrumentationRegistry.getContext().getResources().getDisplayMetrics(); } return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics); } public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper { TestPreOLinkAccessibilityHelper(TextView view) { super(view); } @Override public int getVirtualViewAt(float x, float y) { return super.getVirtualViewAt(x, y); } @Override public void getVisibleVirtualViews(List virtualViewIds) { super.getVisibleVirtualViews(virtualViewIds); } @Override public void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { super.onPopulateEventForVirtualView(virtualViewId, event); } @Override public void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat info) { super.onPopulateNodeForVirtualView(virtualViewId, info); } @Override public boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) { return super.onPerformActionForVirtualView(virtualViewId, action, arguments); } } }