Loading api/current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -41102,6 +41102,7 @@ package android.text { public static class InputFilter.AllCaps implements android.text.InputFilter { public static class InputFilter.AllCaps implements android.text.InputFilter { ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(java.util.Locale); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); } } api/system-current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -44647,6 +44647,7 @@ package android.text { public static class InputFilter.AllCaps implements android.text.InputFilter { public static class InputFilter.AllCaps implements android.text.InputFilter { ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(java.util.Locale); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); } } api/test-current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -41329,6 +41329,7 @@ package android.text { public static class InputFilter.AllCaps implements android.text.InputFilter { public static class InputFilter.AllCaps implements android.text.InputFilter { ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(java.util.Locale); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); } } core/java/android/text/InputFilter.java +107 −21 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,10 @@ package android.text; package android.text; import android.annotation.Nullable; import java.util.Locale; /** /** * InputFilters can be attached to {@link Editable}s to constrain the * InputFilters can be attached to {@link Editable}s to constrain the * changes that can be made to them. * changes that can be made to them. Loading @@ -37,37 +41,119 @@ public interface InputFilter * Note: If <var>source</var> is an instance of {@link Spanned} or * Note: If <var>source</var> is an instance of {@link Spanned} or * {@link Spannable}, the span objects in the <var>source</var> should be * {@link Spannable}, the span objects in the <var>source</var> should be * copied into the filtered result (i.e. the non-null return value). * copied into the filtered result (i.e. the non-null return value). * {@link TextUtils#copySpansFrom} can be used for convenience. * {@link TextUtils#copySpansFrom} can be used for convenience if the * span boundary indices would be remaining identical relative to the source. */ */ public CharSequence filter(CharSequence source, int start, int end, public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend); Spanned dest, int dstart, int dend); /** /** * This filter will capitalize all the lower case letters that are added * This filter will capitalize all the lowercase and titlecase letters that are added * through edits. * through edits. (Note that if there are no lowercase or titlecase letters in the input, the * text would not be transformed, even if the result of capitalization of the string is * different from the string.) */ */ public static class AllCaps implements InputFilter { public static class AllCaps implements InputFilter { private final Locale mLocale; public AllCaps() { mLocale = null; } /** * Constructs a locale-specific AllCaps filter, to make sure capitalization rules of that * locale are used for transforming the sequence. */ public AllCaps(@Nullable Locale locale) { mLocale = locale; } public CharSequence filter(CharSequence source, int start, int end, public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { Spanned dest, int dstart, int dend) { for (int i = start; i < end; i++) { final CharSequence wrapper = new CharSequenceWrapper(source, start, end); if (Character.isLowerCase(source.charAt(i))) { char[] v = new char[end - start]; boolean lowerOrTitleFound = false; TextUtils.getChars(source, start, end, v, 0); final int length = end - start; String s = new String(v).toUpperCase(); for (int i = 0, cp; i < length; i += Character.charCount(cp)) { // We access 'wrapper' instead of 'source' to make sure no code unit beyond 'end' is if (source instanceof Spanned) { // ever accessed. SpannableString sp = new SpannableString(s); cp = Character.codePointAt(wrapper, i); TextUtils.copySpansFrom((Spanned) source, if (Character.isLowerCase(cp) || Character.isTitleCase(cp)) { start, end, null, sp, 0); lowerOrTitleFound = true; return sp; break; } else { return s; } } } } if (!lowerOrTitleFound) { return null; // keep original } } final boolean copySpans = source instanceof Spanned; final CharSequence upper = TextUtils.toUpperCase(mLocale, wrapper, copySpans); if (upper == wrapper) { // Nothing was changed in the uppercasing operation. This is weird, since // we had found at least one lowercase or titlecase character. But we can't // do anything better than keeping the original in this case. return null; // keep original return null; // keep original } } // Return a SpannableString or String for backward compatibility. return copySpans ? new SpannableString(upper) : upper.toString(); } private static class CharSequenceWrapper implements CharSequence, Spanned { private final CharSequence mSource; private final int mStart, mEnd; private final int mLength; CharSequenceWrapper(CharSequence source, int start, int end) { mSource = source; mStart = start; mEnd = end; mLength = end - start; } public int length() { return mLength; } public char charAt(int index) { if (index < 0 || index >= mLength) { throw new IndexOutOfBoundsException(); } return mSource.charAt(mStart + index); } public CharSequence subSequence(int start, int end) { if (start < 0 || end < 0 || end > mLength || start > end) { throw new IndexOutOfBoundsException(); } return new CharSequenceWrapper(mSource, mStart + start, mStart + end); } public String toString() { return mSource.subSequence(mStart, mEnd).toString(); } public <T> T[] getSpans(int start, int end, Class<T> type) { return ((Spanned) mSource).getSpans(mStart + start, mStart + end, type); } public int getSpanStart(Object tag) { return ((Spanned) mSource).getSpanStart(tag) - mStart; } public int getSpanEnd(Object tag) { return ((Spanned) mSource).getSpanEnd(tag) - mStart; } public int getSpanFlags(Object tag) { return ((Spanned) mSource).getSpanFlags(tag); } public int nextSpanTransition(int start, int limit, Class type) { return ((Spanned) mSource).nextSpanTransition(mStart + start, mStart + limit, type) - mStart; } } } } /** /** Loading core/java/android/text/TextUtils.java +71 −0 Original line number Original line Diff line number Diff line Loading @@ -23,6 +23,8 @@ import android.annotation.PluralsRes; import android.content.Context; import android.content.Context; import android.content.res.Resources; import android.content.res.Resources; import android.icu.lang.UCharacter; import android.icu.lang.UCharacter; import android.icu.text.CaseMap; import android.icu.text.Edits; import android.icu.util.ULocale; import android.icu.util.ULocale; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; Loading Loading @@ -1072,6 +1074,75 @@ public class TextUtils { } } } } /** * Transforms a CharSequences to uppercase, copying the sources spans and keeping them spans as * much as possible close to their relative original places. In the case the the uppercase * string is identical to the sources, the source itself is returned instead of being copied. * * If copySpans is set, source must be an instance of Spanned. * * {@hide} */ @NonNull public static CharSequence toUpperCase(@Nullable Locale locale, @NonNull CharSequence source, boolean copySpans) { final Edits edits = new Edits(); if (!copySpans) { // No spans. Just uppercase the characters. final StringBuilder result = CaseMap.toUpper().apply( locale, source, new StringBuilder(), edits); return edits.hasChanges() ? result : source; } 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 int sourceLength = source.length(); final Spanned spanned = (Spanned) source; 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 indices are not at the end of the string, since in that case // iterator.findSourceIndex() would fail. final int destStart = sourceStart == sourceLength ? result.length() : toUpperMapToDest(iterator, sourceStart); final int destEnd = sourceEnd == sourceLength ? result.length() : toUpperMapToDest(iterator, sourceEnd); result.setSpan(span, destStart, destEnd, flags); } return result; } // helper method for toUpperCase() private static int toUpperMapToDest(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()); } } public enum TruncateAt { public enum TruncateAt { START, START, MIDDLE, MIDDLE, Loading Loading
api/current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -41102,6 +41102,7 @@ package android.text { public static class InputFilter.AllCaps implements android.text.InputFilter { public static class InputFilter.AllCaps implements android.text.InputFilter { ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(java.util.Locale); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); } }
api/system-current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -44647,6 +44647,7 @@ package android.text { public static class InputFilter.AllCaps implements android.text.InputFilter { public static class InputFilter.AllCaps implements android.text.InputFilter { ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(java.util.Locale); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); } }
api/test-current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -41329,6 +41329,7 @@ package android.text { public static class InputFilter.AllCaps implements android.text.InputFilter { public static class InputFilter.AllCaps implements android.text.InputFilter { ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(); ctor public InputFilter.AllCaps(java.util.Locale); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); method public java.lang.CharSequence filter(java.lang.CharSequence, int, int, android.text.Spanned, int, int); } }
core/java/android/text/InputFilter.java +107 −21 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,10 @@ package android.text; package android.text; import android.annotation.Nullable; import java.util.Locale; /** /** * InputFilters can be attached to {@link Editable}s to constrain the * InputFilters can be attached to {@link Editable}s to constrain the * changes that can be made to them. * changes that can be made to them. Loading @@ -37,37 +41,119 @@ public interface InputFilter * Note: If <var>source</var> is an instance of {@link Spanned} or * Note: If <var>source</var> is an instance of {@link Spanned} or * {@link Spannable}, the span objects in the <var>source</var> should be * {@link Spannable}, the span objects in the <var>source</var> should be * copied into the filtered result (i.e. the non-null return value). * copied into the filtered result (i.e. the non-null return value). * {@link TextUtils#copySpansFrom} can be used for convenience. * {@link TextUtils#copySpansFrom} can be used for convenience if the * span boundary indices would be remaining identical relative to the source. */ */ public CharSequence filter(CharSequence source, int start, int end, public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend); Spanned dest, int dstart, int dend); /** /** * This filter will capitalize all the lower case letters that are added * This filter will capitalize all the lowercase and titlecase letters that are added * through edits. * through edits. (Note that if there are no lowercase or titlecase letters in the input, the * text would not be transformed, even if the result of capitalization of the string is * different from the string.) */ */ public static class AllCaps implements InputFilter { public static class AllCaps implements InputFilter { private final Locale mLocale; public AllCaps() { mLocale = null; } /** * Constructs a locale-specific AllCaps filter, to make sure capitalization rules of that * locale are used for transforming the sequence. */ public AllCaps(@Nullable Locale locale) { mLocale = locale; } public CharSequence filter(CharSequence source, int start, int end, public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { Spanned dest, int dstart, int dend) { for (int i = start; i < end; i++) { final CharSequence wrapper = new CharSequenceWrapper(source, start, end); if (Character.isLowerCase(source.charAt(i))) { char[] v = new char[end - start]; boolean lowerOrTitleFound = false; TextUtils.getChars(source, start, end, v, 0); final int length = end - start; String s = new String(v).toUpperCase(); for (int i = 0, cp; i < length; i += Character.charCount(cp)) { // We access 'wrapper' instead of 'source' to make sure no code unit beyond 'end' is if (source instanceof Spanned) { // ever accessed. SpannableString sp = new SpannableString(s); cp = Character.codePointAt(wrapper, i); TextUtils.copySpansFrom((Spanned) source, if (Character.isLowerCase(cp) || Character.isTitleCase(cp)) { start, end, null, sp, 0); lowerOrTitleFound = true; return sp; break; } else { return s; } } } } if (!lowerOrTitleFound) { return null; // keep original } } final boolean copySpans = source instanceof Spanned; final CharSequence upper = TextUtils.toUpperCase(mLocale, wrapper, copySpans); if (upper == wrapper) { // Nothing was changed in the uppercasing operation. This is weird, since // we had found at least one lowercase or titlecase character. But we can't // do anything better than keeping the original in this case. return null; // keep original return null; // keep original } } // Return a SpannableString or String for backward compatibility. return copySpans ? new SpannableString(upper) : upper.toString(); } private static class CharSequenceWrapper implements CharSequence, Spanned { private final CharSequence mSource; private final int mStart, mEnd; private final int mLength; CharSequenceWrapper(CharSequence source, int start, int end) { mSource = source; mStart = start; mEnd = end; mLength = end - start; } public int length() { return mLength; } public char charAt(int index) { if (index < 0 || index >= mLength) { throw new IndexOutOfBoundsException(); } return mSource.charAt(mStart + index); } public CharSequence subSequence(int start, int end) { if (start < 0 || end < 0 || end > mLength || start > end) { throw new IndexOutOfBoundsException(); } return new CharSequenceWrapper(mSource, mStart + start, mStart + end); } public String toString() { return mSource.subSequence(mStart, mEnd).toString(); } public <T> T[] getSpans(int start, int end, Class<T> type) { return ((Spanned) mSource).getSpans(mStart + start, mStart + end, type); } public int getSpanStart(Object tag) { return ((Spanned) mSource).getSpanStart(tag) - mStart; } public int getSpanEnd(Object tag) { return ((Spanned) mSource).getSpanEnd(tag) - mStart; } public int getSpanFlags(Object tag) { return ((Spanned) mSource).getSpanFlags(tag); } public int nextSpanTransition(int start, int limit, Class type) { return ((Spanned) mSource).nextSpanTransition(mStart + start, mStart + limit, type) - mStart; } } } } /** /** Loading
core/java/android/text/TextUtils.java +71 −0 Original line number Original line Diff line number Diff line Loading @@ -23,6 +23,8 @@ import android.annotation.PluralsRes; import android.content.Context; import android.content.Context; import android.content.res.Resources; import android.content.res.Resources; import android.icu.lang.UCharacter; import android.icu.lang.UCharacter; import android.icu.text.CaseMap; import android.icu.text.Edits; import android.icu.util.ULocale; import android.icu.util.ULocale; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; Loading Loading @@ -1072,6 +1074,75 @@ public class TextUtils { } } } } /** * Transforms a CharSequences to uppercase, copying the sources spans and keeping them spans as * much as possible close to their relative original places. In the case the the uppercase * string is identical to the sources, the source itself is returned instead of being copied. * * If copySpans is set, source must be an instance of Spanned. * * {@hide} */ @NonNull public static CharSequence toUpperCase(@Nullable Locale locale, @NonNull CharSequence source, boolean copySpans) { final Edits edits = new Edits(); if (!copySpans) { // No spans. Just uppercase the characters. final StringBuilder result = CaseMap.toUpper().apply( locale, source, new StringBuilder(), edits); return edits.hasChanges() ? result : source; } 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 int sourceLength = source.length(); final Spanned spanned = (Spanned) source; 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 indices are not at the end of the string, since in that case // iterator.findSourceIndex() would fail. final int destStart = sourceStart == sourceLength ? result.length() : toUpperMapToDest(iterator, sourceStart); final int destEnd = sourceEnd == sourceLength ? result.length() : toUpperMapToDest(iterator, sourceEnd); result.setSpan(span, destStart, destEnd, flags); } return result; } // helper method for toUpperCase() private static int toUpperMapToDest(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()); } } public enum TruncateAt { public enum TruncateAt { START, START, MIDDLE, MIDDLE, Loading