diff options
| -rw-r--r-- | core/java/android/text/method/AllCapsTransformationMethod.java | 62 |
1 files changed, 60 insertions, 2 deletions
diff --git a/core/java/android/text/method/AllCapsTransformationMethod.java b/core/java/android/text/method/AllCapsTransformationMethod.java index 0cea82114a77..15f40d5121ef 100644 --- a/core/java/android/text/method/AllCapsTransformationMethod.java +++ b/core/java/android/text/method/AllCapsTransformationMethod.java @@ -17,6 +17,10 @@ package android.text.method; import android.content.Context; import android.graphics.Rect; +import android.icu.text.CaseMap; +import android.icu.text.Edits; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.util.Log; import android.view.View; import android.widget.TextView; @@ -35,7 +39,7 @@ public class AllCapsTransformationMethod implements TransformationMethod2 { private Locale mLocale; public AllCapsTransformationMethod(Context context) { - mLocale = context.getResources().getConfiguration().locale; + mLocale = context.getResources().getConfiguration().getLocales().get(0); } @Override @@ -56,7 +60,61 @@ public class AllCapsTransformationMethod implements TransformationMethod2 { if (locale == null) { locale = mLocale; } - return source.toString().toUpperCase(locale); + + if (!(source instanceof Spanned)) { // No spans + return CaseMap.toUpper().apply( + locale, source, new StringBuilder(), + null /* we don't need the edits */); + } + + final Edits edits = new Edits(); + final SpannableStringBuilder result = CaseMap.toUpper().apply( + locale, source, new SpannableStringBuilder(), edits); + if (!edits.hasChanges()) { + // No changes happened while capitalizing. We can return the source as it was. + return source; + } + + final Edits.Iterator iterator = edits.getFineIterator(); + final Spanned spanned = (Spanned) source; + final int sourceLength = source.length(); + final Object[] spans = spanned.getSpans(0, sourceLength, Object.class); + for (Object span : spans) { + final int sourceStart = spanned.getSpanStart(span); + final int sourceEnd = spanned.getSpanEnd(span); + final int flags = spanned.getSpanFlags(span); + // Make sure the indexes are not at the end of the string, since in that case + // iterator.findSourceIndex() would fail. + final int destStart = sourceStart == sourceLength ? result.length() : + mapToDest(iterator, sourceStart); + final int destEnd = sourceEnd == sourceLength ? result.length() : + mapToDest(iterator, sourceEnd); + result.setSpan(span, destStart, destEnd, flags); + } + return result; + } + + private static int mapToDest(Edits.Iterator iterator, int sourceIndex) { + // Guaranteed to succeed if sourceIndex < source.length(). + iterator.findSourceIndex(sourceIndex); + if (sourceIndex == iterator.sourceIndex()) { + return iterator.destinationIndex(); + } + // We handle the situation differently depending on if we are in the changed slice or an + // unchanged one: In an unchanged slice, we can find the exact location the span + // boundary was before and map there. + // + // But in a changed slice, we need to treat the whole destination slice as an atomic unit. + // We adjust the span boundary to the end of that slice to reduce of the chance of adjacent + // spans in the source overlapping in the result. (The choice for the end vs the beginning + // is somewhat arbitrary, but was taken because we except to see slightly more spans only + // affecting a base character compared to spans only affecting a combining character.) + if (iterator.hasChange()) { + return iterator.destinationIndex() + iterator.newLength(); + } else { + // Move the index 1:1 along with this unchanged piece of text. + return iterator.destinationIndex() + (sourceIndex - iterator.sourceIndex()); + } } @Override |