Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aa8380fa authored by Roozbeh Pournader's avatar Roozbeh Pournader Committed by Android (Google) Code Review
Browse files

Merge "Internationalize InputFilter.AllCaps"

parents 938ffbc8 205a9939
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -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);
  }
  }
+1 −0
Original line number Original line Diff line number Diff line
@@ -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);
  }
  }
+1 −0
Original line number Original line Diff line number Diff line
@@ -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);
  }
  }
+107 −21
Original line number Original line Diff line number Diff line
@@ -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.
@@ -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;
            }
        }
    }
    }


    /**
    /**
+71 −0
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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