1/*
2 * Copyright (C) 2010 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.internal.widget;
18
19import java.util.Locale;
20import android.content.Context;
21import android.content.res.Resources;
22import android.content.res.XmlResourceParser;
23import android.graphics.drawable.Drawable;
24import android.inputmethodservice.Keyboard;
25import android.inputmethodservice.KeyboardView;
26import com.android.internal.R;
27
28/**
29 * A basic, embed-able keyboard designed for password entry. Allows entry of all Latin-1 characters.
30 *
31 * It has two modes: alpha and numeric. In alpha mode, it allows all Latin-1 characters and enables
32 * an additional keyboard with symbols.  In numeric mode, it shows a 12-key DTMF dialer-like
33 * keypad with alpha characters hints.
34 */
35public class PasswordEntryKeyboard extends Keyboard {
36    private static final int SHIFT_OFF = 0;
37    private static final int SHIFT_ON = 1;
38    private static final int SHIFT_LOCKED = 2;
39    public static final int KEYCODE_SPACE = ' ';
40
41    private Drawable mShiftIcon;
42    private Drawable mShiftLockIcon;
43
44    // These two arrays must be the same length
45    private Drawable[] mOldShiftIcons = { null, null };
46    private Key[] mShiftKeys = { null, null };
47
48    private Key mEnterKey;
49    private Key mF1Key;
50    private Key mSpaceKey;
51    private int mShiftState = SHIFT_OFF;
52
53    static int sSpacebarVerticalCorrection;
54
55    public PasswordEntryKeyboard(Context context, int xmlLayoutResId) {
56        this(context, xmlLayoutResId, 0);
57    }
58
59    public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int width, int height) {
60        this(context, xmlLayoutResId, 0, width, height);
61    }
62
63    public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int mode) {
64        super(context, xmlLayoutResId, mode);
65        init(context);
66    }
67
68    public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int mode,
69            int width, int height) {
70        super(context, xmlLayoutResId, mode, width, height);
71        init(context);
72    }
73
74    private void init(Context context) {
75        final Resources res = context.getResources();
76        mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift);
77        mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
78        sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
79                R.dimen.password_keyboard_spacebar_vertical_correction);
80    }
81
82    public PasswordEntryKeyboard(Context context, int layoutTemplateResId,
83            CharSequence characters, int columns, int horizontalPadding) {
84        super(context, layoutTemplateResId, characters, columns, horizontalPadding);
85    }
86
87    @Override
88    protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
89            XmlResourceParser parser) {
90        LatinKey key = new LatinKey(res, parent, x, y, parser);
91        final int code = key.codes[0];
92        if (code >=0 && code != '\n' && (code < 32 || code > 127)) {
93            // Log.w(TAG, "Key code for " + key.label + " is not latin-1");
94            key.label = " ";
95            key.setEnabled(false);
96        }
97        switch (key.codes[0]) {
98            case 10:
99                mEnterKey = key;
100                break;
101            case PasswordEntryKeyboardView.KEYCODE_F1:
102                mF1Key = key;
103                break;
104            case 32:
105                mSpaceKey = key;
106                break;
107        }
108        return key;
109    }
110
111    /**
112     * Allows enter key resources to be overridden
113     * @param res resources to grab given items from
114     * @param previewId preview drawable shown on enter key
115     * @param iconId normal drawable shown on enter key
116     * @param labelId string shown on enter key
117     */
118    void setEnterKeyResources(Resources res, int previewId, int iconId, int labelId) {
119        if (mEnterKey != null) {
120            // Reset some of the rarely used attributes.
121            mEnterKey.popupCharacters = null;
122            mEnterKey.popupResId = 0;
123            mEnterKey.text = null;
124
125            mEnterKey.iconPreview = res.getDrawable(previewId);
126            mEnterKey.icon = res.getDrawable(iconId);
127            mEnterKey.label = res.getText(labelId);
128
129            // Set the initial size of the preview icon
130            if (mEnterKey.iconPreview != null) {
131                mEnterKey.iconPreview.setBounds(0, 0,
132                        mEnterKey.iconPreview.getIntrinsicWidth(),
133                        mEnterKey.iconPreview.getIntrinsicHeight());
134            }
135        }
136    }
137
138    /**
139     * Allows shiftlock to be turned on.  See {@link #setShiftLocked(boolean)}
140     *
141     */
142    void enableShiftLock() {
143        int i = 0;
144        for (int index : getShiftKeyIndices()) {
145            if (index >= 0 && i < mShiftKeys.length) {
146                mShiftKeys[i] = getKeys().get(index);
147                if (mShiftKeys[i] instanceof LatinKey) {
148                    ((LatinKey)mShiftKeys[i]).enableShiftLock();
149                }
150                mOldShiftIcons[i] = mShiftKeys[i].icon;
151                i++;
152            }
153        }
154    }
155
156    /**
157     * Turn on shift lock. This turns on the LED for this key, if it has one.
158     * It should be followed by a call to {@link KeyboardView#invalidateKey(int)}
159     * or {@link KeyboardView#invalidateAllKeys()}
160     *
161     * @param shiftLocked
162     */
163    void setShiftLocked(boolean shiftLocked) {
164        for (Key shiftKey : mShiftKeys) {
165            if (shiftKey != null) {
166                shiftKey.on = shiftLocked;
167                shiftKey.icon = mShiftLockIcon;
168            }
169        }
170        mShiftState = shiftLocked ? SHIFT_LOCKED : SHIFT_ON;
171    }
172
173    /**
174     * Turn on shift mode. Sets shift mode and turns on icon for shift key.
175     * It should be followed by a call to {@link KeyboardView#invalidateKey(int)}
176     * or {@link KeyboardView#invalidateAllKeys()}
177     *
178     * @param shiftLocked
179     */
180    @Override
181    public boolean setShifted(boolean shiftState) {
182        boolean shiftChanged = false;
183        if (shiftState == false) {
184            shiftChanged = mShiftState != SHIFT_OFF;
185            mShiftState = SHIFT_OFF;
186        } else if (mShiftState == SHIFT_OFF) {
187            shiftChanged = mShiftState == SHIFT_OFF;
188            mShiftState = SHIFT_ON;
189        }
190        for (int i = 0; i < mShiftKeys.length; i++) {
191            if (mShiftKeys[i] != null) {
192                if (shiftState == false) {
193                    mShiftKeys[i].on = false;
194                    mShiftKeys[i].icon = mOldShiftIcons[i];
195                } else if (mShiftState == SHIFT_OFF) {
196                    mShiftKeys[i].on = false;
197                    mShiftKeys[i].icon = mShiftIcon;
198                }
199            } else {
200                // return super.setShifted(shiftState);
201            }
202        }
203        return shiftChanged;
204    }
205
206    /**
207     * Whether or not keyboard is shifted.
208     * @return true if keyboard state is shifted.
209     */
210    @Override
211    public boolean isShifted() {
212        if (mShiftKeys[0] != null) {
213            return mShiftState != SHIFT_OFF;
214        } else {
215            return super.isShifted();
216        }
217    }
218
219    static class LatinKey extends Keyboard.Key {
220        private boolean mShiftLockEnabled;
221        private boolean mEnabled = true;
222
223        public LatinKey(Resources res, Keyboard.Row parent, int x, int y,
224                XmlResourceParser parser) {
225            super(res, parent, x, y, parser);
226            if (popupCharacters != null && popupCharacters.length() == 0) {
227                // If there is a keyboard with no keys specified in popupCharacters
228                popupResId = 0;
229            }
230        }
231
232        void setEnabled(boolean enabled) {
233            mEnabled = enabled;
234        }
235
236        void enableShiftLock() {
237            mShiftLockEnabled = true;
238        }
239
240        @Override
241        public void onReleased(boolean inside) {
242            if (!mShiftLockEnabled) {
243                super.onReleased(inside);
244            } else {
245                pressed = !pressed;
246            }
247        }
248
249        /**
250         * Overriding this method so that we can reduce the target area for certain keys.
251         */
252        @Override
253        public boolean isInside(int x, int y) {
254            if (!mEnabled) {
255                return false;
256            }
257            final int code = codes[0];
258            if (code == KEYCODE_SHIFT || code == KEYCODE_DELETE) {
259                y -= height / 10;
260                if (code == KEYCODE_SHIFT) x += width / 6;
261                if (code == KEYCODE_DELETE) x -= width / 6;
262            } else if (code == KEYCODE_SPACE) {
263                y += PasswordEntryKeyboard.sSpacebarVerticalCorrection;
264            }
265            return super.isInside(x, y);
266        }
267    }
268}
269