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 17package android.text.method; 18 19import android.text.InputType; 20import android.text.Spanned; 21import android.text.SpannableStringBuilder; 22import android.view.KeyEvent; 23 24 25/** 26 * For digits-only text entry 27 * <p></p> 28 * As for all implementations of {@link KeyListener}, this class is only concerned 29 * with hardware keyboards. Software input methods have no obligation to trigger 30 * the methods in this class. 31 */ 32public class DigitsKeyListener extends NumberKeyListener 33{ 34 private char[] mAccepted; 35 private boolean mSign; 36 private boolean mDecimal; 37 38 private static final int SIGN = 1; 39 private static final int DECIMAL = 2; 40 41 @Override 42 protected char[] getAcceptedChars() { 43 return mAccepted; 44 } 45 46 /** 47 * The characters that are used. 48 * 49 * @see KeyEvent#getMatch 50 * @see #getAcceptedChars 51 */ 52 private static final char[][] CHARACTERS = { 53 { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }, 54 { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' }, 55 { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' }, 56 { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' }, 57 }; 58 59 private static boolean isSignChar(final char c) { 60 return c == '-' || c == '+'; 61 } 62 63 // TODO: Needs internationalization 64 private static boolean isDecimalPointChar(final char c) { 65 return c == '.'; 66 } 67 68 /** 69 * Allocates a DigitsKeyListener that accepts the digits 0 through 9. 70 */ 71 public DigitsKeyListener() { 72 this(false, false); 73 } 74 75 /** 76 * Allocates a DigitsKeyListener that accepts the digits 0 through 9, 77 * plus the minus sign (only at the beginning) and/or decimal point 78 * (only one per field) if specified. 79 */ 80 public DigitsKeyListener(boolean sign, boolean decimal) { 81 mSign = sign; 82 mDecimal = decimal; 83 84 int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); 85 mAccepted = CHARACTERS[kind]; 86 } 87 88 /** 89 * Returns a DigitsKeyListener that accepts the digits 0 through 9. 90 */ 91 public static DigitsKeyListener getInstance() { 92 return getInstance(false, false); 93 } 94 95 /** 96 * Returns a DigitsKeyListener that accepts the digits 0 through 9, 97 * plus the minus sign (only at the beginning) and/or decimal point 98 * (only one per field) if specified. 99 */ 100 public static DigitsKeyListener getInstance(boolean sign, boolean decimal) { 101 int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); 102 103 if (sInstance[kind] != null) 104 return sInstance[kind]; 105 106 sInstance[kind] = new DigitsKeyListener(sign, decimal); 107 return sInstance[kind]; 108 } 109 110 /** 111 * Returns a DigitsKeyListener that accepts only the characters 112 * that appear in the specified String. Note that not all characters 113 * may be available on every keyboard. 114 */ 115 public static DigitsKeyListener getInstance(String accepted) { 116 // TODO: do we need a cache of these to avoid allocating? 117 118 DigitsKeyListener dim = new DigitsKeyListener(); 119 120 dim.mAccepted = new char[accepted.length()]; 121 accepted.getChars(0, accepted.length(), dim.mAccepted, 0); 122 123 return dim; 124 } 125 126 public int getInputType() { 127 int contentType = InputType.TYPE_CLASS_NUMBER; 128 if (mSign) { 129 contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED; 130 } 131 if (mDecimal) { 132 contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL; 133 } 134 return contentType; 135 } 136 137 @Override 138 public CharSequence filter(CharSequence source, int start, int end, 139 Spanned dest, int dstart, int dend) { 140 CharSequence out = super.filter(source, start, end, dest, dstart, dend); 141 142 if (mSign == false && mDecimal == false) { 143 return out; 144 } 145 146 if (out != null) { 147 source = out; 148 start = 0; 149 end = out.length(); 150 } 151 152 int sign = -1; 153 int decimal = -1; 154 int dlen = dest.length(); 155 156 /* 157 * Find out if the existing text has a sign or decimal point characters. 158 */ 159 160 for (int i = 0; i < dstart; i++) { 161 char c = dest.charAt(i); 162 163 if (isSignChar(c)) { 164 sign = i; 165 } else if (isDecimalPointChar(c)) { 166 decimal = i; 167 } 168 } 169 for (int i = dend; i < dlen; i++) { 170 char c = dest.charAt(i); 171 172 if (isSignChar(c)) { 173 return ""; // Nothing can be inserted in front of a sign character. 174 } else if (isDecimalPointChar(c)) { 175 decimal = i; 176 } 177 } 178 179 /* 180 * If it does, we must strip them out from the source. 181 * In addition, a sign character must be the very first character, 182 * and nothing can be inserted before an existing sign character. 183 * Go in reverse order so the offsets are stable. 184 */ 185 186 SpannableStringBuilder stripped = null; 187 188 for (int i = end - 1; i >= start; i--) { 189 char c = source.charAt(i); 190 boolean strip = false; 191 192 if (isSignChar(c)) { 193 if (i != start || dstart != 0) { 194 strip = true; 195 } else if (sign >= 0) { 196 strip = true; 197 } else { 198 sign = i; 199 } 200 } else if (isDecimalPointChar(c)) { 201 if (decimal >= 0) { 202 strip = true; 203 } else { 204 decimal = i; 205 } 206 } 207 208 if (strip) { 209 if (end == start + 1) { 210 return ""; // Only one character, and it was stripped. 211 } 212 213 if (stripped == null) { 214 stripped = new SpannableStringBuilder(source, start, end); 215 } 216 217 stripped.delete(i - start, i + 1 - start); 218 } 219 } 220 221 if (stripped != null) { 222 return stripped; 223 } else if (out != null) { 224 return out; 225 } else { 226 return null; 227 } 228 } 229 230 private static DigitsKeyListener[] sInstance = new DigitsKeyListener[4]; 231} 232