• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &hellip; dend</code> of <code>dest</code>
36      * with the new text from the range <code>start &hellip; 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