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