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