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