diff options
| -rw-r--r-- | api/current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/text/TextUtils.java | 11 | ||||
| -rw-r--r-- | core/java/android/text/style/AccessibilityReplacementSpan.java | 100 | ||||
| -rw-r--r-- | core/java/android/text/style/ReplacementSpan.java | 23 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityNodeInfo.java | 101 |
5 files changed, 210 insertions, 27 deletions
diff --git a/api/current.txt b/api/current.txt index 47cfe3cb3e80..d70fbdb96a8b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); } diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 81643e90428f..5bda86704d88 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -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); } diff --git a/core/java/android/text/style/AccessibilityReplacementSpan.java b/core/java/android/text/style/AccessibilityReplacementSpan.java new file mode 100644 index 000000000000..07b0975846aa --- /dev/null +++ b/core/java/android/text/style/AccessibilityReplacementSpan.java @@ -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]; + } + }; +} diff --git a/core/java/android/text/style/ReplacementSpan.java b/core/java/android/text/style/ReplacementSpan.java index 5f94ad054b63..05532326cd84 100644 --- a/core/java/android/text/style/ReplacementSpan.java +++ b/core/java/android/text/style/ReplacementSpan.java @@ -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 @@ -61,6 +63,27 @@ public abstract class ReplacementSpan extends MetricAffectingSpan { 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. */ diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index cf29ed712793..06e9d0dbf6d6 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -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,37 +2643,86 @@ 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. * * @return The hint text. |