Merge "Create new API for ImageSpan"

This commit is contained in:
Kevin Chang
2019-10-08 06:09:44 +00:00
committed by Android (Google) Code Review
5 changed files with 210 additions and 27 deletions

View File

@@ -47136,7 +47136,9 @@ package android.text.style {
public abstract class ReplacementSpan extends android.text.style.MetricAffectingSpan {
ctor public ReplacementSpan();
method public abstract void draw(@NonNull android.graphics.Canvas, CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, float, int, int, int, @NonNull android.graphics.Paint);
method @Nullable public CharSequence getContentDescription();
method public abstract int getSize(@NonNull android.graphics.Paint, CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @Nullable android.graphics.Paint.FontMetricsInt);
method public void setContentDescription(@Nullable CharSequence);
method public void updateDrawState(android.text.TextPaint);
method public void updateMeasureState(android.text.TextPaint);
}

View File

@@ -36,6 +36,7 @@ import android.os.Parcelable;
import android.sysprop.DisplayProperties;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AccessibilityClickableSpan;
import android.text.style.AccessibilityReplacementSpan;
import android.text.style.AccessibilityURLSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
@@ -735,6 +736,8 @@ public class TextUtils {
/** @hide */
public static final int LINE_HEIGHT_SPAN = 28;
/** @hide */
public static final int ACCESSIBILITY_REPLACEMENT_SPAN = 29;
/** @hide */
public static final int LAST_SPAN = LINE_HEIGHT_SPAN;
/**
@@ -860,7 +863,7 @@ public class TextUtils {
case LEADING_MARGIN_SPAN:
readSpan(p, sp, new LeadingMarginSpan.Standard(p));
break;
break;
case URL_SPAN:
readSpan(p, sp, new URLSpan(p));
@@ -933,7 +936,11 @@ public class TextUtils {
case LINE_HEIGHT_SPAN:
readSpan(p, sp, new LineHeightSpan.Standard(p));
break;
case ACCESSIBILITY_REPLACEMENT_SPAN:
readSpan(p, sp, new AccessibilityReplacementSpan(p));
break;
default:
throw new RuntimeException("bogus span encoding " + kind);
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2019 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 android.text.style;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.ParcelableSpan;
import android.text.TextUtils;
/**
* This class serves as a parcelable placeholder for the ReplacementSpans.
*
* This span contains content description of original span to let Accessibility service to do the
* substitution for it.
*
* @hide
*/
public class AccessibilityReplacementSpan extends ReplacementSpan
implements ParcelableSpan {
// The content description of the span this one replaces
private CharSequence mContentDescription;
/**
* @param contentDescription The content description of the span this one replaces
*/
public AccessibilityReplacementSpan(CharSequence contentDescription) {
this.setContentDescription(contentDescription);
mContentDescription = contentDescription;
}
public AccessibilityReplacementSpan(Parcel p) {
mContentDescription = p.readCharSequence();
}
@Override
public int getSpanTypeId() {
return getSpanTypeIdInternal();
}
@Override
public int getSpanTypeIdInternal() {
return TextUtils.ACCESSIBILITY_REPLACEMENT_SPAN;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
writeToParcelInternal(dest, flags);
}
@Override
public void writeToParcelInternal(Parcel dest, int flags) {
dest.writeCharSequence(mContentDescription);
}
@Override
public int getSize(Paint paint, CharSequence text, int start, int end,
Paint.FontMetricsInt fm) {
return 0;
}
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
int bottom, Paint paint) {
}
public static final @android.annotation.NonNull
Parcelable.Creator<AccessibilityReplacementSpan> CREATOR =
new Parcelable.Creator<AccessibilityReplacementSpan>() {
@Override
public AccessibilityReplacementSpan createFromParcel(Parcel parcel) {
return new AccessibilityReplacementSpan(parcel);
}
@Override
public AccessibilityReplacementSpan[] newArray(int size) {
return new AccessibilityReplacementSpan[size];
}
};
}

View File

@@ -25,6 +25,8 @@ import android.text.TextPaint;
public abstract class ReplacementSpan extends MetricAffectingSpan {
private CharSequence mContentDescription = null;
/**
* Returns the width of the span. Extending classes can set the height of the span by updating
* attributes of {@link android.graphics.Paint.FontMetricsInt}. If the span covers the whole
@@ -60,6 +62,27 @@ public abstract class ReplacementSpan extends MetricAffectingSpan {
@IntRange(from = 0) int start, @IntRange(from = 0) int end, float x,
int top, int y, int bottom, @NonNull Paint paint);
/**
* Gets a brief description of this ImageSpan for use in accessibility support.
*
* @return The content description.
*/
@Nullable
public CharSequence getContentDescription() {
return mContentDescription;
}
/**
* Sets the specific content description into ImageSpan.
* ReplacementSpans are shared with accessibility services,
* but only the content description is available from them.
*
* @param contentDescription content description. The default value is null.
*/
public void setContentDescription(@Nullable CharSequence contentDescription) {
mContentDescription = contentDescription;
}
/**
* This method does nothing, since ReplacementSpans are measured
* explicitly instead of affecting Paint properties.

View File

@@ -39,8 +39,10 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AccessibilityClickableSpan;
import android.text.style.AccessibilityReplacementSpan;
import android.text.style.AccessibilityURLSpan;
import android.text.style.ClickableSpan;
import android.text.style.ReplacementSpan;
import android.text.style.URLSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -2641,36 +2643,85 @@ public class AccessibilityNodeInfo implements Parcelable {
public void setText(CharSequence text) {
enforceNotSealed();
mOriginalText = text;
// Replace any ClickableSpans in mText with placeholders
if (text instanceof Spanned) {
ClickableSpan[] spans =
((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
if (spans.length > 0) {
Spannable spannable = new SpannableStringBuilder(text);
for (int i = 0; i < spans.length; i++) {
ClickableSpan span = spans[i];
if ((span instanceof AccessibilityClickableSpan)
|| (span instanceof AccessibilityURLSpan)) {
// We've already done enough
break;
}
int spanToReplaceStart = spannable.getSpanStart(span);
int spanToReplaceEnd = spannable.getSpanEnd(span);
int spanToReplaceFlags = spannable.getSpanFlags(span);
spannable.removeSpan(span);
ClickableSpan replacementSpan = (span instanceof URLSpan)
? new AccessibilityURLSpan((URLSpan) span)
: new AccessibilityClickableSpan(span.getId());
spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
spanToReplaceFlags);
}
mText = spannable;
return;
}
CharSequence tmpText = text;
tmpText = replaceClickableSpan(tmpText);
tmpText = replaceReplacementSpan(tmpText);
mText = tmpText;
return;
}
mText = (text == null) ? null : text.subSequence(0, text.length());
}
/**
* Replaces any ClickableSpans in mText with placeholders.
*
* @param text The text.
*
* @return The spannable with ClickableSpan replacement.
*/
private CharSequence replaceClickableSpan(CharSequence text) {
ClickableSpan[] clickableSpans =
((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
Spannable spannable = new SpannableStringBuilder(text);
if (clickableSpans.length == 0) {
return text;
}
for (int i = 0; i < clickableSpans.length; i++) {
ClickableSpan span = clickableSpans[i];
if ((span instanceof AccessibilityClickableSpan)
|| (span instanceof AccessibilityURLSpan)) {
// We've already done enough
break;
}
int spanToReplaceStart = spannable.getSpanStart(span);
int spanToReplaceEnd = spannable.getSpanEnd(span);
int spanToReplaceFlags = spannable.getSpanFlags(span);
spannable.removeSpan(span);
ClickableSpan replacementSpan = (span instanceof URLSpan)
? new AccessibilityURLSpan((URLSpan) span)
: new AccessibilityClickableSpan(span.getId());
spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
spanToReplaceFlags);
}
return spannable;
}
/**
* Replace any ImageSpans in mText with its content description.
*
* @param text The text.
*
* @return The spannable with ReplacementSpan replacement.
*/
private CharSequence replaceReplacementSpan(CharSequence text) {
ReplacementSpan[] replacementSpans =
((Spanned) text).getSpans(0, text.length(), ReplacementSpan.class);
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
if (replacementSpans.length == 0) {
return text;
}
for (int i = 0; i < replacementSpans.length; i++) {
ReplacementSpan span = replacementSpans[i];
CharSequence replacementText = span.getContentDescription();
if (span instanceof AccessibilityReplacementSpan) {
// We've already done enough
break;
}
if (replacementText == null) {
continue;
}
int spanToReplaceStart = spannable.getSpanStart(span);
int spanToReplaceEnd = spannable.getSpanEnd(span);
int spanToReplaceFlags = spannable.getSpanFlags(span);
spannable.removeSpan(span);
ReplacementSpan replacementSpan = new AccessibilityReplacementSpan(replacementText);
spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
spanToReplaceFlags);
}
return spannable;
}
/**
* Gets the hint text of this node. Only applies to nodes where text can be entered.
*