Home | History | Annotate | Download | only in style
      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 package android.text.style;
     17 
     18 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
     19 import static android.view.accessibility.AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID;
     20 import static android.view.accessibility.AccessibilityNodeInfo.UNDEFINED_NODE_ID;
     21 import static android.view.accessibility.AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
     22 
     23 import android.os.Bundle;
     24 import android.os.Parcel;
     25 import android.os.Parcelable;
     26 import android.text.ParcelableSpan;
     27 import android.text.Spanned;
     28 import android.text.TextUtils;
     29 import android.view.View;
     30 import android.view.accessibility.AccessibilityInteractionClient;
     31 import android.view.accessibility.AccessibilityNodeInfo;
     32 
     33 import com.android.internal.R;
     34 
     35 /**
     36  * {@link ClickableSpan} cannot be parceled, but accessibility services need to be able to cause
     37  * their callback handlers to be called. This class serves as a parcelable placeholder for the
     38  * real spans.
     39  *
     40  * This span is also passed back to an app's process when an accessibility service tries to click
     41  * it. It contains enough information to track down the original clickable span so it can be
     42  * called.
     43  *
     44  * @hide
     45  */
     46 public class AccessibilityClickableSpan extends ClickableSpan
     47         implements ParcelableSpan {
     48     // The id of the span this one replaces
     49     private final int mOriginalClickableSpanId;
     50 
     51     private int mWindowId = UNDEFINED_WINDOW_ID;
     52     private long mSourceNodeId = UNDEFINED_NODE_ID;
     53     private int mConnectionId = UNDEFINED_CONNECTION_ID;
     54 
     55     /**
     56      * @param originalClickableSpanId The id of the span this one replaces
     57      */
     58     public AccessibilityClickableSpan(int originalClickableSpanId) {
     59         mOriginalClickableSpanId = originalClickableSpanId;
     60     }
     61 
     62     public AccessibilityClickableSpan(Parcel p) {
     63         mOriginalClickableSpanId = p.readInt();
     64     }
     65 
     66     @Override
     67     public int getSpanTypeId() {
     68         return getSpanTypeIdInternal();
     69     }
     70 
     71     @Override
     72     public int getSpanTypeIdInternal() {
     73         return TextUtils.ACCESSIBILITY_CLICKABLE_SPAN;
     74     }
     75 
     76     @Override
     77     public int describeContents() {
     78         return 0;
     79     }
     80 
     81     @Override
     82     public void writeToParcel(Parcel dest, int flags) {
     83         writeToParcelInternal(dest, flags);
     84     }
     85 
     86     @Override
     87     public void writeToParcelInternal(Parcel dest, int flags) {
     88         dest.writeInt(mOriginalClickableSpanId);
     89     }
     90 
     91     /**
     92      * Find the ClickableSpan that matches the one used to create this object.
     93      *
     94      * @param text The text that contains the original ClickableSpan.
     95      * @return The ClickableSpan that matches this object, or {@code null} if no such object
     96      * can be found.
     97      */
     98     public ClickableSpan findClickableSpan(CharSequence text) {
     99         if (!(text instanceof Spanned)) {
    100             return null;
    101         }
    102         Spanned sp = (Spanned) text;
    103         ClickableSpan[] os = sp.getSpans(0, text.length(), ClickableSpan.class);
    104         for (int i = 0; i < os.length; i++) {
    105             if (os[i].getId() == mOriginalClickableSpanId) {
    106                 return os[i];
    107             }
    108         }
    109         return null;
    110     }
    111 
    112     /**
    113      * Configure this object to perform clicks on the view that contains the original span.
    114      *
    115      * @param accessibilityNodeInfo The info corresponding to the view containing the original
    116      *                              span.
    117      */
    118     public void copyConnectionDataFrom(AccessibilityNodeInfo accessibilityNodeInfo) {
    119         mConnectionId = accessibilityNodeInfo.getConnectionId();
    120         mWindowId = accessibilityNodeInfo.getWindowId();
    121         mSourceNodeId = accessibilityNodeInfo.getSourceNodeId();
    122     }
    123 
    124     /**
    125      * Perform the click from an accessibility service. Will not work unless
    126      * setAccessibilityNodeInfo is called with a properly initialized node.
    127      *
    128      * @param unused This argument is required by the superclass but is unused. The real view will
    129      * be determined by the AccessibilityNodeInfo.
    130      */
    131     @Override
    132     public void onClick(View unused) {
    133         Bundle arguments = new Bundle();
    134         arguments.putParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN, this);
    135 
    136         if ((mWindowId == UNDEFINED_WINDOW_ID) || (mSourceNodeId == UNDEFINED_NODE_ID)
    137                 || (mConnectionId == UNDEFINED_CONNECTION_ID)) {
    138             throw new RuntimeException(
    139                     "ClickableSpan for accessibility service not properly initialized");
    140         }
    141 
    142         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    143         client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
    144                 R.id.accessibilityActionClickOnClickableSpan, arguments);
    145     }
    146 
    147     public static final Parcelable.Creator<AccessibilityClickableSpan> CREATOR =
    148             new Parcelable.Creator<AccessibilityClickableSpan>() {
    149                 @Override
    150                 public AccessibilityClickableSpan createFromParcel(Parcel parcel) {
    151                     return new AccessibilityClickableSpan(parcel);
    152                 }
    153 
    154                 @Override
    155                 public AccessibilityClickableSpan[] newArray(int size) {
    156                     return new AccessibilityClickableSpan[size];
    157                 }
    158             };
    159 }
    160