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