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