4860e4ee48
Test: make setup-wizard-lib Bug: 76692459 Change-Id: I40171e973d442b1a1815e9e7d7c2cc984cb38bac
218 lines
8.9 KiB
Java
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><annotation link="foobar"> will create a
|
|
* {@link com.android.setupwizardlib.span.LinkSpan} that broadcasts with the key
|
|
* "foobar"</li>
|
|
* <li><annotation textAppearance="TextAppearance.FooBar"> 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;
|
|
}
|
|
}
|