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 = new char[][] { 53 new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }, 54 new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' }, 55 new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' }, 56 new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' }, 57 }; 58 59 /** 60 * Allocates a DigitsKeyListener that accepts the digits 0 through 9. 61 */ 62 public DigitsKeyListener() { 63 this(false, false); 64 } 65 66 /** 67 * Allocates a DigitsKeyListener that accepts the digits 0 through 9, 68 * plus the minus sign (only at the beginning) and/or decimal point 69 * (only one per field) if specified. 70 */ 71 public DigitsKeyListener(boolean sign, boolean decimal) { 72 mSign = sign; 73 mDecimal = decimal; 74 75 int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); 76 mAccepted = CHARACTERS[kind]; 77 } 78 79 /** 80 * Returns a DigitsKeyListener that accepts the digits 0 through 9. 81 */ 82 public static DigitsKeyListener getInstance() { 83 return getInstance(false, false); 84 } 85 86 /** 87 * Returns a DigitsKeyListener that accepts the digits 0 through 9, 88 * plus the minus sign (only at the beginning) and/or decimal point 89 * (only one per field) if specified. 90 */ 91 public static DigitsKeyListener getInstance(boolean sign, boolean decimal) { 92 int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); 93 94 if (sInstance[kind] != null) 95 return sInstance[kind]; 96 97 sInstance[kind] = new DigitsKeyListener(sign, decimal); 98 return sInstance[kind]; 99 } 100 101 /** 102 * Returns a DigitsKeyListener that accepts only the characters 103 * that appear in the specified String. Note that not all characters 104 * may be available on every keyboard. 105 */ 106 public static DigitsKeyListener getInstance(String accepted) { 107 // TODO: do we need a cache of these to avoid allocating? 108 109 DigitsKeyListener dim = new DigitsKeyListener(); 110 111 dim.mAccepted = new char[accepted.length()]; 112 accepted.getChars(0, accepted.length(), dim.mAccepted, 0); 113 114 return dim; 115 } 116 117 public int getInputType() { 118 int contentType = InputType.TYPE_CLASS_NUMBER; 119 if (mSign) { 120 contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED; 121 } 122 if (mDecimal) { 123 contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL; 124 } 125 return contentType; 126 } 127 128 @Override 129 public CharSequence filter(CharSequence source, int start, int end, 130 Spanned dest, int dstart, int dend) { 131 CharSequence out = super.filter(source, start, end, dest, dstart, dend); 132 133 if (mSign == false && mDecimal == false) { 134 return out; 135 } 136 137 if (out != null) { 138 source = out; 139 start = 0; 140 end = out.length(); 141 } 142 143 int sign = -1; 144 int decimal = -1; 145 int dlen = dest.length(); 146 147 /* 148 * Find out if the existing text has '-' or '.' characters. 149 */ 150 151 for (int i = 0; i < dstart; i++) { 152 char c = dest.charAt(i); 153 154 if (c == '-') { 155 sign = i; 156 } else if (c == '.') { 157 decimal = i; 158 } 159 } 160 for (int i = dend; i < dlen; i++) { 161 char c = dest.charAt(i); 162 163 if (c == '-') { 164 return ""; // Nothing can be inserted in front of a '-'. 165 } else if (c == '.') { 166 decimal = i; 167 } 168 } 169 170 /* 171 * If it does, we must strip them out from the source. 172 * In addition, '-' must be the very first character, 173 * and nothing can be inserted before an existing '-'. 174 * Go in reverse order so the offsets are stable. 175 */ 176 177 SpannableStringBuilder stripped = null; 178 179 for (int i = end - 1; i >= start; i--) { 180 char c = source.charAt(i); 181 boolean strip = false; 182 183 if (c == '-') { 184 if (i != start || dstart != 0) { 185 strip = true; 186 } else if (sign >= 0) { 187 strip = true; 188 } else { 189 sign = i; 190 } 191 } else if (c == '.') { 192 if (decimal >= 0) { 193 strip = true; 194 } else { 195 decimal = i; 196 } 197 } 198 199 if (strip) { 200 if (end == start + 1) { 201 return ""; // Only one character, and it was stripped. 202 } 203 204 if (stripped == null) { 205 stripped = new SpannableStringBuilder(source, start, end); 206 } 207 208 stripped.delete(i - start, i + 1 - start); 209 } 210 } 211 212 if (stripped != null) { 213 return stripped; 214 } else if (out != null) { 215 return out; 216 } else { 217 return null; 218 } 219 } 220 221 private static DigitsKeyListener[] sInstance = new DigitsKeyListener[4]; 222} 223