AccessibilityClickableSpan.java revision 193520e3dff5248ddcf8435203bf99d2ba667219
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 */ 16package android.text.style; 17 18import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN; 19 20import android.os.Bundle; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.text.ParcelableSpan; 24import android.text.Spanned; 25import android.text.TextUtils; 26import android.view.View; 27import android.view.accessibility.AccessibilityNodeInfo; 28 29import com.android.internal.R; 30 31import java.lang.ref.WeakReference; 32 33 34/** 35 * {@link ClickableSpan} cannot be parceled, but accessibility services need to be able to cause 36 * their callback handlers to be called. This class serves as a parcelable placeholder for the 37 * real spans. 38 * 39 * This span is also passed back to an app's process when an accessibility service tries to click 40 * it. It contains enough information to track down the original clickable span so it can be 41 * called. 42 * 43 * @hide 44 */ 45public class AccessibilityClickableSpan extends ClickableSpan 46 implements ParcelableSpan { 47 // The id of the span this one replaces 48 private final int mOriginalClickableSpanId; 49 50 // Only retain a weak reference to the node to avoid referencing cycles that could create memory 51 // leaks. 52 private WeakReference<AccessibilityNodeInfo> mAccessibilityNodeInfoRef; 53 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 * Set the accessibilityNodeInfo that this placeholder belongs to. This node is not 114 * included in the parceling logic, and must be set to allow the onClick handler to function. 115 * 116 * @param accessibilityNodeInfo The info this span is part of 117 */ 118 public void setAccessibilityNodeInfo(AccessibilityNodeInfo accessibilityNodeInfo) { 119 mAccessibilityNodeInfoRef = new WeakReference<>(accessibilityNodeInfo); 120 } 121 122 /** 123 * Perform the click from an accessibility service. Will not work unless 124 * setAccessibilityNodeInfo is called with a properly initialized node. 125 * 126 * @param unused This argument is required by the superclass but is unused. The real view will 127 * be determined by the AccessibilityNodeInfo. 128 */ 129 @Override 130 public void onClick(View unused) { 131 if (mAccessibilityNodeInfoRef == null) { 132 return; 133 } 134 AccessibilityNodeInfo info = mAccessibilityNodeInfoRef.get(); 135 if (info == null) { 136 return; 137 } 138 Bundle arguments = new Bundle(); 139 arguments.putParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN, this); 140 141 info.performAction(R.id.accessibilityActionClickOnClickableSpan, arguments); 142 } 143 144 public static final Parcelable.Creator<AccessibilityClickableSpan> CREATOR = 145 new Parcelable.Creator<AccessibilityClickableSpan>() { 146 @Override 147 public AccessibilityClickableSpan createFromParcel(Parcel parcel) { 148 return new AccessibilityClickableSpan(parcel); 149 } 150 151 @Override 152 public AccessibilityClickableSpan[] newArray(int size) { 153 return new AccessibilityClickableSpan[size]; 154 } 155 }; 156} 157