1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.setupwizardlib.util; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 import static org.mockito.Matchers.eq; 22 import static org.mockito.Matchers.same; 23 import static org.mockito.Mockito.mock; 24 import static org.mockito.Mockito.verify; 25 26 import android.graphics.Rect; 27 import android.os.Bundle; 28 import android.support.test.InstrumentationRegistry; 29 import android.support.test.filters.SmallTest; 30 import android.support.test.runner.AndroidJUnit4; 31 import android.support.v4.text.BidiFormatter; 32 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 33 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; 34 import android.support.v4.widget.ExploreByTouchHelper; 35 import android.text.SpannableStringBuilder; 36 import android.util.DisplayMetrics; 37 import android.util.TypedValue; 38 import android.view.MotionEvent; 39 import android.view.View; 40 import android.view.accessibility.AccessibilityEvent; 41 import android.widget.FrameLayout; 42 import android.widget.TextView; 43 44 import com.android.setupwizardlib.span.LinkSpan; 45 import com.android.setupwizardlib.util.LinkAccessibilityHelper.PreOLinkAccessibilityHelper; 46 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 import java.util.ArrayList; 51 import java.util.Collections; 52 import java.util.List; 53 54 @RunWith(AndroidJUnit4.class) 55 @SmallTest 56 public class LinkAccessibilityHelperTest { 57 58 private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); 59 60 private TextView mTextView; 61 private TestPreOLinkAccessibilityHelper mHelper; 62 63 private DisplayMetrics mDisplayMetrics; 64 65 @Test 66 public void testGetVirtualViewAt() { 67 initTextView(); 68 final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10)); 69 assertEquals("Virtual view ID should be 1", 1, virtualViewId); 70 } 71 72 @Test 73 public void testGetVirtualViewAtHost() { 74 initTextView(); 75 final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100)); 76 assertEquals("Virtual view ID should be INVALID_ID", 77 ExploreByTouchHelper.INVALID_ID, virtualViewId); 78 } 79 80 @Test 81 public void testGetVisibleVirtualViews() { 82 initTextView(); 83 List<Integer> virtualViewIds = new ArrayList<>(); 84 mHelper.getVisibleVirtualViews(virtualViewIds); 85 86 assertEquals("VisibleVirtualViews should be [1]", 87 Collections.singletonList(1), virtualViewIds); 88 } 89 90 @Test 91 public void testOnPopulateEventForVirtualView() { 92 initTextView(); 93 AccessibilityEvent event = AccessibilityEvent.obtain(); 94 mHelper.onPopulateEventForVirtualView(1, event); 95 96 // LinkSpan is set on substring(1, 2) of "Hello world" --> "e" 97 assertEquals("LinkSpan description should be \"e\"", 98 "e", event.getContentDescription().toString()); 99 100 event.recycle(); 101 } 102 103 @Test 104 public void testOnPopulateEventForVirtualViewHost() { 105 initTextView(); 106 AccessibilityEvent event = AccessibilityEvent.obtain(); 107 mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event); 108 109 assertEquals("Host view description should be \"Hello world\"", "Hello world", 110 event.getContentDescription().toString()); 111 112 event.recycle(); 113 } 114 115 @Test 116 public void testOnPopulateNodeForVirtualView() { 117 initTextView(); 118 AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); 119 mHelper.onPopulateNodeForVirtualView(1, info); 120 121 assertEquals("LinkSpan description should be \"e\"", 122 "e", info.getContentDescription().toString()); 123 assertTrue("LinkSpan should be focusable", info.isFocusable()); 124 assertTrue("LinkSpan should be clickable", info.isClickable()); 125 Rect bounds = new Rect(); 126 info.getBoundsInParent(bounds); 127 assertEquals("LinkSpan bounds should be (10.5dp, 0dp, 18.5dp, 20.5dp)", 128 new Rect(dp2Px(10.5f), dp2Px(0f), dp2Px(18.5f), dp2Px(20.5f)), bounds); 129 130 info.recycle(); 131 } 132 133 @Test 134 public void testNullLayout() { 135 initTextView(); 136 // Setting the padding will cause the layout to be null-ed out. 137 mTextView.setPadding(1, 1, 1, 1); 138 139 AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); 140 mHelper.onPopulateNodeForVirtualView(0, info); 141 142 Rect bounds = new Rect(); 143 info.getBoundsInParent(bounds); 144 assertEquals("LinkSpan bounds should be (0, 0, 1, 1)", 145 new Rect(0, 0, 1, 1), bounds); 146 147 info.recycle(); 148 } 149 150 @Test 151 public void testRtlLayout() { 152 SpannableStringBuilder ssb = new SpannableStringBuilder(" "); 153 ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); 154 initTextView(ssb); 155 156 AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); 157 mHelper.onPopulateNodeForVirtualView(1, info); 158 159 assertEquals("LinkSpan description should be \"\"", 160 "", info.getContentDescription().toString()); 161 Rect bounds = new Rect(); 162 info.getBoundsInParent(bounds); 163 assertEquals("LinkSpan bounds should be (481.5dp, 0dp, 489.5dp, 20.5dp)", 164 new Rect(dp2Px(481.5f), dp2Px(0f), dp2Px(489.5f), dp2Px(20.5f)), bounds); 165 166 info.recycle(); 167 } 168 169 @Test 170 public void testMultilineLink() { 171 SpannableStringBuilder ssb = new SpannableStringBuilder( 172 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " 173 + "Praesent accumsan efficitur eros eu porttitor."); 174 ssb.setSpan(LINK_SPAN, 51, 74, 0 /* flags */); 175 initTextView(ssb); 176 177 AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); 178 mHelper.onPopulateNodeForVirtualView(51, info); 179 180 assertEquals("LinkSpan description should match the span", 181 "elit. Praesent accumsan", info.getContentDescription().toString()); 182 Rect bounds = new Rect(); 183 info.getBoundsInParent(bounds); 184 assertEquals("LinkSpan bounds should match first line of the span", 185 new Rect(dp2Px(343f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), bounds); 186 187 info.recycle(); 188 } 189 190 @Test 191 public void testRtlMultilineLink() { 192 String iwLoremIpsum = " . , " 193 + " '. , , " 194 + " ."; 195 SpannableStringBuilder ssb = new SpannableStringBuilder(iwLoremIpsum); 196 ssb.setSpan(LINK_SPAN, 50, 100, 0 /* flags */); 197 initTextView(ssb); 198 199 AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); 200 mHelper.onPopulateNodeForVirtualView(50, info); 201 202 assertEquals("LinkSpan description should match the span", 203 iwLoremIpsum.substring(50, 100), 204 info.getContentDescription().toString()); 205 Rect bounds = new Rect(); 206 info.getBoundsInParent(bounds); 207 assertEquals("LinkSpan bounds should match the first line of the span", 208 new Rect(dp2Px(0f), dp2Px(0f), dp2Px(150f), dp2Px(19.5f)), bounds); 209 210 info.recycle(); 211 } 212 213 @Test 214 public void testBidiMultilineLink() { 215 String iwLoremIpsum = " . , " 216 + " '. , , " 217 + " ."; 218 BidiFormatter formatter = BidiFormatter.getInstance(false /* rtlContext */); 219 SpannableStringBuilder ssb = new SpannableStringBuilder(); 220 ssb.append("hello ").append(formatter.unicodeWrap(iwLoremIpsum)).append(" world"); 221 ssb.setSpan(LINK_SPAN, 222 "hello ".length() + 2, // Add two for the characters added by BidiFormatter 223 "hello ".length() + 2 + iwLoremIpsum.length(), 224 0 /* flags */); 225 initTextView(ssb); 226 227 AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); 228 mHelper.onPopulateNodeForVirtualView("hello ".length() + 2, info); 229 230 assertEquals("LinkSpan description should match the span", 231 iwLoremIpsum, 232 info.getContentDescription().toString()); 233 Rect bounds = new Rect(); 234 info.getBoundsInParent(bounds); 235 assertEquals("LinkSpan bounds should match the first line of the span", 236 new Rect(dp2Px(491.5f), dp2Px(0f), dp2Px(500f), dp2Px(19.5f)), bounds); 237 238 info.recycle(); 239 } 240 241 @Test 242 public void testMethodDelegation() { 243 initTextView(); 244 ExploreByTouchHelper delegate = mock(TestPreOLinkAccessibilityHelper.class); 245 LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate); 246 247 AccessibilityEvent accessibilityEvent = 248 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED); 249 250 helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED); 251 verify(delegate).sendAccessibilityEvent( 252 same(mTextView), 253 eq(AccessibilityEvent.TYPE_VIEW_CLICKED)); 254 255 helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent); 256 verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent)); 257 258 helper.performAccessibilityAction( 259 mTextView, 260 AccessibilityActionCompat.ACTION_CLICK.getId(), 261 Bundle.EMPTY); 262 verify(delegate).performAccessibilityAction( 263 same(mTextView), 264 eq(AccessibilityActionCompat.ACTION_CLICK.getId()), 265 eq(Bundle.EMPTY)); 266 267 helper.dispatchPopulateAccessibilityEvent( 268 mTextView, 269 accessibilityEvent); 270 verify(delegate).dispatchPopulateAccessibilityEvent( 271 same(mTextView), 272 same(accessibilityEvent)); 273 274 MotionEvent motionEvent = MotionEvent.obtain(0, 0, 0, 0, 0, 0); 275 helper.dispatchHoverEvent(motionEvent); 276 verify(delegate).dispatchHoverEvent(eq(motionEvent)); 277 278 helper.getAccessibilityNodeProvider(mTextView); 279 verify(delegate).getAccessibilityNodeProvider(same(mTextView)); 280 281 helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent); 282 verify(delegate).onInitializeAccessibilityEvent( 283 same(mTextView), 284 eq(accessibilityEvent)); 285 286 AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain(); 287 helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo); 288 verify(delegate).onInitializeAccessibilityNodeInfo( 289 same(mTextView), 290 same(accessibilityNodeInfo)); 291 292 helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent); 293 verify(delegate).onPopulateAccessibilityEvent( 294 same(mTextView), 295 same(accessibilityEvent)); 296 297 FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext()); 298 helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent); 299 verify(delegate).onRequestSendAccessibilityEvent( 300 same(parent), 301 same(mTextView), 302 same(accessibilityEvent)); 303 } 304 305 private void initTextView() { 306 SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); 307 ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); 308 initTextView(ssb); 309 } 310 311 private void initTextView(CharSequence text) { 312 mTextView = new TextView(InstrumentationRegistry.getContext()); 313 mTextView.setSingleLine(false); 314 mTextView.setText(text); 315 mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); 316 mHelper = new TestPreOLinkAccessibilityHelper(mTextView); 317 318 int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500), 319 View.MeasureSpec.EXACTLY); 320 mTextView.measure(measureExactly500dp, measureExactly500dp); 321 mTextView.layout(dp2Px(0), dp2Px(0), dp2Px(500), dp2Px(500)); 322 } 323 324 private int dp2Px(float dp) { 325 if (mDisplayMetrics == null) { 326 mDisplayMetrics = 327 InstrumentationRegistry.getContext().getResources().getDisplayMetrics(); 328 } 329 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics); 330 } 331 332 public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper { 333 334 TestPreOLinkAccessibilityHelper(TextView view) { 335 super(view); 336 } 337 338 @Override 339 public int getVirtualViewAt(float x, float y) { 340 return super.getVirtualViewAt(x, y); 341 } 342 343 @Override 344 public void getVisibleVirtualViews(List<Integer> virtualViewIds) { 345 super.getVisibleVirtualViews(virtualViewIds); 346 } 347 348 @Override 349 public void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { 350 super.onPopulateEventForVirtualView(virtualViewId, event); 351 } 352 353 @Override 354 public void onPopulateNodeForVirtualView(int virtualViewId, 355 AccessibilityNodeInfoCompat info) { 356 super.onPopulateNodeForVirtualView(virtualViewId, info); 357 } 358 359 @Override 360 public boolean onPerformActionForVirtualView(int virtualViewId, int action, 361 Bundle arguments) { 362 return super.onPerformActionForVirtualView(virtualViewId, action, arguments); 363 } 364 } 365 } 366