Home | History | Annotate | Download | only in util
      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