• 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.text.InputType;
20 import android.text.Spanned;
21 import android.text.SpannableStringBuilder;
22 import 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  */
32 public 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
getAcceptedChars()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 
isSignChar(final char c)59     private static boolean isSignChar(final char c) {
60         return c == '-' || c == '+';
61     }
62 
63     // TODO: Needs internationalization
isDecimalPointChar(final char c)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      */
DigitsKeyListener()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      */
DigitsKeyListener(boolean sign, boolean decimal)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      */
getInstance()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      */
getInstance(boolean sign, boolean decimal)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      */
getInstance(String accepted)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 
getInputType()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
filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)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