• 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 
17 package com.google.android.setupdesign.span;
18 
19 import android.content.Context;
20 import android.content.ContextWrapper;
21 import android.os.Build;
22 import androidx.annotation.Nullable;
23 import android.text.Selection;
24 import android.text.Spannable;
25 import android.text.TextPaint;
26 import android.text.style.ClickableSpan;
27 import android.util.Log;
28 import android.view.View;
29 import android.widget.TextView;
30 
31 /**
32  * A clickable span that will listen for click events and send it back to the context. To use this
33  * class, implement {@link OnLinkClickListener} in your TextView, or use {@link
34  * com.google.android.setupdesign.view.RichTextView#setOnClickListener(View.OnClickListener)}.
35  *
36  * <p>Note on accessibility: For TalkBack to be able to traverse and interact with the links, you
37  * should use {@code LinkAccessibilityHelper} in your {@code TextView} subclass. Optionally you can
38  * also use {@code RichTextView}, which includes link support.
39  */
40 public class LinkSpan extends ClickableSpan {
41 
42   /*
43    * Implementation note: When the orientation changes, TextView retains a reference to this span
44    * instead of writing it to a parcel (ClickableSpan is not Parcelable). If this class has any
45    * reference to the containing Activity (i.e. the activity context, or any views in the
46    * activity), it will cause memory leak.
47    */
48 
49   /* static section */
50 
51   private static final String TAG = "LinkSpan";
52 
53   /** @deprecated Use {@link OnLinkClickListener} */
54   @Deprecated
55   public interface OnClickListener {
onClick(LinkSpan span)56     void onClick(LinkSpan span);
57   }
58 
59   /**
60    * Listener that is invoked when a link span is clicked. If the containing view of this span
61    * implements this interface, this will be invoked when the link is clicked.
62    */
63   public interface OnLinkClickListener {
64 
65     /**
66      * Called when a link has been clicked.
67      *
68      * @param span The span that was clicked.
69      * @return True if the click was handled, stopping further propagation of the click event.
70      */
onLinkClick(LinkSpan span)71     boolean onLinkClick(LinkSpan span);
72   }
73 
74   /* non-static section */
75 
76   private final String id;
77 
LinkSpan(String id)78   public LinkSpan(String id) {
79     this.id = id;
80   }
81 
82   @Override
onClick(View view)83   public void onClick(View view) {
84     if (dispatchClick(view)) {
85       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
86         // Prevent the touch event from bubbling up to the parent views.
87         view.cancelPendingInputEvents();
88       }
89     } else {
90       Log.w(TAG, "Dropping click event. No listener attached.");
91     }
92     if (view instanceof TextView) {
93       // Remove the highlight effect when the click happens by clearing the selection
94       CharSequence text = ((TextView) view).getText();
95       if (text instanceof Spannable) {
96         Selection.setSelection((Spannable) text, 0);
97       }
98     }
99   }
100 
dispatchClick(View view)101   private boolean dispatchClick(View view) {
102     boolean handled = false;
103     if (view instanceof OnLinkClickListener) {
104       handled = ((OnLinkClickListener) view).onLinkClick(this);
105     }
106     if (!handled) {
107       final OnClickListener listener = getLegacyListenerFromContext(view.getContext());
108       if (listener != null) {
109         listener.onClick(this);
110         handled = true;
111       }
112     }
113     return handled;
114   }
115 
116   /** @deprecated Deprecated together with {@link OnClickListener} */
117   @Nullable
118   @Deprecated
getLegacyListenerFromContext(@ullable Context context)119   private OnClickListener getLegacyListenerFromContext(@Nullable Context context) {
120     while (true) {
121       if (context instanceof OnClickListener) {
122         return (OnClickListener) context;
123       } else if (context instanceof ContextWrapper) {
124         // Unwrap any context wrapper, in base the base context implements onClickListener.
125         // ContextWrappers cannot have circular base contexts, so at some point this will
126         // reach the one of the other cases and return.
127         context = ((ContextWrapper) context).getBaseContext();
128       } else {
129         return null;
130       }
131     }
132   }
133 
134   @Override
updateDrawState(TextPaint drawState)135   public void updateDrawState(TextPaint drawState) {
136     super.updateDrawState(drawState);
137     drawState.setUnderlineText(false);
138   }
139 
getId()140   public String getId() {
141     return id;
142   }
143 }
144