/*
 * 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 Editables to constrain the changes that can be made to them.
/** * 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 dstart … dend of dest with the new text from the range start … end of source. Return the CharSequence that you would like to have placed there instead, including an empty string if appropriate, or null 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 dest from this method; you may only examine it for context. Note: If source is an instance of Spanned or Spannable, the span objects in the source should be copied into the filtered result (i.e. the non-null return value). TextUtils.copySpansFrom can be used for convenience if the span boundary indices would be remaining identical relative to the source.
/** * This method is called when the buffer is going to replace the * range <code>dstart &hellip; dend</code> of <code>dest</code> * with the new text from the range <code>start &hellip; 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.)
/** * 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.
/** * 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.
/** * 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); } }
Returns:the maximum length enforced by this input filter
/** * @return the maximum length enforced by this input filter */
public int getMax() { return mMax; } } }