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.widget;
18
19import android.content.Context;
20import android.view.KeyEvent;
21import android.text.Editable;
22import android.text.InputFilter;
23import android.text.Selection;
24import android.text.Spannable;
25import android.text.Spanned;
26import android.text.TextWatcher;
27import android.text.method.DialerKeyListener;
28import android.text.method.KeyListener;
29import android.text.method.TextKeyListener;
30import android.util.AttributeSet;
31import android.util.Log;
32import android.view.KeyCharacterMap;
33import android.view.View;
34import android.graphics.Rect;
35
36
37
38public class DialerFilter extends RelativeLayout
39{
40    public DialerFilter(Context context) {
41        super(context);
42    }
43
44    public DialerFilter(Context context, AttributeSet attrs) {
45        super(context, attrs);
46    }
47
48    @Override
49    protected void onFinishInflate() {
50        super.onFinishInflate();
51
52        // Setup the filter view
53        mInputFilters = new InputFilter[] { new InputFilter.AllCaps() };
54
55        mHint = (EditText) findViewById(com.android.internal.R.id.hint);
56        if (mHint == null) {
57            throw new IllegalStateException("DialerFilter must have a child EditText named hint");
58        }
59        mHint.setFilters(mInputFilters);
60
61        mLetters = mHint;
62        mLetters.setKeyListener(TextKeyListener.getInstance());
63        mLetters.setMovementMethod(null);
64        mLetters.setFocusable(false);
65
66        // Setup the digits view
67        mPrimary = (EditText) findViewById(com.android.internal.R.id.primary);
68        if (mPrimary == null) {
69            throw new IllegalStateException("DialerFilter must have a child EditText named primary");
70        }
71        mPrimary.setFilters(mInputFilters);
72
73        mDigits = mPrimary;
74        mDigits.setKeyListener(DialerKeyListener.getInstance());
75        mDigits.setMovementMethod(null);
76        mDigits.setFocusable(false);
77
78        // Look for an icon
79        mIcon = (ImageView) findViewById(com.android.internal.R.id.icon);
80
81        // Setup focus & highlight for this view
82        setFocusable(true);
83
84        // XXX Force the mode to QWERTY for now, since 12-key isn't supported
85        mIsQwerty = true;
86        setMode(DIGITS_AND_LETTERS);
87    }
88
89    /**
90     * Only show the icon view when focused, if there is one.
91     */
92    @Override
93    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
94        super.onFocusChanged(focused, direction, previouslyFocusedRect);
95
96        if (mIcon != null) {
97            mIcon.setVisibility(focused ? View.VISIBLE : View.GONE);
98        }
99    }
100
101
102    public boolean isQwertyKeyboard() {
103        return mIsQwerty;
104    }
105
106    @Override
107    public boolean onKeyDown(int keyCode, KeyEvent event) {
108        boolean handled = false;
109
110        switch (keyCode) {
111            case KeyEvent.KEYCODE_DPAD_UP:
112            case KeyEvent.KEYCODE_DPAD_DOWN:
113            case KeyEvent.KEYCODE_DPAD_LEFT:
114            case KeyEvent.KEYCODE_DPAD_RIGHT:
115            case KeyEvent.KEYCODE_ENTER:
116            case KeyEvent.KEYCODE_DPAD_CENTER:
117                break;
118
119            case KeyEvent.KEYCODE_DEL:
120                switch (mMode) {
121                    case DIGITS_AND_LETTERS:
122                        handled = mDigits.onKeyDown(keyCode, event);
123                        handled &= mLetters.onKeyDown(keyCode, event);
124                        break;
125
126                    case DIGITS_AND_LETTERS_NO_DIGITS:
127                        handled = mLetters.onKeyDown(keyCode, event);
128                        if (mLetters.getText().length() == mDigits.getText().length()) {
129                            setMode(DIGITS_AND_LETTERS);
130                        }
131                        break;
132
133                    case DIGITS_AND_LETTERS_NO_LETTERS:
134                        if (mDigits.getText().length() == mLetters.getText().length()) {
135                            mLetters.onKeyDown(keyCode, event);
136                            setMode(DIGITS_AND_LETTERS);
137                        }
138                        handled = mDigits.onKeyDown(keyCode, event);
139                        break;
140
141                    case DIGITS_ONLY:
142                        handled = mDigits.onKeyDown(keyCode, event);
143                        break;
144
145                    case LETTERS_ONLY:
146                        handled = mLetters.onKeyDown(keyCode, event);
147                        break;
148                }
149                break;
150
151            default:
152                //mIsQwerty = msg.getKeyIsQwertyKeyboard();
153
154                switch (mMode) {
155                    case DIGITS_AND_LETTERS:
156                        handled = mLetters.onKeyDown(keyCode, event);
157
158                        // pass this throw so the shift state is correct (for example,
159                        // on a standard QWERTY keyboard, * and 8 are on the same key)
160                        if (KeyEvent.isModifierKey(keyCode)) {
161                            mDigits.onKeyDown(keyCode, event);
162                            handled = true;
163                            break;
164                        }
165
166                        // Only check to see if the digit is valid if the key is a printing key
167                        // in the TextKeyListener. This prevents us from hiding the digits
168                        // line when keys like UP and DOWN are hit.
169                        // XXX note that KEYCODE_TAB is special-cased here for
170                        // devices that share tab and 0 on a single key.
171                        boolean isPrint = event.isPrintingKey();
172                        if (isPrint || keyCode == KeyEvent.KEYCODE_SPACE
173                                || keyCode == KeyEvent.KEYCODE_TAB) {
174                            char c = event.getMatch(DialerKeyListener.CHARACTERS);
175                            if (c != 0) {
176                                handled &= mDigits.onKeyDown(keyCode, event);
177                            } else {
178                                setMode(DIGITS_AND_LETTERS_NO_DIGITS);
179                            }
180                        }
181                        break;
182
183                    case DIGITS_AND_LETTERS_NO_LETTERS:
184                    case DIGITS_ONLY:
185                        handled = mDigits.onKeyDown(keyCode, event);
186                        break;
187
188                    case DIGITS_AND_LETTERS_NO_DIGITS:
189                    case LETTERS_ONLY:
190                        handled = mLetters.onKeyDown(keyCode, event);
191                        break;
192                }
193        }
194
195        if (!handled) {
196            return super.onKeyDown(keyCode, event);
197        } else {
198            return true;
199        }
200    }
201
202    @Override
203    public boolean onKeyUp(int keyCode, KeyEvent event) {
204        boolean a = mLetters.onKeyUp(keyCode, event);
205        boolean b = mDigits.onKeyUp(keyCode, event);
206        return a || b;
207    }
208
209    public int getMode() {
210        return mMode;
211    }
212
213    /**
214     * Change the mode of the widget.
215     *
216     * @param newMode The mode to switch to.
217     */
218    public void setMode(int newMode) {
219        switch (newMode) {
220            case DIGITS_AND_LETTERS:
221                makeDigitsPrimary();
222                mLetters.setVisibility(View.VISIBLE);
223                mDigits.setVisibility(View.VISIBLE);
224                break;
225
226            case DIGITS_ONLY:
227                makeDigitsPrimary();
228                mLetters.setVisibility(View.GONE);
229                mDigits.setVisibility(View.VISIBLE);
230                break;
231
232            case LETTERS_ONLY:
233                makeLettersPrimary();
234                mLetters.setVisibility(View.VISIBLE);
235                mDigits.setVisibility(View.GONE);
236                break;
237
238            case DIGITS_AND_LETTERS_NO_LETTERS:
239                makeDigitsPrimary();
240                mLetters.setVisibility(View.INVISIBLE);
241                mDigits.setVisibility(View.VISIBLE);
242                break;
243
244            case DIGITS_AND_LETTERS_NO_DIGITS:
245                makeLettersPrimary();
246                mLetters.setVisibility(View.VISIBLE);
247                mDigits.setVisibility(View.INVISIBLE);
248                break;
249
250        }
251        int oldMode = mMode;
252        mMode = newMode;
253        onModeChange(oldMode, newMode);
254    }
255
256    private void makeLettersPrimary() {
257        if (mPrimary == mDigits) {
258            swapPrimaryAndHint(true);
259        }
260    }
261
262    private void makeDigitsPrimary() {
263        if (mPrimary == mLetters) {
264            swapPrimaryAndHint(false);
265        }
266    }
267
268    private void swapPrimaryAndHint(boolean makeLettersPrimary) {
269        Editable lettersText = mLetters.getText();
270        Editable digitsText = mDigits.getText();
271        KeyListener lettersInput = mLetters.getKeyListener();
272        KeyListener digitsInput = mDigits.getKeyListener();
273
274        if (makeLettersPrimary) {
275            mLetters = mPrimary;
276            mDigits = mHint;
277        } else {
278            mLetters = mHint;
279            mDigits = mPrimary;
280        }
281
282        mLetters.setKeyListener(lettersInput);
283        mLetters.setText(lettersText);
284        lettersText = mLetters.getText();
285        Selection.setSelection(lettersText, lettersText.length());
286
287        mDigits.setKeyListener(digitsInput);
288        mDigits.setText(digitsText);
289        digitsText = mDigits.getText();
290        Selection.setSelection(digitsText, digitsText.length());
291
292        // Reset the filters
293        mPrimary.setFilters(mInputFilters);
294        mHint.setFilters(mInputFilters);
295    }
296
297
298    public CharSequence getLetters() {
299        if (mLetters.getVisibility() == View.VISIBLE) {
300            return mLetters.getText();
301        } else {
302            return "";
303        }
304    }
305
306    public CharSequence getDigits() {
307        if (mDigits.getVisibility() == View.VISIBLE) {
308            return mDigits.getText();
309        } else {
310            return "";
311        }
312    }
313
314    public CharSequence getFilterText() {
315        if (mMode != DIGITS_ONLY) {
316            return getLetters();
317        } else {
318            return getDigits();
319        }
320    }
321
322    public void append(String text) {
323        switch (mMode) {
324            case DIGITS_AND_LETTERS:
325                mDigits.getText().append(text);
326                mLetters.getText().append(text);
327                break;
328
329            case DIGITS_AND_LETTERS_NO_LETTERS:
330            case DIGITS_ONLY:
331                mDigits.getText().append(text);
332                break;
333
334            case DIGITS_AND_LETTERS_NO_DIGITS:
335            case LETTERS_ONLY:
336                mLetters.getText().append(text);
337                break;
338        }
339    }
340
341    /**
342     * Clears both the digits and the filter text.
343     */
344    public void clearText() {
345        Editable text;
346
347        text = mLetters.getText();
348        text.clear();
349
350        text = mDigits.getText();
351        text.clear();
352
353        // Reset the mode based on the hardware type
354        if (mIsQwerty) {
355            setMode(DIGITS_AND_LETTERS);
356        } else {
357            setMode(DIGITS_ONLY);
358        }
359    }
360
361    public void setLettersWatcher(TextWatcher watcher) {
362        CharSequence text = mLetters.getText();
363        Spannable span = (Spannable)text;
364        span.setSpan(watcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
365    }
366
367    public void setDigitsWatcher(TextWatcher watcher) {
368        CharSequence text = mDigits.getText();
369        Spannable span = (Spannable)text;
370        span.setSpan(watcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
371    }
372
373    public void setFilterWatcher(TextWatcher watcher) {
374        if (mMode != DIGITS_ONLY) {
375            setLettersWatcher(watcher);
376        } else {
377            setDigitsWatcher(watcher);
378        }
379    }
380
381    public void removeFilterWatcher(TextWatcher watcher) {
382        Spannable text;
383        if (mMode != DIGITS_ONLY) {
384            text = mLetters.getText();
385        } else {
386            text = mDigits.getText();
387        }
388        text.removeSpan(watcher);
389    }
390
391    /**
392     * Called right after the mode changes to give subclasses the option to
393     * restyle, etc.
394     */
395    protected void onModeChange(int oldMode, int newMode) {
396    }
397
398    /** This mode has both lines */
399    public static final int DIGITS_AND_LETTERS = 1;
400    /** This mode is when after starting in {@link #DIGITS_AND_LETTERS} mode the filter
401     *  has removed all possibility of the digits matching, leaving only the letters line */
402    public static final int DIGITS_AND_LETTERS_NO_DIGITS = 2;
403    /** This mode is when after starting in {@link #DIGITS_AND_LETTERS} mode the filter
404     *  has removed all possibility of the letters matching, leaving only the digits line */
405    public static final int DIGITS_AND_LETTERS_NO_LETTERS = 3;
406    /** This mode has only the digits line */
407    public static final int DIGITS_ONLY = 4;
408    /** This mode has only the letters line */
409    public static final int LETTERS_ONLY = 5;
410
411    EditText mLetters;
412    EditText mDigits;
413    EditText mPrimary;
414    EditText mHint;
415    InputFilter mInputFilters[];
416    ImageView mIcon;
417    int mMode;
418    private boolean mIsQwerty;
419}
420