blob: a9a7b2f2fa7a8f38eb6af505b2cafe9e8c8abb53 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.text;
18
Roozbeh Pournader4e5e15a2017-08-01 19:30:28 -070019import android.annotation.NonNull;
Mathew Inwoodefeab842018-08-14 15:21:30 +010020import android.annotation.UnsupportedAppUsage;
Roozbeh Pournader4e5e15a2017-08-01 19:30:28 -070021
22import com.android.internal.util.Preconditions;
Roozbeh Pournader205a9932017-06-08 00:23:42 -070023
24import java.util.Locale;
25
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026/**
27 * InputFilters can be attached to {@link Editable}s to constrain the
28 * changes that can be made to them.
29 */
30public interface InputFilter
31{
32 /**
33 * This method is called when the buffer is going to replace the
34 * range <code>dstart &hellip; dend</code> of <code>dest</code>
35 * with the new text from the range <code>start &hellip; 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.
Roozbeh Pournader205a9932017-06-08 00:23:42 -070043 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044 * Note: If <var>source</var> is an instance of {@link Spanned} or
Roozbeh Pournader205a9932017-06-08 00:23:42 -070045 * {@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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 */
50 public CharSequence filter(CharSequence source, int start, int end,
51 Spanned dest, int dstart, int dend);
52
53 /**
Roozbeh Pournader205a9932017-06-08 00:23:42 -070054 * 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.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058 */
59 public static class AllCaps implements InputFilter {
Roozbeh Pournader205a9932017-06-08 00:23:42 -070060 private final Locale mLocale;
61
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 */
Roozbeh Pournader4e5e15a2017-08-01 19:30:28 -070070 public AllCaps(@NonNull Locale locale) {
71 Preconditions.checkNotNull(locale);
Roozbeh Pournader205a9932017-06-08 00:23:42 -070072 mLocale = locale;
73 }
74
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 public CharSequence filter(CharSequence source, int start, int end,
76 Spanned dest, int dstart, int dend) {
Roozbeh Pournader205a9932017-06-08 00:23:42 -070077 final CharSequence wrapper = new CharSequenceWrapper(source, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078
Roozbeh Pournader205a9932017-06-08 00:23:42 -070079 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 }
89 }
Roozbeh Pournader205a9932017-06-08 00:23:42 -070090 if (!lowerOrTitleFound) {
91 return null; // keep original
92 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093
Roozbeh Pournader205a9932017-06-08 00:23:42 -070094 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
111 CharSequenceWrapper(CharSequence source, int start, int end) {
112 mSource = source;
113 mStart = start;
114 mEnd = end;
115 mLength = end - start;
116 }
117
118 public int length() {
119 return mLength;
120 }
121
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
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
136 public String toString() {
137 return mSource.subSequence(mStart, mEnd).toString();
138 }
139
140 public <T> T[] getSpans(int start, int end, Class<T> type) {
141 return ((Spanned) mSource).getSpans(mStart + start, mStart + end, type);
142 }
143
144 public int getSpanStart(Object tag) {
145 return ((Spanned) mSource).getSpanStart(tag) - mStart;
146 }
147
148 public int getSpanEnd(Object tag) {
149 return ((Spanned) mSource).getSpanEnd(tag) - mStart;
150 }
151
152 public int getSpanFlags(Object tag) {
153 return ((Spanned) mSource).getSpanFlags(tag);
154 }
155
156 public int nextSpanTransition(int start, int limit, Class type) {
157 return ((Spanned) mSource).nextSpanTransition(mStart + start, mStart + limit, type)
158 - mStart;
159 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 }
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 {
Mathew Inwoodefeab842018-08-14 15:21:30 +0100168 @UnsupportedAppUsage
Alan Viverette029942f2014-08-12 14:55:56 -0700169 private final int mMax;
170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 public LengthFilter(int max) {
172 mMax = max;
173 }
174
Alan Viverette029942f2014-08-12 14:55:56 -0700175 public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
176 int dstart, int dend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 int keep = mMax - (dest.length() - (dend - dstart));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 if (keep <= 0) {
179 return "";
180 } else if (keep >= end - start) {
181 return null; // keep original
182 } else {
Ishibashi Takako2f0f4422010-11-04 08:43:33 +0100183 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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 }
192 }
193
Alan Viverette029942f2014-08-12 14:55:56 -0700194 /**
195 * @return the maximum length enforced by this input filter
196 */
197 public int getMax() {
198 return mMax;
199 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 }
201}