• 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.method;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.icu.text.DecimalFormatSymbols;
22 import android.text.Editable;
23 import android.text.InputFilter;
24 import android.text.Selection;
25 import android.text.Spannable;
26 import android.text.SpannableStringBuilder;
27 import android.text.Spanned;
28 import android.text.format.DateFormat;
29 import android.view.KeyEvent;
30 import android.view.View;
31 
32 import libcore.icu.LocaleData;
33 
34 import java.util.Collection;
35 import java.util.Locale;
36 
37 /**
38  * For numeric text entry
39  * <p></p>
40  * As for all implementations of {@link KeyListener}, this class is only concerned
41  * with hardware keyboards.  Software input methods have no obligation to trigger
42  * the methods in this class.
43  */
44 public abstract class NumberKeyListener extends BaseKeyListener
45     implements InputFilter
46 {
47     /**
48      * You can say which characters you can accept.
49      */
50     @NonNull
getAcceptedChars()51     protected abstract char[] getAcceptedChars();
52 
lookup(KeyEvent event, Spannable content)53     protected int lookup(KeyEvent event, Spannable content) {
54         return event.getMatch(getAcceptedChars(), getMetaState(content, event));
55     }
56 
filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)57     public CharSequence filter(CharSequence source, int start, int end,
58                                Spanned dest, int dstart, int dend) {
59         char[] accept = getAcceptedChars();
60         boolean filter = false;
61 
62         int i;
63         for (i = start; i < end; i++) {
64             if (!ok(accept, source.charAt(i))) {
65                 break;
66             }
67         }
68 
69         if (i == end) {
70             // It was all OK.
71             return null;
72         }
73 
74         if (end - start == 1) {
75             // It was not OK, and there is only one char, so nothing remains.
76             return "";
77         }
78 
79         SpannableStringBuilder filtered =
80             new SpannableStringBuilder(source, start, end);
81         i -= start;
82         end -= start;
83 
84         int len = end - start;
85         // Only count down to i because the chars before that were all OK.
86         for (int j = end - 1; j >= i; j--) {
87             if (!ok(accept, source.charAt(j))) {
88                 filtered.delete(j, j + 1);
89             }
90         }
91 
92         return filtered;
93     }
94 
ok(char[] accept, char c)95     protected static boolean ok(char[] accept, char c) {
96         for (int i = accept.length - 1; i >= 0; i--) {
97             if (accept[i] == c) {
98                 return true;
99             }
100         }
101 
102         return false;
103     }
104 
105     @Override
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)106     public boolean onKeyDown(View view, Editable content,
107                              int keyCode, KeyEvent event) {
108         int selStart, selEnd;
109 
110         {
111             int a = Selection.getSelectionStart(content);
112             int b = Selection.getSelectionEnd(content);
113 
114             selStart = Math.min(a, b);
115             selEnd = Math.max(a, b);
116         }
117 
118         if (selStart < 0 || selEnd < 0) {
119             selStart = selEnd = 0;
120             Selection.setSelection(content, 0);
121         }
122 
123         int i = event != null ? lookup(event, content) : 0;
124         int repeatCount = event != null ? event.getRepeatCount() : 0;
125         if (repeatCount == 0) {
126             if (i != 0) {
127                 if (selStart != selEnd) {
128                     Selection.setSelection(content, selEnd);
129                 }
130 
131                 content.replace(selStart, selEnd, String.valueOf((char) i));
132 
133                 adjustMetaAfterKeypress(content);
134                 return true;
135             }
136         } else if (i == '0' && repeatCount == 1) {
137             // Pretty hackish, it replaces the 0 with the +
138 
139             if (selStart == selEnd && selEnd > 0 &&
140                     content.charAt(selStart - 1) == '0') {
141                 content.replace(selStart - 1, selEnd, String.valueOf('+'));
142                 adjustMetaAfterKeypress(content);
143                 return true;
144             }
145         }
146 
147         adjustMetaAfterKeypress(content);
148         return super.onKeyDown(view, content, keyCode, event);
149     }
150 
151     /* package */
152     @Nullable
addDigits(@onNull Collection<Character> collection, @Nullable Locale locale)153     static boolean addDigits(@NonNull Collection<Character> collection, @Nullable Locale locale) {
154         if (locale == null) {
155             return false;
156         }
157         final String[] digits = DecimalFormatSymbols.getInstance(locale).getDigitStrings();
158         for (int i = 0; i < 10; i++) {
159             if (digits[i].length() > 1) { // multi-codeunit digits. Not supported.
160                 return false;
161             }
162             collection.add(Character.valueOf(digits[i].charAt(0)));
163         }
164         return true;
165     }
166 
167     // From http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
168     private static final String DATE_TIME_FORMAT_SYMBOLS =
169             "GyYuUrQqMLlwWdDFgEecabBhHKkjJCmsSAzZOvVXx";
170     private static final char SINGLE_QUOTE = '\'';
171 
172     /* package */
addFormatCharsFromSkeleton( @onNull Collection<Character> collection, @Nullable Locale locale, @NonNull String skeleton, @NonNull String symbolsToIgnore)173     static boolean addFormatCharsFromSkeleton(
174             @NonNull Collection<Character> collection, @Nullable Locale locale,
175             @NonNull String skeleton, @NonNull String symbolsToIgnore) {
176         if (locale == null) {
177             return false;
178         }
179         final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
180         boolean outsideQuotes = true;
181         for (int i = 0; i < pattern.length(); i++) {
182             final char ch = pattern.charAt(i);
183             if (Character.isSurrogate(ch)) { // characters outside BMP are not supported.
184                 return false;
185             } else if (ch == SINGLE_QUOTE) {
186                 outsideQuotes = !outsideQuotes;
187                 // Single quote characters should be considered if and only if they follow
188                 // another single quote.
189                 if (i == 0 || pattern.charAt(i - 1) != SINGLE_QUOTE) {
190                     continue;
191                 }
192             }
193 
194             if (outsideQuotes) {
195                 if (symbolsToIgnore.indexOf(ch) != -1) {
196                     // Skip expected pattern characters.
197                     continue;
198                 } else if (DATE_TIME_FORMAT_SYMBOLS.indexOf(ch) != -1) {
199                     // An unexpected symbols is seen. We've failed.
200                     return false;
201                 }
202             }
203             // If we are here, we are either inside quotes, or we have seen a non-pattern
204             // character outside quotes. So ch is a valid character in a date.
205             collection.add(Character.valueOf(ch));
206         }
207         return true;
208     }
209 
210     /* package */
addFormatCharsFromSkeletons( @onNull Collection<Character> collection, @Nullable Locale locale, @NonNull String[] skeletons, @NonNull String symbolsToIgnore)211     static boolean addFormatCharsFromSkeletons(
212             @NonNull Collection<Character> collection, @Nullable Locale locale,
213             @NonNull String[] skeletons, @NonNull String symbolsToIgnore) {
214         for (int i = 0; i < skeletons.length; i++) {
215             final boolean success = addFormatCharsFromSkeleton(
216                     collection, locale, skeletons[i], symbolsToIgnore);
217             if (!success) {
218                 return false;
219             }
220         }
221         return true;
222     }
223 
224 
225     /* package */
addAmPmChars(@onNull Collection<Character> collection, @Nullable Locale locale)226     static boolean addAmPmChars(@NonNull Collection<Character> collection,
227                                 @Nullable Locale locale) {
228         if (locale == null) {
229             return false;
230         }
231         final String[] amPm = LocaleData.get(locale).amPm;
232         for (int i = 0; i < amPm.length; i++) {
233             for (int j = 0; j < amPm[i].length(); j++) {
234                 final char ch = amPm[i].charAt(j);
235                 if (Character.isBmpCodePoint(ch)) {
236                     collection.add(Character.valueOf(ch));
237                 } else {  // We don't support non-BMP characters.
238                     return false;
239                 }
240             }
241         }
242         return true;
243     }
244 
245     /* package */
246     @NonNull
collectionToArray(@onNull Collection<Character> chars)247     static char[] collectionToArray(@NonNull Collection<Character> chars) {
248         final char[] result = new char[chars.size()];
249         int i = 0;
250         for (Character ch : chars) {
251             result[i++] = ch;
252         }
253         return result;
254     }
255 }
256