Add test for LinkAccessibilityHelper

- For the AccessibilityDelegateCompat methods, add tests that the
  delegation is done correctly.
- Refactored LinkAccessibilityHelper to make the dependency direction
  clearer.

Test: ./gradlew connectedAndroidTest test
Change-Id: I6132c0820ee6de1b9cc71a2838bdf05a34d7d2af
This commit is contained in:
Maurice Lam 2017-05-15 19:09:24 -07:00
parent 1ae463059c
commit b72f3fb459
4 changed files with 269 additions and 248 deletions

View file

@ -19,6 +19,8 @@ package com.android.setupwizardlib.util;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.Bundle; 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.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; 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 * An accessibility delegate that allows {@link android.text.style.ClickableSpan} to be focused and
* clicked by accessibility services. * 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> * <pre>
* LinkAccessibilityHelper mAccessibilityHelper; * LinkAccessibilityHelper mAccessibilityHelper;
* *
@ -68,294 +69,255 @@ public class LinkAccessibilityHelper extends AccessibilityDelegateCompat {
private static final String TAG = "LinkAccessibilityHelper"; private static final String TAG = "LinkAccessibilityHelper";
private final TextView mView; private final AccessibilityDelegateCompat mDelegate;
private final Rect mTempRect = new Rect();
private final ExploreByTouchHelper mExploreByTouchHelper;
public LinkAccessibilityHelper(TextView view) { public LinkAccessibilityHelper(TextView view) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { this(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
// Pre-O, we essentially extend ExploreByTouchHelper to expose a virtual view hierarchy // Platform support was added in O. This helper will be no-op
mExploreByTouchHelper = new ExploreByTouchHelper(view) { ? new AccessibilityDelegateCompat()
@Override // Pre-O, we extend ExploreByTouchHelper to expose a virtual view hierarchy
protected int getVirtualViewAt(float x, float y) { : new PreOLinkAccessibilityHelper(view));
return LinkAccessibilityHelper.this.getVirtualViewAt(x, y); }
}
@Override @VisibleForTesting
protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) {
LinkAccessibilityHelper.this.getVisibleVirtualViews(virtualViewIds); mDelegate = delegate;
}
@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);
}
@Override
protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
Bundle arguments) {
return LinkAccessibilityHelper.this
.onPerformActionForVirtualView(virtualViewId, action, arguments);
}
};
} else {
mExploreByTouchHelper = null;
}
mView = view;
} }
@Override @Override
public void sendAccessibilityEvent(View host, int eventType) { public void sendAccessibilityEvent(View host, int eventType) {
if (mExploreByTouchHelper != null) { mDelegate.sendAccessibilityEvent(host, eventType);
mExploreByTouchHelper.sendAccessibilityEvent(host, eventType);
} else {
super.sendAccessibilityEvent(host, eventType);
}
} }
@Override @Override
public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
if (mExploreByTouchHelper != null) { mDelegate.sendAccessibilityEventUnchecked(host, event);
mExploreByTouchHelper.sendAccessibilityEventUnchecked(host, event);
} else {
super.sendAccessibilityEventUnchecked(host, event);
}
} }
@Override @Override
public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
return (mExploreByTouchHelper != null) return mDelegate.dispatchPopulateAccessibilityEvent(host, event);
? mExploreByTouchHelper.dispatchPopulateAccessibilityEvent(host, event)
: super.dispatchPopulateAccessibilityEvent(host, event);
} }
@Override @Override
public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
if (mExploreByTouchHelper != null) { mDelegate.onPopulateAccessibilityEvent(host, event);
mExploreByTouchHelper.onPopulateAccessibilityEvent(host, event);
} else {
super.onPopulateAccessibilityEvent(host, event);
}
} }
@Override @Override
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
if (mExploreByTouchHelper != null) { mDelegate.onInitializeAccessibilityEvent(host, event);
mExploreByTouchHelper.onInitializeAccessibilityEvent(host, event);
} else {
super.onInitializeAccessibilityEvent(host, event);
}
} }
@Override @Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
if (mExploreByTouchHelper != null) { mDelegate.onInitializeAccessibilityNodeInfo(host, info);
mExploreByTouchHelper.onInitializeAccessibilityNodeInfo(host, info);
} else {
super.onInitializeAccessibilityNodeInfo(host, info);
}
} }
@Override @Override
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
AccessibilityEvent event) { AccessibilityEvent event) {
return (mExploreByTouchHelper != null) return mDelegate.onRequestSendAccessibilityEvent(host, child, event);
? mExploreByTouchHelper.onRequestSendAccessibilityEvent(host, child, event)
: super.onRequestSendAccessibilityEvent(host, child, event);
} }
@Override @Override
public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) { public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
return (mExploreByTouchHelper != null) return mDelegate.getAccessibilityNodeProvider(host);
? mExploreByTouchHelper.getAccessibilityNodeProvider(host)
: super.getAccessibilityNodeProvider(host);
} }
@Override @Override
public boolean performAccessibilityAction(View host, int action, Bundle args) { public boolean performAccessibilityAction(View host, int action, Bundle args) {
return (mExploreByTouchHelper != null) return mDelegate.performAccessibilityAction(host, action, args);
? mExploreByTouchHelper.performAccessibilityAction(host, action, args)
: super.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) { public final boolean dispatchHoverEvent(MotionEvent event) {
return (mExploreByTouchHelper != null) ? mExploreByTouchHelper.dispatchHoverEvent(event) return mDelegate instanceof ExploreByTouchHelper
: false; && ((ExploreByTouchHelper) mDelegate).dispatchHoverEvent(event);
} }
protected int getVirtualViewAt(float x, float y) { @VisibleForTesting
final CharSequence text = mView.getText(); static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper {
if (text instanceof Spanned) {
final Spanned spannedText = (Spanned) text; private final Rect mTempRect = new Rect();
final int offset = getOffsetForPosition(mView, x, y); private final TextView mView;
ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class);
if (linkSpans.length == 1) { PreOLinkAccessibilityHelper(TextView view) {
ClickableSpan linkSpan = linkSpans[0]; super(view);
return spannedText.getSpanStart(linkSpan); mView = view;
}
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 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));
}
} }
} }
return ExploreByTouchHelper.INVALID_ID;
}
protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
final CharSequence text = mView.getText(); final ClickableSpan span = getSpanForOffset(virtualViewId);
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 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());
}
}
protected void onPopulateNodeForVirtualView(int virtualViewId,
AccessibilityNodeInfoCompat info) {
final ClickableSpan span = getSpanForOffset(virtualViewId);
if (span != null) {
info.setContentDescription(getTextForSpan(span));
} else {
Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
info.setContentDescription(mView.getText());
}
info.setFocusable(true);
info.setClickable(true);
getBoundsForSpan(span, mTempRect);
if (mTempRect.isEmpty()) {
Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
mTempRect.set(0, 0, 1, 1);
}
info.setBoundsInParent(mTempRect);
info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
}
protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
Bundle arguments) {
if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
ClickableSpan span = getSpanForOffset(virtualViewId);
if (span != null) { if (span != null) {
span.onClick(mView); event.setContentDescription(getTextForSpan(span));
return true;
} else { } else {
Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId); Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
event.setContentDescription(mView.getText());
} }
} }
return false;
}
private ClickableSpan getSpanForOffset(int offset) { protected void onPopulateNodeForVirtualView(
CharSequence text = mView.getText(); int virtualViewId,
if (text instanceof Spanned) { AccessibilityNodeInfoCompat info) {
Spanned spannedText = (Spanned) text; final ClickableSpan span = getSpanForOffset(virtualViewId);
ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class); if (span != null) {
if (spans.length == 1) { info.setContentDescription(getTextForSpan(span));
return spans[0]; } 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 null;
}
private CharSequence getTextForSpan(ClickableSpan span) { protected boolean onPerformActionForVirtualView(
CharSequence text = mView.getText(); int virtualViewId,
if (text instanceof Spanned) { int action,
Spanned spannedText = (Spanned) text; Bundle arguments) {
return spannedText.subSequence(spannedText.getSpanStart(span), if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
spannedText.getSpanEnd(span)); ClickableSpan span = getSpanForOffset(virtualViewId);
} if (span != null) {
return text; span.onClick(mView);
} return true;
// 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 { } else {
// If the span wraps across multiple lines, only use the first line (as returned Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
// by layout.getLineBounds above), and adjust the "start" of outrect to where
// the span starts, leaving the "end" of outrect at the end of the line.
// ("start" being left for LTR, and right for RTL)
if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
outRect.right = (int) xStart;
} else {
outRect.left = (int) xStart;
}
} }
// Offset for padding
outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
} }
return false;
} }
return outRect;
}
// Compat implementation of TextView#getOffsetForPosition(). private ClickableSpan getSpanForOffset(int offset) {
CharSequence text = mView.getText();
if (text instanceof Spanned) {
Spanned spannedText = (Spanned) text;
ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
if (spans.length == 1) {
return spans[0];
}
}
return null;
}
private static int getOffsetForPosition(TextView view, float x, float y) { private CharSequence getTextForSpan(ClickableSpan span) {
if (view.getLayout() == null) return -1; CharSequence text = mView.getText();
final int line = getLineAtCoordinate(view, y); if (text instanceof Spanned) {
return getOffsetAtCoordinate(view, line, x); Spanned spannedText = (Spanned) text;
} return spannedText.subSequence(
spannedText.getSpanStart(span),
spannedText.getSpanEnd(span));
}
return text;
}
private static float convertToLocalHorizontalCoordinate(TextView view, float x) { // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for
x -= view.getTotalPaddingLeft(); // the section on the first line.
// Clamp the position to inside of the view. private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
x = Math.max(0.0f, x); CharSequence text = mView.getText();
x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x); outRect.setEmpty();
x += view.getScrollX(); if (text instanceof Spanned) {
return x; final Layout layout = mView.getLayout();
} if (layout != null) {
Spanned spannedText = (Spanned) text;
final int spanStart = spannedText.getSpanStart(span);
final int spanEnd = spannedText.getSpanEnd(span);
final float xStart = layout.getPrimaryHorizontal(spanStart);
final float xEnd = layout.getPrimaryHorizontal(spanEnd);
final int lineStart = layout.getLineForOffset(spanStart);
final int lineEnd = layout.getLineForOffset(spanEnd);
layout.getLineBounds(lineStart, outRect);
if (lineEnd == lineStart) {
// If the span is on a single line, adjust both the left and right bounds
// so outrect is exactly bounding the span.
outRect.left = (int) Math.min(xStart, xEnd);
outRect.right = (int) Math.max(xStart, xEnd);
} else {
// If the span wraps across multiple lines, only use the first line (as
// returned by layout.getLineBounds above), and adjust the "start" of
// outrect to where the span starts, leaving the "end" of outrect at the end
// of the line. ("start" being left for LTR, and right for RTL)
if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
outRect.right = (int) xStart;
} else {
outRect.left = (int) xStart;
}
}
private static int getLineAtCoordinate(TextView view, float y) { // Offset for padding
y -= view.getTotalPaddingTop(); outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
// Clamp the position to inside of the view. }
y = Math.max(0.0f, y); }
y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y); return outRect;
y += view.getScrollY(); }
return view.getLayout().getLineForVertical((int) y);
}
private static int getOffsetAtCoordinate(TextView view, int line, float x) { // Compat implementation of TextView#getOffsetForPosition().
x = convertToLocalHorizontalCoordinate(view, x);
return view.getLayout().getOffsetForHorizontal(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 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);
}
} }
} }

