1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.text; 18 19 import android.annotation.NonNull; 20 import android.compat.annotation.UnsupportedAppUsage; 21 22 import com.android.internal.util.Preconditions; 23 24 import java.util.Locale; 25 26 /** 27 * InputFilters can be attached to {@link Editable}s to constrain the 28 * changes that can be made to them. 29 */ 30 public interface InputFilter 31 { 32 /** 33 * This method is called when the buffer is going to replace the 34 * range <code>dstart … dend</code> of <code>dest</code> 35 * with the new text from the range <code>start … end</code> 36 * of <code>source</code>. Return the CharSequence that you would 37 * like to have placed there instead, including an empty string 38 * if appropriate, or <code>null</code> to accept the original 39 * replacement. Be careful to not to reject 0-length replacements, 40 * as this is what happens when you delete text. Also beware that 41 * you should not attempt to make any changes to <code>dest</code> 42 * from this method; you may only examine it for context. 43 * 44 * Note: If <var>source</var> is an instance of {@link Spanned} or 45 * {@link Spannable}, the span objects in the <var>source</var> should be 46 * copied into the filtered result (i.e. the non-null return value). 47 * {@link TextUtils#copySpansFrom} can be used for convenience if the 48 * span boundary indices would be remaining identical relative to the source. 49 */ filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)50 public CharSequence filter(CharSequence source, int start, int end, 51 Spanned dest, int dstart, int dend); 52 53 /** 54 * This filter will capitalize all the lowercase and titlecase letters that are added 55 * through edits. (Note that if there are no lowercase or titlecase letters in the input, the 56 * text would not be transformed, even if the result of capitalization of the string is 57 * different from the string.) 58 */ 59 public static class AllCaps implements InputFilter { 60 private final Locale mLocale; 61 AllCaps()62 public AllCaps() { 63 mLocale = null; 64 } 65 66 /** 67 * Constructs a locale-specific AllCaps filter, to make sure capitalization rules of that 68 * locale are used for transforming the sequence. 69 */ AllCaps(@onNull Locale locale)70 public AllCaps(@NonNull Locale locale) { 71 Preconditions.checkNotNull(locale); 72 mLocale = locale; 73 } 74 filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)75 public CharSequence filter(CharSequence source, int start, int end, 76 Spanned dest, int dstart, int dend) { 77 final CharSequence wrapper = new CharSequenceWrapper(source, start, end); 78 79 boolean lowerOrTitleFound = false; 80 final int length = end - start; 81 for (int i = 0, cp; i < length; i += Character.charCount(cp)) { 82 // We access 'wrapper' instead of 'source' to make sure no code unit beyond 'end' is 83 // ever accessed. 84 cp = Character.codePointAt(wrapper, i); 85 if (Character.isLowerCase(cp) || Character.isTitleCase(cp)) { 86 lowerOrTitleFound = true; 87 break; 88 } 89 } 90 if (!lowerOrTitleFound) { 91 return null; // keep original 92 } 93 94 final boolean copySpans = source instanceof Spanned; 95 final CharSequence upper = TextUtils.toUpperCase(mLocale, wrapper, copySpans); 96 if (upper == wrapper) { 97 // Nothing was changed in the uppercasing operation. This is weird, since 98 // we had found at least one lowercase or titlecase character. But we can't 99 // do anything better than keeping the original in this case. 100 return null; // keep original 101 } 102 // Return a SpannableString or String for backward compatibility. 103 return copySpans ? new SpannableString(upper) : upper.toString(); 104 } 105 106 private static class CharSequenceWrapper implements CharSequence, Spanned { 107 private final CharSequence mSource; 108 private final int mStart, mEnd; 109 private final int mLength; 110 CharSequenceWrapper(CharSequence source, int start, int end)111 CharSequenceWrapper(CharSequence source, int start, int end) { 112 mSource = source; 113 mStart = start; 114 mEnd = end; 115 mLength = end - start; 116 } 117 length()118 public int length() { 119 return mLength; 120 } 121 charAt(int index)122 public char charAt(int index) { 123 if (index < 0 || index >= mLength) { 124 throw new IndexOutOfBoundsException(); 125 } 126 return mSource.charAt(mStart + index); 127 } 128 subSequence(int start, int end)129 public CharSequence subSequence(int start, int end) { 130 if (start < 0 || end < 0 || end > mLength || start > end) { 131 throw new IndexOutOfBoundsException(); 132 } 133 return new CharSequenceWrapper(mSource, mStart + start, mStart + end); 134 } 135 toString()136 public String toString() { 137 return mSource.subSequence(mStart, mEnd).toString(); 138 } 139 getSpans(int start, int end, Class<T> type)140 public <T> T[] getSpans(int start, int end, Class<T> type) { 141 return ((Spanned) mSource).getSpans(mStart + start, mStart + end, type); 142 } 143 getSpanStart(Object tag)144 public int getSpanStart(Object tag) { 145 return ((Spanned) mSource).getSpanStart(tag) - mStart; 146 } 147 getSpanEnd(Object tag)148 public int getSpanEnd(Object tag) { 149 return ((Spanned) mSource).getSpanEnd(tag) - mStart; 150 } 151 getSpanFlags(Object tag)152 public int getSpanFlags(Object tag) { 153 return ((Spanned) mSource).getSpanFlags(tag); 154 } 155 nextSpanTransition(int start, int limit, Class type)156 public int nextSpanTransition(int start, int limit, Class type) { 157 return ((Spanned) mSource).nextSpanTransition(mStart + start, mStart + limit, type) 158 - mStart; 159 } 160 } 161 } 162 163 /** 164 * This filter will constrain edits not to make the length of the text 165 * greater than the specified length. 166 */ 167 public static class LengthFilter implements InputFilter { 168 @UnsupportedAppUsage 169 private final int mMax; 170 LengthFilter(int max)171 public LengthFilter(int max) { 172 mMax = max; 173 } 174 filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)175 public CharSequence filter(CharSequence source, int start, int end, Spanned dest, 176 int dstart, int dend) { 177 int keep = mMax - (dest.length() - (dend - dstart)); 178 if (keep <= 0) { 179 return ""; 180 } else if (keep >= end - start) { 181 return null; // keep original 182 } else { 183 keep += start; 184 if (Character.isHighSurrogate(source.charAt(keep - 1))) { 185 --keep; 186 if (keep == start) { 187 return ""; 188 } 189 } 190 return source.subSequence(start, keep); 191 } 192 } 193 194 /** 195 * @return the maximum length enforced by this input filter 196 */ getMax()197 public int getMax() { 198 return mMax; 199 } 200 } 201 } 202