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