/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.setupwizardlib.util; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; import android.text.Layout; import android.text.Spanned; import android.text.style.ClickableSpan; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityNodeProviderCompat; import androidx.customview.widget.ExploreByTouchHelper; import java.util.List; /** * An accessibility delegate that allows {@link android.text.style.ClickableSpan} to be focused and * clicked by accessibility services. * *
Note: This class is a no-op on Android O or above since there is native * support for ClickableSpan accessibility. * *
Sample usage: *
* LinkAccessibilityHelper mAccessibilityHelper; * * private void init() { * mAccessibilityHelper = new LinkAccessibilityHelper(myTextView); * ViewCompat.setAccessibilityDelegate(myTextView, mLinkHelper); * } * * {@literal @}Override * protected boolean dispatchHoverEvent({@literal @}NonNull MotionEvent event) { * if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) { * return true; * } * return super.dispatchHoverEvent(event); * } ** * @see com.android.setupwizardlib.view.RichTextView * @see androidx.customview.widget.ExploreByTouchHelper */ public class LinkAccessibilityHelper extends AccessibilityDelegateCompat { private static final String TAG = "LinkAccessibilityHelper"; private final AccessibilityDelegateCompat mDelegate; public LinkAccessibilityHelper(TextView view) { 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)); } @VisibleForTesting LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) { mDelegate = delegate; } @Override public void sendAccessibilityEvent(View host, int eventType) { mDelegate.sendAccessibilityEvent(host, eventType); } @Override public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { mDelegate.sendAccessibilityEventUnchecked(host, event); } @Override public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { return mDelegate.dispatchPopulateAccessibilityEvent(host, event); } @Override public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { mDelegate.onPopulateAccessibilityEvent(host, event); } @Override public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { mDelegate.onInitializeAccessibilityEvent(host, event); } @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { mDelegate.onInitializeAccessibilityNodeInfo(host, info); } @Override public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) { return mDelegate.onRequestSendAccessibilityEvent(host, child, event); } @Override public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) { return mDelegate.getAccessibilityNodeProvider(host); } @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { return mDelegate.performAccessibilityAction(host, action, args); } /** * 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 mDelegate instanceof ExploreByTouchHelper && ((ExploreByTouchHelper) mDelegate).dispatchHoverEvent(event); } @VisibleForTesting static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper { private final Rect mTempRect = new Rect(); private final TextView mView; PreOLinkAccessibilityHelper(TextView view) { super(view); mView = view; } @Override 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; } @Override protected void getVisibleVirtualViews(List