View file

@ -14,29 +14,35 @@
* limitations under the License. * 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.assertEquals;
import static org.junit.Assert.assertTrue; 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.graphics.Rect;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import android.support.v4.text.BidiFormatter; import android.support.v4.text.BidiFormatter;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import android.support.v4.widget.ExploreByTouchHelper; import android.support.v4.widget.ExploreByTouchHelper;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import com.android.setupwizardlib.span.LinkSpan; 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -52,13 +58,12 @@ public class LinkAccessibilityHelperTest {
private static final LinkSpan LINK_SPAN = new LinkSpan("foobar"); private static final LinkSpan LINK_SPAN = new LinkSpan("foobar");
private TextView mTextView; private TextView mTextView;
private TestLinkAccessibilityHelper mHelper; private TestPreOLinkAccessibilityHelper mHelper;
private DisplayMetrics mDisplayMetrics; private DisplayMetrics mDisplayMetrics;
@Test @Test
public void testGetVirtualViewAt() { public void testGetVirtualViewAt() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView(); initTextView();
final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10)); final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10));
assertEquals("Virtual view ID should be 1", 1, virtualViewId); assertEquals("Virtual view ID should be 1", 1, virtualViewId);
@ -66,7 +71,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testGetVirtualViewAtHost() { public void testGetVirtualViewAtHost() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView(); initTextView();
final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100)); final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100));
assertEquals("Virtual view ID should be INVALID_ID", assertEquals("Virtual view ID should be INVALID_ID",
@ -75,7 +79,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testGetVisibleVirtualViews() { public void testGetVisibleVirtualViews() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView(); initTextView();
List<Integer> virtualViewIds = new ArrayList<>(); List<Integer> virtualViewIds = new ArrayList<>();
mHelper.getVisibleVirtualViews(virtualViewIds); mHelper.getVisibleVirtualViews(virtualViewIds);
@ -86,7 +89,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testOnPopulateEventForVirtualView() { public void testOnPopulateEventForVirtualView() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView(); initTextView();
AccessibilityEvent event = AccessibilityEvent.obtain(); AccessibilityEvent event = AccessibilityEvent.obtain();
mHelper.onPopulateEventForVirtualView(1, event); mHelper.onPopulateEventForVirtualView(1, event);
@ -100,7 +102,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testOnPopulateEventForVirtualViewHost() { public void testOnPopulateEventForVirtualViewHost() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView(); initTextView();
AccessibilityEvent event = AccessibilityEvent.obtain(); AccessibilityEvent event = AccessibilityEvent.obtain();
mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event); mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event);
@ -113,7 +114,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testOnPopulateNodeForVirtualView() { public void testOnPopulateNodeForVirtualView() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView(); initTextView();
AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
mHelper.onPopulateNodeForVirtualView(1, info); mHelper.onPopulateNodeForVirtualView(1, info);
@ -132,7 +132,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testNullLayout() { public void testNullLayout() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView(); initTextView();
// Setting the padding will cause the layout to be null-ed out. // Setting the padding will cause the layout to be null-ed out.
mTextView.setPadding(1, 1, 1, 1); mTextView.setPadding(1, 1, 1, 1);
@ -150,7 +149,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testRtlLayout() { public void testRtlLayout() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום"); SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום");
ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */);
initTextView(ssb); initTextView(ssb);
@ -170,7 +168,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testMultilineLink() { public void testMultilineLink() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
SpannableStringBuilder ssb = new SpannableStringBuilder( SpannableStringBuilder ssb = new SpannableStringBuilder(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
+ "Praesent accumsan efficitur eros eu porttitor."); + "Praesent accumsan efficitur eros eu porttitor.");
@ -192,7 +189,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testRtlMultilineLink() { public void testRtlMultilineLink() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים "
+ "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד "
+ "דפים המחשב מיזמים ב."; + "דפים המחשב מיזמים ב.";
@ -216,7 +212,6 @@ public class LinkAccessibilityHelperTest {
@Test @Test
public void testBidiMultilineLink() { public void testBidiMultilineLink() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים " String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים "
+ "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד " + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד "
+ "דפים המחשב מיזמים ב."; + "דפים המחשב מיזמים ב.";
@ -243,6 +238,70 @@ public class LinkAccessibilityHelperTest {
info.recycle(); 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() { private void initTextView() {
SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world");
ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */); ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */);
@ -254,7 +313,7 @@ public class LinkAccessibilityHelperTest {
mTextView.setSingleLine(false); mTextView.setSingleLine(false);
mTextView.setText(text); mTextView.setText(text);
mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
mHelper = new TestLinkAccessibilityHelper(mTextView); mHelper = new TestPreOLinkAccessibilityHelper(mTextView);
int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500), int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500),
View.MeasureSpec.EXACTLY); View.MeasureSpec.EXACTLY);
@ -270,9 +329,9 @@ public class LinkAccessibilityHelperTest {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics); 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); super(view);
} }

View file

@ -16,5 +16,5 @@
apply from: 'standalone-rules.gradle' apply from: 'standalone-rules.gradle'
android.compileSdkVersion 25 android.compileSdkVersion 26
android.buildToolsVersion '25.0.0' android.buildToolsVersion '26.0.0'

View file

@ -1,6 +1,6 @@
// Set the default SDK and build tools version for all apps // Set the default SDK and build tools version for all apps
compileSdkVersion 25 compileSdkVersion 26
buildToolsVersion = '25.0.0' buildToolsVersion = '26.0.0'
// enable Java7 // enable Java7
compileOptions.sourceCompatibility JavaVersion.VERSION_1_7 compileOptions.sourceCompatibility JavaVersion.VERSION_1_7