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