MetaKeyKeyListener.java revision 3954fd9a05232cb6f7fc52aa49a0b34c1539028a
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.text.method; 18 19import android.text.Editable; 20import android.text.NoCopySpan; 21import android.text.Spannable; 22import android.text.Spanned; 23import android.view.KeyEvent; 24import android.view.View; 25import android.view.KeyCharacterMap; 26 27/** 28 * This base class encapsulates the behavior for tracking the state of 29 * meta keys such as SHIFT, ALT and SYM as well as the pseudo-meta state of selecting text. 30 * <p> 31 * Key listeners that care about meta state should inherit from this class; 32 * you should not instantiate this class directly in a client. 33 * </p><p> 34 * This class provides two mechanisms for tracking meta state that can be used 35 * together or independently. 36 * </p> 37 * <ul> 38 * <li>Methods such as {@link #handleKeyDown(long, int, KeyEvent)} and 39 * {@link #getMetaState(long)} operate on a meta key state bit mask.</li> 40 * <li>Methods such as {@link #onKeyDown(View, Editable, int, KeyEvent)} and 41 * {@link #getMetaState(CharSequence, int)} operate on meta key state flags stored 42 * as spans in an {@link Editable} text buffer. The spans only describe the current 43 * meta key state of the text editor; they do not carry any positional information.</li> 44 * </ul> 45 * <p> 46 * The behavior of this class varies according to the keyboard capabilities 47 * described by the {@link KeyCharacterMap} of the keyboard device such as 48 * the {@link KeyCharacterMap#getModifierBehavior() key modifier behavior}. 49 * </p><p> 50 * {@link MetaKeyKeyListener} implements chorded and toggled key modifiers. 51 * When key modifiers are toggled into a latched or locked state, the state 52 * of the modifier is stored in the {@link Editable} text buffer or in a 53 * meta state integer managed by the client. These latched or locked modifiers 54 * should be considered to be held <b>in addition to</b> those that the 55 * keyboard already reported as being pressed in {@link KeyEvent#getMetaState()}. 56 * In other words, the {@link MetaKeyKeyListener} augments the meta state 57 * provided by the keyboard; it does not replace it. This distinction is important 58 * to ensure that meta keys not handled by {@link MetaKeyKeyListener} such as 59 * {@link KeyEvent#KEYCODE_CAPS_LOCK} or {@link KeyEvent#KEYCODE_NUM_LOCK} are 60 * taken into consideration. 61 * </p><p> 62 * To ensure correct meta key behavior, the following pattern should be used 63 * when mapping key codes to characters: 64 * </p> 65 * <code> 66 * private char getUnicodeChar(TextKeyListener listener, KeyEvent event, Editable textBuffer) { 67 * // Use the combined meta states from the event and the key listener. 68 * int metaState = event.getMetaState() | listener.getMetaState(textBuffer); 69 * return event.getUnicodeChar(metaState); 70 * } 71 * </code> 72 */ 73public abstract class MetaKeyKeyListener { 74 /** 75 * Flag that indicates that the SHIFT key is on. 76 * Value equals {@link KeyEvent#META_SHIFT_ON}. 77 */ 78 public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON; 79 /** 80 * Flag that indicates that the ALT key is on. 81 * Value equals {@link KeyEvent#META_ALT_ON}. 82 */ 83 public static final int META_ALT_ON = KeyEvent.META_ALT_ON; 84 /** 85 * Flag that indicates that the SYM key is on. 86 * Value equals {@link KeyEvent#META_SYM_ON}. 87 */ 88 public static final int META_SYM_ON = KeyEvent.META_SYM_ON; 89 90 /** 91 * Flag that indicates that the SHIFT key is locked in CAPS mode. 92 */ 93 public static final int META_CAP_LOCKED = KeyEvent.META_CAP_LOCKED; 94 /** 95 * Flag that indicates that the ALT key is locked. 96 */ 97 public static final int META_ALT_LOCKED = KeyEvent.META_ALT_LOCKED; 98 /** 99 * Flag that indicates that the SYM key is locked. 100 */ 101 public static final int META_SYM_LOCKED = KeyEvent.META_SYM_LOCKED; 102 103 /** 104 * @hide pending API review 105 */ 106 public static final int META_SELECTING = KeyEvent.META_SELECTING; 107 108 // These bits are privately used by the meta key key listener. 109 // They are deliberately assigned values outside of the representable range of an 'int' 110 // so as not to conflict with any meta key states publicly defined by KeyEvent. 111 private static final long META_CAP_USED = 1L << 32; 112 private static final long META_ALT_USED = 1L << 33; 113 private static final long META_SYM_USED = 1L << 34; 114 115 private static final long META_CAP_PRESSED = 1L << 40; 116 private static final long META_ALT_PRESSED = 1L << 41; 117 private static final long META_SYM_PRESSED = 1L << 42; 118 119 private static final long META_CAP_RELEASED = 1L << 48; 120 private static final long META_ALT_RELEASED = 1L << 49; 121 private static final long META_SYM_RELEASED = 1L << 50; 122 123 private static final long META_SHIFT_MASK = META_SHIFT_ON 124 | META_CAP_LOCKED | META_CAP_USED 125 | META_CAP_PRESSED | META_CAP_RELEASED; 126 private static final long META_ALT_MASK = META_ALT_ON 127 | META_ALT_LOCKED | META_ALT_USED 128 | META_ALT_PRESSED | META_ALT_RELEASED; 129 private static final long META_SYM_MASK = META_SYM_ON 130 | META_SYM_LOCKED | META_SYM_USED 131 | META_SYM_PRESSED | META_SYM_RELEASED; 132 133 private static final Object CAP = new NoCopySpan.Concrete(); 134 private static final Object ALT = new NoCopySpan.Concrete(); 135 private static final Object SYM = new NoCopySpan.Concrete(); 136 private static final Object SELECTING = new NoCopySpan.Concrete(); 137 138 private static final int PRESSED_RETURN_VALUE = 1; 139 private static final int LOCKED_RETURN_VALUE = 2; 140 141 /** 142 * Resets all meta state to inactive. 143 */ 144 public static void resetMetaState(Spannable text) { 145 text.removeSpan(CAP); 146 text.removeSpan(ALT); 147 text.removeSpan(SYM); 148 text.removeSpan(SELECTING); 149 } 150 151 /** 152 * Gets the state of the meta keys. 153 * 154 * @param text the buffer in which the meta key would have been pressed. 155 * 156 * @return an integer in which each bit set to one represents a pressed 157 * or locked meta key. 158 */ 159 public static final int getMetaState(CharSequence text) { 160 return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) | 161 getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) | 162 getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) | 163 getActive(text, SELECTING, META_SELECTING, META_SELECTING); 164 } 165 166 // As META_SELECTING is @hide we should not mention it in public comments, hence the 167 // omission in @param meta 168 /** 169 * Gets the state of a particular meta key. 170 * 171 * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON 172 * @param text the buffer in which the meta key would have been pressed. 173 * 174 * @return 0 if inactive, 1 if active, 2 if locked. 175 */ 176 public static final int getMetaState(CharSequence text, int meta) { 177 switch (meta) { 178 case META_SHIFT_ON: 179 return getActive(text, CAP, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); 180 181 case META_ALT_ON: 182 return getActive(text, ALT, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); 183 184 case META_SYM_ON: 185 return getActive(text, SYM, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); 186 187 case META_SELECTING: 188 return getActive(text, SELECTING, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); 189 190 default: 191 return 0; 192 } 193 } 194 195 private static int getActive(CharSequence text, Object meta, 196 int on, int lock) { 197 if (!(text instanceof Spanned)) { 198 return 0; 199 } 200 201 Spanned sp = (Spanned) text; 202 int flag = sp.getSpanFlags(meta); 203 204 if (flag == LOCKED) { 205 return lock; 206 } else if (flag != 0) { 207 return on; 208 } else { 209 return 0; 210 } 211 } 212 213 /** 214 * Call this method after you handle a keypress so that the meta 215 * state will be reset to unshifted (if it is not still down) 216 * or primed to be reset to unshifted (once it is released). 217 */ 218 public static void adjustMetaAfterKeypress(Spannable content) { 219 adjust(content, CAP); 220 adjust(content, ALT); 221 adjust(content, SYM); 222 } 223 224 /** 225 * Returns true if this object is one that this class would use to 226 * keep track of any meta state in the specified text. 227 */ 228 public static boolean isMetaTracker(CharSequence text, Object what) { 229 return what == CAP || what == ALT || what == SYM || 230 what == SELECTING; 231 } 232 233 /** 234 * Returns true if this object is one that this class would use to 235 * keep track of the selecting meta state in the specified text. 236 */ 237 public static boolean isSelectingMetaTracker(CharSequence text, Object what) { 238 return what == SELECTING; 239 } 240 241 private static void adjust(Spannable content, Object what) { 242 int current = content.getSpanFlags(what); 243 244 if (current == PRESSED) 245 content.setSpan(what, 0, 0, USED); 246 else if (current == RELEASED) 247 content.removeSpan(what); 248 } 249 250 /** 251 * Call this if you are a method that ignores the locked meta state 252 * (arrow keys, for example) and you handle a key. 253 */ 254 protected static void resetLockedMeta(Spannable content) { 255 resetLock(content, CAP); 256 resetLock(content, ALT); 257 resetLock(content, SYM); 258 resetLock(content, SELECTING); 259 } 260 261 private static void resetLock(Spannable content, Object what) { 262 int current = content.getSpanFlags(what); 263 264 if (current == LOCKED) 265 content.removeSpan(what); 266 } 267 268 /** 269 * Handles presses of the meta keys. 270 */ 271 public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { 272 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 273 press(content, CAP); 274 return true; 275 } 276 277 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 278 || keyCode == KeyEvent.KEYCODE_NUM) { 279 press(content, ALT); 280 return true; 281 } 282 283 if (keyCode == KeyEvent.KEYCODE_SYM) { 284 press(content, SYM); 285 return true; 286 } 287 288 return false; // no super to call through to 289 } 290 291 private void press(Editable content, Object what) { 292 int state = content.getSpanFlags(what); 293 294 if (state == PRESSED) 295 ; // repeat before use 296 else if (state == RELEASED) 297 content.setSpan(what, 0, 0, LOCKED); 298 else if (state == USED) 299 ; // repeat after use 300 else if (state == LOCKED) 301 content.removeSpan(what); 302 else 303 content.setSpan(what, 0, 0, PRESSED); 304 } 305 306 /** 307 * Start selecting text. 308 * @hide pending API review 309 */ 310 public static void startSelecting(View view, Spannable content) { 311 content.setSpan(SELECTING, 0, 0, PRESSED); 312 } 313 314 /** 315 * Stop selecting text. This does not actually collapse the selection; 316 * call {@link android.text.Selection#setSelection} too. 317 * @hide pending API review 318 */ 319 public static void stopSelecting(View view, Spannable content) { 320 content.removeSpan(SELECTING); 321 } 322 323 /** 324 * Handles release of the meta keys. 325 */ 326 public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) { 327 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 328 release(content, CAP, event); 329 return true; 330 } 331 332 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 333 || keyCode == KeyEvent.KEYCODE_NUM) { 334 release(content, ALT, event); 335 return true; 336 } 337 338 if (keyCode == KeyEvent.KEYCODE_SYM) { 339 release(content, SYM, event); 340 return true; 341 } 342 343 return false; // no super to call through to 344 } 345 346 private void release(Editable content, Object what, KeyEvent event) { 347 int current = content.getSpanFlags(what); 348 349 switch (event.getKeyCharacterMap().getModifierBehavior()) { 350 case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED: 351 if (current == USED) 352 content.removeSpan(what); 353 else if (current == PRESSED) 354 content.setSpan(what, 0, 0, RELEASED); 355 break; 356 357 default: 358 content.removeSpan(what); 359 break; 360 } 361 } 362 363 public void clearMetaKeyState(View view, Editable content, int states) { 364 clearMetaKeyState(content, states); 365 } 366 367 public static void clearMetaKeyState(Editable content, int states) { 368 if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP); 369 if ((states&META_ALT_ON) != 0) content.removeSpan(ALT); 370 if ((states&META_SYM_ON) != 0) content.removeSpan(SYM); 371 if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING); 372 } 373 374 /** 375 * Call this if you are a method that ignores the locked meta state 376 * (arrow keys, for example) and you handle a key. 377 */ 378 public static long resetLockedMeta(long state) { 379 if ((state & META_CAP_LOCKED) != 0) { 380 state &= ~META_SHIFT_MASK; 381 } 382 if ((state & META_ALT_LOCKED) != 0) { 383 state &= ~META_ALT_MASK; 384 } 385 if ((state & META_SYM_LOCKED) != 0) { 386 state &= ~META_SYM_MASK; 387 } 388 return state; 389 } 390 391 // --------------------------------------------------------------------- 392 // Version of API that operates on a state bit mask 393 // --------------------------------------------------------------------- 394 395 /** 396 * Gets the state of the meta keys. 397 * 398 * @param state the current meta state bits. 399 * 400 * @return an integer in which each bit set to one represents a pressed 401 * or locked meta key. 402 */ 403 public static final int getMetaState(long state) { 404 int result = 0; 405 406 if ((state & META_CAP_LOCKED) != 0) { 407 result |= META_CAP_LOCKED; 408 } else if ((state & META_SHIFT_ON) != 0) { 409 result |= META_SHIFT_ON; 410 } 411 412 if ((state & META_ALT_LOCKED) != 0) { 413 result |= META_ALT_LOCKED; 414 } else if ((state & META_ALT_ON) != 0) { 415 result |= META_ALT_ON; 416 } 417 418 if ((state & META_SYM_LOCKED) != 0) { 419 result |= META_SYM_LOCKED; 420 } else if ((state & META_SYM_ON) != 0) { 421 result |= META_SYM_ON; 422 } 423 424 return result; 425 } 426 427 /** 428 * Gets the state of a particular meta key. 429 * 430 * @param state the current state bits. 431 * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON 432 * 433 * @return 0 if inactive, 1 if active, 2 if locked. 434 */ 435 public static final int getMetaState(long state, int meta) { 436 switch (meta) { 437 case META_SHIFT_ON: 438 if ((state & META_CAP_LOCKED) != 0) return LOCKED_RETURN_VALUE; 439 if ((state & META_SHIFT_ON) != 0) return PRESSED_RETURN_VALUE; 440 return 0; 441 442 case META_ALT_ON: 443 if ((state & META_ALT_LOCKED) != 0) return LOCKED_RETURN_VALUE; 444 if ((state & META_ALT_ON) != 0) return PRESSED_RETURN_VALUE; 445 return 0; 446 447 case META_SYM_ON: 448 if ((state & META_SYM_LOCKED) != 0) return LOCKED_RETURN_VALUE; 449 if ((state & META_SYM_ON) != 0) return PRESSED_RETURN_VALUE; 450 return 0; 451 452 default: 453 return 0; 454 } 455 } 456 457 /** 458 * Call this method after you handle a keypress so that the meta 459 * state will be reset to unshifted (if it is not still down) 460 * or primed to be reset to unshifted (once it is released). Takes 461 * the current state, returns the new state. 462 */ 463 public static long adjustMetaAfterKeypress(long state) { 464 if ((state & META_CAP_PRESSED) != 0) { 465 state = (state & ~META_SHIFT_MASK) | META_SHIFT_ON | META_CAP_USED; 466 } else if ((state & META_CAP_RELEASED) != 0) { 467 state &= ~META_SHIFT_MASK; 468 } 469 470 if ((state & META_ALT_PRESSED) != 0) { 471 state = (state & ~META_ALT_MASK) | META_ALT_ON | META_ALT_USED; 472 } else if ((state & META_ALT_RELEASED) != 0) { 473 state &= ~META_ALT_MASK; 474 } 475 476 if ((state & META_SYM_PRESSED) != 0) { 477 state = (state & ~META_SYM_MASK) | META_SYM_ON | META_SYM_USED; 478 } else if ((state & META_SYM_RELEASED) != 0) { 479 state &= ~META_SYM_MASK; 480 } 481 return state; 482 } 483 484 /** 485 * Handles presses of the meta keys. 486 */ 487 public static long handleKeyDown(long state, int keyCode, KeyEvent event) { 488 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 489 return press(state, META_SHIFT_ON, META_SHIFT_MASK, 490 META_CAP_LOCKED, META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED); 491 } 492 493 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 494 || keyCode == KeyEvent.KEYCODE_NUM) { 495 return press(state, META_ALT_ON, META_ALT_MASK, 496 META_ALT_LOCKED, META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED); 497 } 498 499 if (keyCode == KeyEvent.KEYCODE_SYM) { 500 return press(state, META_SYM_ON, META_SYM_MASK, 501 META_SYM_LOCKED, META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED); 502 } 503 return state; 504 } 505 506 private static long press(long state, int what, long mask, 507 long locked, long pressed, long released, long used) { 508 if ((state & pressed) != 0) { 509 // repeat before use 510 } else if ((state & released) != 0) { 511 state = (state &~ mask) | what | locked; 512 } else if ((state & used) != 0) { 513 // repeat after use 514 } else if ((state & locked) != 0) { 515 state &= ~mask; 516 } else { 517 state |= what | pressed; 518 } 519 return state; 520 } 521 522 /** 523 * Handles release of the meta keys. 524 */ 525 public static long handleKeyUp(long state, int keyCode, KeyEvent event) { 526 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 527 return release(state, META_SHIFT_ON, META_SHIFT_MASK, 528 META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED, event); 529 } 530 531 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 532 || keyCode == KeyEvent.KEYCODE_NUM) { 533 return release(state, META_ALT_ON, META_ALT_MASK, 534 META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED, event); 535 } 536 537 if (keyCode == KeyEvent.KEYCODE_SYM) { 538 return release(state, META_SYM_ON, META_SYM_MASK, 539 META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED, event); 540 } 541 return state; 542 } 543 544 private static long release(long state, int what, long mask, 545 long pressed, long released, long used, KeyEvent event) { 546 switch (event.getKeyCharacterMap().getModifierBehavior()) { 547 case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED: 548 if ((state & used) != 0) { 549 state &= ~mask; 550 } else if ((state & pressed) != 0) { 551 state |= what | released; 552 } 553 break; 554 555 default: 556 state &= ~mask; 557 break; 558 } 559 return state; 560 } 561 562 /** 563 * Clears the state of the specified meta key if it is locked. 564 * @param state the meta key state 565 * @param which meta keys to clear, may be a combination of {@link #META_SHIFT_ON}, 566 * {@link #META_ALT_ON} or {@link #META_SYM_ON}. 567 */ 568 public long clearMetaKeyState(long state, int which) { 569 if ((which & META_SHIFT_ON) != 0 && (state & META_CAP_LOCKED) != 0) { 570 state &= ~META_SHIFT_MASK; 571 } 572 if ((which & META_ALT_ON) != 0 && (state & META_ALT_LOCKED) != 0) { 573 state &= ~META_ALT_MASK; 574 } 575 if ((which & META_SYM_ON) != 0 && (state & META_SYM_LOCKED) != 0) { 576 state &= ~META_SYM_MASK; 577 } 578 return state; 579 } 580 581 /** 582 * The meta key has been pressed but has not yet been used. 583 */ 584 private static final int PRESSED = 585 Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT); 586 587 /** 588 * The meta key has been pressed and released but has still 589 * not yet been used. 590 */ 591 private static final int RELEASED = 592 Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT); 593 594 /** 595 * The meta key has been pressed and used but has not yet been released. 596 */ 597 private static final int USED = 598 Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT); 599 600 /** 601 * The meta key has been pressed and released without use, and then 602 * pressed again; it may also have been released again. 603 */ 604 private static final int LOCKED = 605 Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT); 606} 607