From b72f3fb4598d2bd2560cdf5043defc80a0199e2e Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Mon, 15 May 2017 19:09:24 -0700 Subject: [PATCH] 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 --- .../util/LinkAccessibilityHelper.java | 414 ++++++++---------- .../LinkAccessibilityHelperTest.java | 95 +++- library/standalone.gradle | 4 +- tools/gradle/android.properties | 4 +- 4 files changed, 269 insertions(+), 248 deletions(-) rename library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/{test => util}/LinkAccessibilityHelperTest.java (75%) diff --git a/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/library/gingerbread/src/com/android/setupwizardlib/util/LinkAccessibilityHelper.java index 1e663d6..1fb3a37 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. - *

- * Note: From Android O on, there is native support for ClickableSpan - * accessibility, so this class is not needed (and indeed has no effect.) - *

* - *

Sample usage: + *

Note: This class is a no-op on Android O or above since there is native + * support for ClickableSpan accessibility. + * + *

Sample usage: *

  * LinkAccessibilityHelper mAccessibilityHelper;
  *
@@ -68,294 +69,255 @@ 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);
-                }
+        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 void getVisibleVirtualViews(List 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);
-
-                }
-
-                @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;
+        }
+
+        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 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 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 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);
+        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+            final ClickableSpan span = getSpanForOffset(virtualViewId);
             if (span != null) {
-                span.onClick(mView);
-                return true;
+                event.setContentDescription(getTextForSpan(span));
             } else {
                 Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+                event.setContentDescription(mView.getText());
             }
         }
-        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];
+        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);
         }
-        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));
-        }
-        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);
+        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 {
-                    // 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;
-                    }
+                    Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
                 }
-
-                // 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) {
-        if (view.getLayout() == null) return -1;
-        final int line = getLineAtCoordinate(view, y);
-        return getOffsetAtCoordinate(view, line, x);
-    }
+        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));
+            }
+            return text;
+        }
 
-    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;
-    }
+        // 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 {
+                        // 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) {
-        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);
-    }
+                    // Offset for padding
+                    outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
+                }
+            }
+            return outRect;
+        }
 
-    private static int getOffsetAtCoordinate(TextView view, int line, float x) {
-        x = convertToLocalHorizontalCoordinate(view, x);
-        return view.getLayout().getOffsetForHorizontal(line, x);
+        // 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 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);
+        }
     }
 }
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
similarity index 75%
rename from library/gingerbread/test/instrumentation/src/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java
rename to 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 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/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/tools/gradle/android.properties b/tools/gradle/android.properties
index 75de660..b4a1e0c 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 26
+buildToolsVersion = '26.0.0'
 
 // enable Java7
 compileOptions.sourceCompatibility JavaVersion.VERSION_1_7