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 17 package com.android.internal.widget; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.inputmethodservice.Keyboard; 22 import android.inputmethodservice.KeyboardView; 23 import android.inputmethodservice.KeyboardView.OnKeyboardActionListener; 24 import android.os.Handler; 25 import android.os.SystemClock; 26 import android.os.Vibrator; 27 import android.provider.Settings; 28 import android.util.Log; 29 import android.view.HapticFeedbackConstants; 30 import android.view.KeyCharacterMap; 31 import android.view.KeyEvent; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.ViewRootImpl; 35 import com.android.internal.R; 36 37 public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { 38 39 public static final int KEYBOARD_MODE_ALPHA = 0; 40 public static final int KEYBOARD_MODE_NUMERIC = 1; 41 private static final int KEYBOARD_STATE_NORMAL = 0; 42 private static final int KEYBOARD_STATE_SHIFTED = 1; 43 private static final int KEYBOARD_STATE_CAPSLOCK = 2; 44 private static final String TAG = "PasswordEntryKeyboardHelper"; 45 private int mKeyboardMode = KEYBOARD_MODE_ALPHA; 46 private int mKeyboardState = KEYBOARD_STATE_NORMAL; 47 private PasswordEntryKeyboard mQwertyKeyboard; 48 private PasswordEntryKeyboard mQwertyKeyboardShifted; 49 private PasswordEntryKeyboard mSymbolsKeyboard; 50 private PasswordEntryKeyboard mSymbolsKeyboardShifted; 51 private PasswordEntryKeyboard mNumericKeyboard; 52 private final Context mContext; 53 private final View mTargetView; 54 private final KeyboardView mKeyboardView; 55 private long[] mVibratePattern; 56 private boolean mEnableHaptics = false; 57 58 public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) { 59 this(context, keyboardView, targetView, true); 60 } 61 62 public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView, 63 boolean useFullScreenWidth) { 64 mContext = context; 65 mTargetView = targetView; 66 mKeyboardView = keyboardView; 67 if (useFullScreenWidth 68 || mKeyboardView.getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) { 69 createKeyboards(); 70 } else { 71 createKeyboardsWithSpecificSize(mKeyboardView.getLayoutParams().width, 72 mKeyboardView.getLayoutParams().height); 73 } 74 mKeyboardView.setOnKeyboardActionListener(this); 75 } 76 77 public void setEnableHaptics(boolean enabled) { 78 mEnableHaptics = enabled; 79 } 80 81 public boolean isAlpha() { 82 return mKeyboardMode == KEYBOARD_MODE_ALPHA; 83 } 84 85 private void createKeyboardsWithSpecificSize(int viewWidth, int viewHeight) { 86 mNumericKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_numeric, 87 viewWidth, viewHeight); 88 mQwertyKeyboard = new PasswordEntryKeyboard(mContext, 89 R.xml.password_kbd_qwerty, R.id.mode_normal, viewWidth, viewHeight); 90 mQwertyKeyboard.enableShiftLock(); 91 92 mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext, 93 R.xml.password_kbd_qwerty_shifted, 94 R.id.mode_normal, viewWidth, viewHeight); 95 mQwertyKeyboardShifted.enableShiftLock(); 96 mQwertyKeyboardShifted.setShifted(true); // always shifted. 97 98 mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_symbols, 99 viewWidth, viewHeight); 100 mSymbolsKeyboard.enableShiftLock(); 101 102 mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext, 103 R.xml.password_kbd_symbols_shift, viewWidth, viewHeight); 104 mSymbolsKeyboardShifted.enableShiftLock(); 105 mSymbolsKeyboardShifted.setShifted(true); // always shifted 106 } 107 108 private void createKeyboards() { 109 mNumericKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_numeric); 110 mQwertyKeyboard = new PasswordEntryKeyboard(mContext, 111 R.xml.password_kbd_qwerty, R.id.mode_normal); 112 mQwertyKeyboard.enableShiftLock(); 113 114 mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext, 115 R.xml.password_kbd_qwerty_shifted, 116 R.id.mode_normal); 117 mQwertyKeyboardShifted.enableShiftLock(); 118 mQwertyKeyboardShifted.setShifted(true); // always shifted. 119 120 mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_symbols); 121 mSymbolsKeyboard.enableShiftLock(); 122 123 mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext, 124 R.xml.password_kbd_symbols_shift); 125 mSymbolsKeyboardShifted.enableShiftLock(); 126 mSymbolsKeyboardShifted.setShifted(true); // always shifted 127 } 128 129 public void setKeyboardMode(int mode) { 130 switch (mode) { 131 case KEYBOARD_MODE_ALPHA: 132 mKeyboardView.setKeyboard(mQwertyKeyboard); 133 mKeyboardState = KEYBOARD_STATE_NORMAL; 134 final boolean visiblePassword = Settings.System.getInt( 135 mContext.getContentResolver(), 136 Settings.System.TEXT_SHOW_PASSWORD, 1) != 0; 137 final boolean enablePreview = false; // TODO: grab from configuration 138 mKeyboardView.setPreviewEnabled(visiblePassword && enablePreview); 139 break; 140 case KEYBOARD_MODE_NUMERIC: 141 mKeyboardView.setKeyboard(mNumericKeyboard); 142 mKeyboardState = KEYBOARD_STATE_NORMAL; 143 mKeyboardView.setPreviewEnabled(false); // never show popup for numeric keypad 144 break; 145 } 146 mKeyboardMode = mode; 147 } 148 149 private void sendKeyEventsToTarget(int character) { 150 ViewRootImpl viewRootImpl = mTargetView.getViewRootImpl(); 151 KeyEvent[] events = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD).getEvents( 152 new char[] { (char) character }); 153 if (events != null) { 154 final int N = events.length; 155 for (int i=0; i<N; i++) { 156 KeyEvent event = events[i]; 157 event = KeyEvent.changeFlags(event, event.getFlags() 158 | KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE); 159 viewRootImpl.dispatchKey(event); 160 } 161 } 162 } 163 164 public void sendDownUpKeyEvents(int keyEventCode) { 165 long eventTime = SystemClock.uptimeMillis(); 166 ViewRootImpl viewRootImpl = mTargetView.getViewRootImpl(); 167 viewRootImpl.dispatchKeyFromIme( 168 new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, 169 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 170 KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); 171 viewRootImpl.dispatchKeyFromIme( 172 new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyEventCode, 0, 0, 173 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 174 KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); 175 } 176 177 public void onKey(int primaryCode, int[] keyCodes) { 178 if (primaryCode == Keyboard.KEYCODE_DELETE) { 179 handleBackspace(); 180 } else if (primaryCode == Keyboard.KEYCODE_SHIFT) { 181 handleShift(); 182 } else if (primaryCode == Keyboard.KEYCODE_CANCEL) { 183 handleClose(); 184 return; 185 } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE && mKeyboardView != null) { 186 handleModeChange(); 187 } else { 188 handleCharacter(primaryCode, keyCodes); 189 // Switch back to old keyboard if we're not in capslock mode 190 if (mKeyboardState == KEYBOARD_STATE_SHIFTED) { 191 // skip to the unlocked state 192 mKeyboardState = KEYBOARD_STATE_CAPSLOCK; 193 handleShift(); 194 } 195 } 196 } 197 198 /** 199 * Sets and enables vibrate pattern. If id is 0 (or can't be loaded), vibrate is disabled. 200 * @param id resource id for array containing vibrate pattern. 201 */ 202 public void setVibratePattern(int id) { 203 int[] tmpArray = null; 204 try { 205 tmpArray = mContext.getResources().getIntArray(id); 206 } catch (Resources.NotFoundException e) { 207 if (id != 0) { 208 Log.e(TAG, "Vibrate pattern missing", e); 209 } 210 } 211 if (tmpArray == null) { 212 mVibratePattern = null; 213 return; 214 } 215 mVibratePattern = new long[tmpArray.length]; 216 for (int i = 0; i < tmpArray.length; i++) { 217 mVibratePattern[i] = tmpArray[i]; 218 } 219 } 220 221 private void handleModeChange() { 222 final Keyboard current = mKeyboardView.getKeyboard(); 223 Keyboard next = null; 224 if (current == mQwertyKeyboard || current == mQwertyKeyboardShifted) { 225 next = mSymbolsKeyboard; 226 } else if (current == mSymbolsKeyboard || current == mSymbolsKeyboardShifted) { 227 next = mQwertyKeyboard; 228 } 229 if (next != null) { 230 mKeyboardView.setKeyboard(next); 231 mKeyboardState = KEYBOARD_STATE_NORMAL; 232 } 233 } 234 235 public void handleBackspace() { 236 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 237 performHapticFeedback(); 238 } 239 240 private void handleShift() { 241 if (mKeyboardView == null) { 242 return; 243 } 244 Keyboard current = mKeyboardView.getKeyboard(); 245 PasswordEntryKeyboard next = null; 246 final boolean isAlphaMode = current == mQwertyKeyboard 247 || current == mQwertyKeyboardShifted; 248 if (mKeyboardState == KEYBOARD_STATE_NORMAL) { 249 mKeyboardState = isAlphaMode ? KEYBOARD_STATE_SHIFTED : KEYBOARD_STATE_CAPSLOCK; 250 next = isAlphaMode ? mQwertyKeyboardShifted : mSymbolsKeyboardShifted; 251 } else if (mKeyboardState == KEYBOARD_STATE_SHIFTED) { 252 mKeyboardState = KEYBOARD_STATE_CAPSLOCK; 253 next = isAlphaMode ? mQwertyKeyboardShifted : mSymbolsKeyboardShifted; 254 } else if (mKeyboardState == KEYBOARD_STATE_CAPSLOCK) { 255 mKeyboardState = KEYBOARD_STATE_NORMAL; 256 next = isAlphaMode ? mQwertyKeyboard : mSymbolsKeyboard; 257 } 258 if (next != null) { 259 if (next != current) { 260 mKeyboardView.setKeyboard(next); 261 } 262 next.setShiftLocked(mKeyboardState == KEYBOARD_STATE_CAPSLOCK); 263 mKeyboardView.setShifted(mKeyboardState != KEYBOARD_STATE_NORMAL); 264 } 265 } 266 267 private void handleCharacter(int primaryCode, int[] keyCodes) { 268 // Maybe turn off shift if not in capslock mode. 269 if (mKeyboardView.isShifted() && primaryCode != ' ' && primaryCode != '\n') { 270 primaryCode = Character.toUpperCase(primaryCode); 271 } 272 sendKeyEventsToTarget(primaryCode); 273 } 274 275 private void handleClose() { 276 277 } 278 279 public void onPress(int primaryCode) { 280 performHapticFeedback(); 281 } 282 283 private void performHapticFeedback() { 284 if (mEnableHaptics) { 285 mKeyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, 286 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING 287 | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); 288 } 289 } 290 291 public void onRelease(int primaryCode) { 292 293 } 294 295 public void onText(CharSequence text) { 296 297 } 298 299 public void swipeDown() { 300 301 } 302 303 public void swipeLeft() { 304 305 } 306 307 public void swipeRight() { 308 309 } 310 311 public void swipeUp() { 312 313 } 314 }; 315