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