SetupWizardLibrary/library/gingerbread/src/com/android/setupwizardlib/view/RichTextView.java
Aurimas Liutikas 4860e4ee48 Migrate setup-wizard-lib to androidx.
Test: make setup-wizard-lib
Bug: 76692459
Change-Id: I40171e973d442b1a1815e9e7d7c2cc984cb38bac
2018-04-18 17:26:19 -07:00

218 lines
8.9 KiB
Java

/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.setupwizardlib.view;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.text.Annotation;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.MovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.TextAppearanceSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.view.ViewCompat;
import com.android.setupwizardlib.span.LinkSpan;
import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener;
import com.android.setupwizardlib.span.SpanHelper;
import com.android.setupwizardlib.util.LinkAccessibilityHelper;
import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod;
/**
* An extension of TextView that automatically replaces the annotation tags as specified in
* {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)}
*/
public class RichTextView extends AppCompatTextView implements OnLinkClickListener {
/* static section */
private static final String TAG = "RichTextView";
private static final String ANNOTATION_LINK = "link";
private static final String ANNOTATION_TEXT_APPEARANCE = "textAppearance";
/**
* Replace <annotation> tags in strings to become their respective types. Currently 2
* types are supported:
* <ol>
* <li>&lt;annotation link="foobar"&gt; will create a
* {@link com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key
* "foobar"</li>
* <li>&lt;annotation textAppearance="TextAppearance.FooBar"&gt; will create a
* {@link android.text.style.TextAppearanceSpan} with @style/TextAppearance.FooBar</li>
* </ol>
*/
public static CharSequence getRichText(Context context, CharSequence text) {
if (text instanceof Spanned) {
final SpannableString spannable = new SpannableString(text);
final Annotation[] spans = spannable.getSpans(0, spannable.length(), Annotation.class);
for (Annotation span : spans) {
final String key = span.getKey();
if (ANNOTATION_TEXT_APPEARANCE.equals(key)) {
String textAppearance = span.getValue();
final int style = context.getResources()
.getIdentifier(textAppearance, "style", context.getPackageName());
if (style == 0) {
Log.w(TAG, "Cannot find resource: " + style);
}
final TextAppearanceSpan textAppearanceSpan =
new TextAppearanceSpan(context, style);
SpanHelper.replaceSpan(spannable, span, textAppearanceSpan);
} else if (ANNOTATION_LINK.equals(key)) {
LinkSpan link = new LinkSpan(span.getValue());
SpanHelper.replaceSpan(spannable, span, link);
}
}
return spannable;
}
return text;
}
/* non-static section */
private LinkAccessibilityHelper mAccessibilityHelper;
private OnLinkClickListener mOnLinkClickListener;
public RichTextView(Context context) {
super(context);
init();
}
public RichTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mAccessibilityHelper = new LinkAccessibilityHelper(this);
ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
}
@Override
public void setText(CharSequence text, BufferType type) {
text = getRichText(getContext(), text);
// Set text first before doing anything else because setMovementMethod internally calls
// setText. This in turn ends up calling this method with mText as the first parameter
super.setText(text, type);
boolean hasLinks = hasLinks(text);
if (hasLinks) {
// When a TextView has a movement method, it will set the view to clickable. This makes
// View.onTouchEvent always return true and consumes the touch event, essentially
// nullifying any return values of MovementMethod.onTouchEvent.
// To still allow propagating touch events to the parent when this view doesn't have
// links, we only set the movement method here if the text contains links.
setMovementMethod(TouchableLinkMovementMethod.getInstance());
} else {
setMovementMethod(null);
}
// ExploreByTouchHelper automatically enables focus for RichTextView
// even though it may not have any links. Causes problems during talkback
// as individual TextViews consume touch events and thereby reducing the focus window
// shown by Talkback. Disable focus if there are no links
setFocusable(hasLinks);
// Do not "reveal" (i.e. scroll to) this view when this view is focused. Since this view is
// focusable in touch mode, we may be focused when the screen is first shown, and starting
// a screen halfway scrolled down is confusing to the user.
if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
setRevealOnFocusHint(false);
// setRevealOnFocusHint is a new API added in SDK 25. For lower SDK versions, do not
// call setFocusableInTouchMode. We won't get touch effect on those earlier versions,
// but the link will still work, and will prevent the scroll view from starting halfway
// down the page.
setFocusableInTouchMode(hasLinks);
}
}
private boolean hasLinks(CharSequence text) {
if (text instanceof Spanned) {
final ClickableSpan[] spans =
((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
return spans.length > 0;
}
return false;
}
@Override
@SuppressWarnings("ClickableViewAccessibility") // super.onTouchEvent is called
public boolean onTouchEvent(MotionEvent event) {
// Since View#onTouchEvent always return true if the view is clickable (which is the case
// when a TextView has a movement method), override the implementation to allow the movement
// method, if it implements TouchableMovementMethod, to say that the touch is not handled,
// allowing the event to bubble up to the parent view.
boolean superResult = super.onTouchEvent(event);
MovementMethod movementMethod = getMovementMethod();
if (movementMethod instanceof TouchableMovementMethod) {
TouchableMovementMethod touchableMovementMethod =
(TouchableMovementMethod) movementMethod;
if (touchableMovementMethod.getLastTouchEvent() == event) {
return touchableMovementMethod.isLastTouchEventHandled();
}
}
return superResult;
}
@Override
protected boolean dispatchHoverEvent(MotionEvent event) {
if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
return true;
}
return super.dispatchHoverEvent(event);
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
// b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a
// workaround, set the state on those drawables directly.
final int[] state = getDrawableState();
for (Drawable drawable : getCompoundDrawablesRelative()) {
if (drawable != null) {
if (drawable.setState(state)) {
invalidateDrawable(drawable);
}
}
}
}
}
public void setOnLinkClickListener(OnLinkClickListener listener) {
mOnLinkClickListener = listener;
}
public OnLinkClickListener getOnLinkClickListener() {
return mOnLinkClickListener;
}
@Override
public boolean onLinkClick(LinkSpan span) {
if (mOnLinkClickListener != null) {
return mOnLinkClickListener.onLinkClick(span);
}
return false;
}
}