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