• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 @android.ravenwood.annotation.RavenwoodKeepWholeClass
47 public class AccessibilityClickableSpan extends ClickableSpan
48         implements ParcelableSpan {
49     // The id of the span this one replaces
50     private final int mOriginalClickableSpanId;
51 
52     private int mWindowId = UNDEFINED_WINDOW_ID;
53     private long mSourceNodeId = UNDEFINED_NODE_ID;
54     private int mConnectionId = UNDEFINED_CONNECTION_ID;
55 
56     /**
57      * @param originalClickableSpanId The id of the span this one replaces
58      */
AccessibilityClickableSpan(int originalClickableSpanId)59     public AccessibilityClickableSpan(int originalClickableSpanId) {
60         mOriginalClickableSpanId = originalClickableSpanId;
61     }
62 
AccessibilityClickableSpan(Parcel p)63     public AccessibilityClickableSpan(Parcel p) {
64         mOriginalClickableSpanId = p.readInt();
65     }
66 
67     @Override
getSpanTypeId()68     public int getSpanTypeId() {
69         return getSpanTypeIdInternal();
70     }
71 
72     @Override
getSpanTypeIdInternal()73     public int getSpanTypeIdInternal() {
74         return TextUtils.ACCESSIBILITY_CLICKABLE_SPAN;
75     }
76 
77     @Override
describeContents()78     public int describeContents() {
79         return 0;
80     }
81 
82     @Override
writeToParcel(Parcel dest, int flags)83     public void writeToParcel(Parcel dest, int flags) {
84         writeToParcelInternal(dest, flags);
85     }
86 
87     @Override
writeToParcelInternal(Parcel dest, int flags)88     public void writeToParcelInternal(Parcel dest, int flags) {
89         dest.writeInt(mOriginalClickableSpanId);
90     }
91 
92     /**
93      * Find the ClickableSpan that matches the one used to create this object.
94      *
95      * @param text The text that contains the original ClickableSpan.
96      * @return The ClickableSpan that matches this object, or {@code null} if no such object
97      * can be found.
98      */
findClickableSpan(CharSequence text)99     public ClickableSpan findClickableSpan(CharSequence text) {
100         if (!(text instanceof Spanned)) {
101             return null;
102         }
103         Spanned sp = (Spanned) text;
104         ClickableSpan[] os = sp.getSpans(0, text.length(), ClickableSpan.class);
105         for (int i = 0; i < os.length; i++) {
106             if (os[i].getId() == mOriginalClickableSpanId) {
107                 return os[i];
108             }
109         }
110         return null;
111     }
112 
113     /**
114      * Configure this object to perform clicks on the view that contains the original span.
115      *
116      * @param accessibilityNodeInfo The info corresponding to the view containing the original
117      *                              span.
118      */
copyConnectionDataFrom(AccessibilityNodeInfo accessibilityNodeInfo)119     public void copyConnectionDataFrom(AccessibilityNodeInfo accessibilityNodeInfo) {
120         mConnectionId = accessibilityNodeInfo.getConnectionId();
121         mWindowId = accessibilityNodeInfo.getWindowId();
122         mSourceNodeId = accessibilityNodeInfo.getSourceNodeId();
123     }
124 
125     /**
126      * Perform the click from an accessibility service. Will not work unless
127      * setAccessibilityNodeInfo is called with a properly initialized node.
128      *
129      * @param unused This argument is required by the superclass but is unused. The real view will
130      * be determined by the AccessibilityNodeInfo.
131      */
132     @Override
onClick(View unused)133     public void onClick(View unused) {
134         Bundle arguments = new Bundle();
135         arguments.putParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN, this);
136 
137         if ((mWindowId == UNDEFINED_WINDOW_ID) || (mSourceNodeId == UNDEFINED_NODE_ID)
138                 || (mConnectionId == UNDEFINED_CONNECTION_ID)) {
139             throw new RuntimeException(
140                     "ClickableSpan for accessibility service not properly initialized");
141         }
142 
143         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
144         client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
145                 R.id.accessibilityActionClickOnClickableSpan, arguments);
146     }
147 
148     public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityClickableSpan> CREATOR =
149             new Parcelable.Creator<AccessibilityClickableSpan>() {
150                 @Override
151                 public AccessibilityClickableSpan createFromParcel(Parcel parcel) {
152                     return new AccessibilityClickableSpan(parcel);
153                 }
154 
155                 @Override
156                 public AccessibilityClickableSpan[] newArray(int size) {
157                     return new AccessibilityClickableSpan[size];
158                 }
159             };
160 
161     /**
162      * @return the ID of the original clickable span that this is applied to.
163      * @hide
164      */
getOriginalClickableSpanId()165     public int getOriginalClickableSpanId() {
166         return mOriginalClickableSpanId;
167     }
168 }
169