| /* |
| * Copyright (C) 2006 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; |
| |
| import android.annotation.NonNull; |
| |
| import com.android.internal.util.Preconditions; |
| |
| import java.util.Locale; |
| |
| /** |
| * InputFilters can be attached to {@link Editable}s to constrain the |
| * changes that can be made to them. |
| */ |
| public interface InputFilter |
| { |
| /** |
| * This method is called when the buffer is going to replace the |
| * range <code>dstart … dend</code> of <code>dest</code> |
| * with the new text from the range <code>start … end</code> |
| * of <code>source</code>. Return the CharSequence that you would |
| * like to have placed there instead, including an empty string |
| * if appropriate, or <code>null</code> to accept the original |
| * replacement. Be careful to not to reject 0-length replacements, |
| * as this is what happens when you delete text. Also beware that |
| * you should not attempt to make any changes to <code>dest</code> |
| * from this method; you may only examine it for context. |
| * |
| * Note: If <var>source</var> is an instance of {@link Spanned} or |
| * {@link Spannable}, the span objects in the <var>source</var> should be |
| * copied into the filtered result (i.e. the non-null return value). |
| * {@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, |
| Spanned dest, int dstart, int dend); |
| |
| /** |
| * This filter will capitalize all the lowercase and titlecase letters that are added |
| * 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 { |
| 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(@NonNull Locale locale) { |
| Preconditions.checkNotNull(locale); |
| mLocale = locale; |
| } |
| |
| public CharSequence filter(CharSequence source, int start, int end, |
| Spanned dest, int dstart, int dend) { |
| final CharSequence wrapper = new CharSequenceWrapper(source, start, end); |
| |
| boolean lowerOrTitleFound = false; |
| final int length = end - start; |
| 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 |
| // ever accessed. |
| cp = Character.codePointAt(wrapper, i); |
| if (Character.isLowerCase(cp) || Character.isTitleCase(cp)) { |
| lowerOrTitleFound = true; |
| break; |
| } |
| } |
| 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 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; |
| } |
| } |
| } |
| |
| /** |
| * This filter will constrain edits not to make the length of the text |
| * greater than the specified length. |
| */ |
| public static class LengthFilter implements InputFilter { |
| private final int mMax; |
| |
| public LengthFilter(int max) { |
| mMax = max; |
| } |
| |
| public CharSequence filter(CharSequence source, int start, int end, Spanned dest, |
| int dstart, int dend) { |
| int keep = mMax - (dest.length() - (dend - dstart)); |
| if (keep <= 0) { |
| return ""; |
| } else if (keep >= end - start) { |
| return null; // keep original |
| } else { |
| keep += start; |
| if (Character.isHighSurrogate(source.charAt(keep - 1))) { |
| --keep; |
| if (keep == start) { |
| return ""; |
| } |
| } |
| return source.subSequence(start, keep); |
| } |
| } |
| |
| /** |
| * @return the maximum length enforced by this input filter |
| */ |
| public int getMax() { |
| return mMax; |
| } |
| } |
| } |