• 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.android.setupwizardlib.view;
18 
19 import android.content.Context;
20 import android.support.v4.view.ViewCompat;
21 import android.text.Annotation;
22 import android.text.SpannableString;
23 import android.text.Spanned;
24 import android.text.method.LinkMovementMethod;
25 import android.text.style.ClickableSpan;
26 import android.text.style.TextAppearanceSpan;
27 import android.util.AttributeSet;
28 import android.util.Log;
29 import android.view.MotionEvent;
30 import android.widget.TextView;
31 
32 import com.android.setupwizardlib.span.LinkSpan;
33 import com.android.setupwizardlib.span.SpanHelper;
34 import com.android.setupwizardlib.util.LinkAccessibilityHelper;
35 
36 /**
37  * An extension of TextView that automatically replaces the annotation tags as specified in
38  * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)}
39  */
40 public class RichTextView extends TextView {
41 
42     /* static section */
43 
44     private static final String TAG = "RichTextView";
45 
46     private static final String ANNOTATION_LINK = "link";
47     private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance";
48 
49     /**
50      * Replace <annotation> tags in strings to become their respective types. Currently 2
51      * types are supported:
52      * <ol>
53      *     <li>&lt;annotation link="foobar"&gt; will create a
54      *     {@link com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key
55      *     "foobar"</li>
56      *     <li>&lt;annotation textAppearance="TextAppearance.FooBar"&gt; will create a
57      *     {@link android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar</li>
58      * </ol>
59      */
getRichText(Context context, CharSequence text)60     public static CharSequence getRichText(Context context, CharSequence text) {
61         if (text instanceof Spanned) {
62             final SpannableString spannable = new SpannableString(text);
63             final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class);
64             for (Annotation span : spans) {
65                 final String key = span.getKey();
66                 if (ANNOTATION_TEXT_APPEARANCE.equals(key)) {
67                     String textAppearance = span.getValue();
68                     final int style = context.getResources()
69                             .getIdentifier(textAppearance, "style", context.getPackageName());
70                     if (style == 0) {
71                         Log.w(TAG, "Cannot find resource: " + style);
72                     }
73                     final TextAppearanceSpan textAppearanceSpan =
74                             new TextAppearanceSpan(context, style);
75                     SpanHelper.replaceSpan(spannable, span, textAppearanceSpan);
76                 } else if (ANNOTATION_LINK.equals(key)) {
77                     LinkSpan link = new LinkSpan(span.getValue());
78                     SpanHelper.replaceSpan(spannable, span, link);
79                 }
80             }
81             return spannable;
82         }
83         return text;
84     }
85 
86     /* non-static section */
87 
88     private LinkAccessibilityHelper mAccessibilityHelper;
89 
RichTextView(Context context)90     public RichTextView(Context context) {
91         super(context);
92         init();
93     }
94 
RichTextView(Context context, AttributeSet attrs)95     public RichTextView(Context context, AttributeSet attrs) {
96         super(context, attrs);
97         init();
98     }
99 
init()100     private void init() {
101         mAccessibilityHelper = new LinkAccessibilityHelper(this);
102         ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
103     }
104 
105     @Override
setText(CharSequence text, BufferType type)106     public void setText(CharSequence text, BufferType type) {
107         text = getRichText(getContext(), text);
108         // Set text first before doing anything else because setMovementMethod internally calls
109         // setText. This in turn ends up calling this method with mText as the first parameter
110         super.setText(text, type);
111         boolean hasLinks = hasLinks(text);
112 
113         if (hasLinks) {
114             // When a TextView has a movement method, it will set the view to clickable. This makes
115             // View.onTouchEvent always return true and consumes the touch event, essentially
116             // nullifying any return values of MovementMethod.onTouchEvent.
117             // To still allow propagating touch events to the parent when this view doesn't have
118             // links, we only set the movement method here if the text contains links.
119             setMovementMethod(LinkMovementMethod.getInstance());
120         } else {
121             setMovementMethod(null);
122         }
123         // ExploreByTouchHelper automatically enables focus for RichTextView
124         // even though it may not have any links. Causes problems during talkback
125         // as individual TextViews consume touch events and thereby reducing the focus window
126         // shown by Talkback. Disable focus if there are no links
127         setFocusable(hasLinks);
128     }
129 
hasLinks(CharSequence text)130     private boolean hasLinks(CharSequence text) {
131         if (text instanceof Spanned) {
132             final ClickableSpan[] spans =
133                     ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
134             return spans.length > 0;
135         }
136         return false;
137     }
138 
139     @Override
dispatchHoverEvent(MotionEvent event)140     protected boolean dispatchHoverEvent(MotionEvent event) {
141         if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
142             return true;
143         }
144         return super.dispatchHoverEvent(event);
145     }
146 }
147