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 17 package android.text.method; 18 19 import android.text.Editable; 20 import android.text.NoCopySpan; 21 import android.text.Spannable; 22 import android.text.Spanned; 23 import android.view.KeyEvent; 24 import android.view.View; 25 import 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 */ 73 public 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 /** 167 * Gets the state of the meta keys for a specific key event. 168 * 169 * For input devices that use toggled key modifiers, the `toggled' state 170 * is stored into the text buffer. This method retrieves the meta state 171 * for this event, accounting for the stored state. If the event has been 172 * created by a device that does not support toggled key modifiers, like 173 * a virtual device for example, the stored state is ignored. 174 * 175 * @param text the buffer in which the meta key would have been pressed. 176 * @param event the event for which to evaluate the meta state. 177 * @return an integer in which each bit set to one represents a pressed 178 * or locked meta key. 179 */ 180 public static final int getMetaState(final CharSequence text, final KeyEvent event) { 181 int metaState = event.getMetaState(); 182 if (event.getKeyCharacterMap().getModifierBehavior() 183 == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) { 184 metaState |= getMetaState(text); 185 } 186 return metaState; 187 } 188 189 // As META_SELECTING is @hide we should not mention it in public comments, hence the 190 // omission in @param meta 191 /** 192 * Gets the state of a particular meta key. 193 * 194 * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON 195 * @param text the buffer in which the meta key would have been pressed. 196 * 197 * @return 0 if inactive, 1 if active, 2 if locked. 198 */ 199 public static final int getMetaState(CharSequence text, int meta) { 200 switch (meta) { 201 case META_SHIFT_ON: 202 return getActive(text, CAP, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); 203 204 case META_ALT_ON: 205 return getActive(text, ALT, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); 206 207 case META_SYM_ON: 208 return getActive(text, SYM, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); 209 210 case META_SELECTING: 211 return getActive(text, SELECTING, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); 212 213 default: 214 return 0; 215 } 216 } 217 218 /** 219 * Gets the state of a particular meta key to use with a particular key event. 220 * 221 * If the key event has been created by a device that does not support toggled 222 * key modifiers, like a virtual keyboard for example, only the meta state in 223 * the key event is considered. 224 * 225 * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON 226 * @param text the buffer in which the meta key would have been pressed. 227 * @param event the event for which to evaluate the meta state. 228 * @return 0 if inactive, 1 if active, 2 if locked. 229 */ 230 public static final int getMetaState(final CharSequence text, final int meta, 231 final KeyEvent event) { 232 int metaState = event.getMetaState(); 233 if (event.getKeyCharacterMap().getModifierBehavior() 234 == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) { 235 metaState |= getMetaState(text); 236 } 237 if (META_SELECTING == meta) { 238 // #getMetaState(long, int) does not support META_SELECTING, but we want the same 239 // behavior as #getMetaState(CharSequence, int) so we need to do it here 240 if ((metaState & META_SELECTING) != 0) { 241 // META_SELECTING is only ever set to PRESSED and can't be LOCKED, so return 1 242 return 1; 243 } 244 return 0; 245 } 246 return getMetaState(metaState, meta); 247 } 248 249 private static int getActive(CharSequence text, Object meta, 250 int on, int lock) { 251 if (!(text instanceof Spanned)) { 252 return 0; 253 } 254 255 Spanned sp = (Spanned) text; 256 int flag = sp.getSpanFlags(meta); 257 258 if (flag == LOCKED) { 259 return lock; 260 } else if (flag != 0) { 261 return on; 262 } else { 263 return 0; 264 } 265 } 266 267 /** 268 * Call this method after you handle a keypress so that the meta 269 * state will be reset to unshifted (if it is not still down) 270 * or primed to be reset to unshifted (once it is released). 271 */ 272 public static void adjustMetaAfterKeypress(Spannable content) { 273 adjust(content, CAP); 274 adjust(content, ALT); 275 adjust(content, SYM); 276 } 277 278 /** 279 * Returns true if this object is one that this class would use to 280 * keep track of any meta state in the specified text. 281 */ 282 public static boolean isMetaTracker(CharSequence text, Object what) { 283 return what == CAP || what == ALT || what == SYM || 284 what == SELECTING; 285 } 286 287 /** 288 * Returns true if this object is one that this class would use to 289 * keep track of the selecting meta state in the specified text. 290 */ 291 public static boolean isSelectingMetaTracker(CharSequence text, Object what) { 292 return what == SELECTING; 293 } 294 295 private static void adjust(Spannable content, Object what) { 296 int current = content.getSpanFlags(what); 297 298 if (current == PRESSED) 299 content.setSpan(what, 0, 0, USED); 300 else if (current == RELEASED) 301 content.removeSpan(what); 302 } 303 304 /** 305 * Call this if you are a method that ignores the locked meta state 306 * (arrow keys, for example) and you handle a key. 307 */ 308 protected static void resetLockedMeta(Spannable content) { 309 resetLock(content, CAP); 310 resetLock(content, ALT); 311 resetLock(content, SYM); 312 resetLock(content, SELECTING); 313 } 314 315 private static void resetLock(Spannable content, Object what) { 316 int current = content.getSpanFlags(what); 317 318 if (current == LOCKED) 319 content.removeSpan(what); 320 } 321 322 /** 323 * Handles presses of the meta keys. 324 */ 325 public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { 326 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 327 press(content, CAP); 328 return true; 329 } 330 331 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 332 || keyCode == KeyEvent.KEYCODE_NUM) { 333 press(content, ALT); 334 return true; 335 } 336 337 if (keyCode == KeyEvent.KEYCODE_SYM) { 338 press(content, SYM); 339 return true; 340 } 341 342 return false; // no super to call through to 343 } 344 345 private void press(Editable content, Object what) { 346 int state = content.getSpanFlags(what); 347 348 if (state == PRESSED) 349 ; // repeat before use 350 else if (state == RELEASED) 351 content.setSpan(what, 0, 0, LOCKED); 352 else if (state == USED) 353 ; // repeat after use 354 else if (state == LOCKED) 355 content.removeSpan(what); 356 else 357 content.setSpan(what, 0, 0, PRESSED); 358 } 359 360 /** 361 * Start selecting text. 362 * @hide pending API review 363 */ 364 public static void startSelecting(View view, Spannable content) { 365 content.setSpan(SELECTING, 0, 0, PRESSED); 366 } 367 368 /** 369 * Stop selecting text. This does not actually collapse the selection; 370 * call {@link android.text.Selection#setSelection} too. 371 * @hide pending API review 372 */ 373 public static void stopSelecting(View view, Spannable content) { 374 content.removeSpan(SELECTING); 375 } 376 377 /** 378 * Handles release of the meta keys. 379 */ 380 public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) { 381 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 382 release(content, CAP, event); 383 return true; 384 } 385 386 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 387 || keyCode == KeyEvent.KEYCODE_NUM) { 388 release(content, ALT, event); 389 return true; 390 } 391 392 if (keyCode == KeyEvent.KEYCODE_SYM) { 393 release(content, SYM, event); 394 return true; 395 } 396 397 return false; // no super to call through to 398 } 399 400 private void release(Editable content, Object what, KeyEvent event) { 401 int current = content.getSpanFlags(what); 402 403 switch (event.getKeyCharacterMap().getModifierBehavior()) { 404 case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED: 405 if (current == USED) 406 content.removeSpan(what); 407 else if (current == PRESSED) 408 content.setSpan(what, 0, 0, RELEASED); 409 break; 410 411 default: 412 content.removeSpan(what); 413 break; 414 } 415 } 416 417 public void clearMetaKeyState(View view, Editable content, int states) { 418 clearMetaKeyState(content, states); 419 } 420 421 public static void clearMetaKeyState(Editable content, int states) { 422 if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP); 423 if ((states&META_ALT_ON) != 0) content.removeSpan(ALT); 424 if ((states&META_SYM_ON) != 0) content.removeSpan(SYM); 425 if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING); 426 } 427 428 /** 429 * Call this if you are a method that ignores the locked meta state 430 * (arrow keys, for example) and you handle a key. 431 */ 432 public static long resetLockedMeta(long state) { 433 if ((state & META_CAP_LOCKED) != 0) { 434 state &= ~META_SHIFT_MASK; 435 } 436 if ((state & META_ALT_LOCKED) != 0) { 437 state &= ~META_ALT_MASK; 438 } 439 if ((state & META_SYM_LOCKED) != 0) { 440 state &= ~META_SYM_MASK; 441 } 442 return state; 443 } 444 445 // --------------------------------------------------------------------- 446 // Version of API that operates on a state bit mask 447 // --------------------------------------------------------------------- 448 449 /** 450 * Gets the state of the meta keys. 451 * 452 * @param state the current meta state bits. 453 * 454 * @return an integer in which each bit set to one represents a pressed 455 * or locked meta key. 456 */ 457 public static final int getMetaState(long state) { 458 int result = 0; 459 460 if ((state & META_CAP_LOCKED) != 0) { 461 result |= META_CAP_LOCKED; 462 } else if ((state & META_SHIFT_ON) != 0) { 463 result |= META_SHIFT_ON; 464 } 465 466 if ((state & META_ALT_LOCKED) != 0) { 467 result |= META_ALT_LOCKED; 468 } else if ((state & META_ALT_ON) != 0) { 469 result |= META_ALT_ON; 470 } 471 472 if ((state & META_SYM_LOCKED) != 0) { 473 result |= META_SYM_LOCKED; 474 } else if ((state & META_SYM_ON) != 0) { 475 result |= META_SYM_ON; 476 } 477 478 return result; 479 } 480 481 /** 482 * Gets the state of a particular meta key. 483 * 484 * @param state the current state bits. 485 * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON 486 * 487 * @return 0 if inactive, 1 if active, 2 if locked. 488 */ 489 public static final int getMetaState(long state, int meta) { 490 switch (meta) { 491 case META_SHIFT_ON: 492 if ((state & META_CAP_LOCKED) != 0) return LOCKED_RETURN_VALUE; 493 if ((state & META_SHIFT_ON) != 0) return PRESSED_RETURN_VALUE; 494 return 0; 495 496 case META_ALT_ON: 497 if ((state & META_ALT_LOCKED) != 0) return LOCKED_RETURN_VALUE; 498 if ((state & META_ALT_ON) != 0) return PRESSED_RETURN_VALUE; 499 return 0; 500 501 case META_SYM_ON: 502 if ((state & META_SYM_LOCKED) != 0) return LOCKED_RETURN_VALUE; 503 if ((state & META_SYM_ON) != 0) return PRESSED_RETURN_VALUE; 504 return 0; 505 506 default: 507 return 0; 508 } 509 } 510 511 /** 512 * Call this method after you handle a keypress so that the meta 513 * state will be reset to unshifted (if it is not still down) 514 * or primed to be reset to unshifted (once it is released). Takes 515 * the current state, returns the new state. 516 */ 517 public static long adjustMetaAfterKeypress(long state) { 518 if ((state & META_CAP_PRESSED) != 0) { 519 state = (state & ~META_SHIFT_MASK) | META_SHIFT_ON | META_CAP_USED; 520 } else if ((state & META_CAP_RELEASED) != 0) { 521 state &= ~META_SHIFT_MASK; 522 } 523 524 if ((state & META_ALT_PRESSED) != 0) { 525 state = (state & ~META_ALT_MASK) | META_ALT_ON | META_ALT_USED; 526 } else if ((state & META_ALT_RELEASED) != 0) { 527 state &= ~META_ALT_MASK; 528 } 529 530 if ((state & META_SYM_PRESSED) != 0) { 531 state = (state & ~META_SYM_MASK) | META_SYM_ON | META_SYM_USED; 532 } else if ((state & META_SYM_RELEASED) != 0) { 533 state &= ~META_SYM_MASK; 534 } 535 return state; 536 } 537 538 /** 539 * Handles presses of the meta keys. 540 */ 541 public static long handleKeyDown(long state, int keyCode, KeyEvent event) { 542 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 543 return press(state, META_SHIFT_ON, META_SHIFT_MASK, 544 META_CAP_LOCKED, META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED); 545 } 546 547 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 548 || keyCode == KeyEvent.KEYCODE_NUM) { 549 return press(state, META_ALT_ON, META_ALT_MASK, 550 META_ALT_LOCKED, META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED); 551 } 552 553 if (keyCode == KeyEvent.KEYCODE_SYM) { 554 return press(state, META_SYM_ON, META_SYM_MASK, 555 META_SYM_LOCKED, META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED); 556 } 557 return state; 558 } 559 560 private static long press(long state, int what, long mask, 561 long locked, long pressed, long released, long used) { 562 if ((state & pressed) != 0) { 563 // repeat before use 564 } else if ((state & released) != 0) { 565 state = (state &~ mask) | what | locked; 566 } else if ((state & used) != 0) { 567 // repeat after use 568 } else if ((state & locked) != 0) { 569 state &= ~mask; 570 } else { 571 state |= what | pressed; 572 } 573 return state; 574 } 575 576 /** 577 * Handles release of the meta keys. 578 */ 579 public static long handleKeyUp(long state, int keyCode, KeyEvent event) { 580 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 581 return release(state, META_SHIFT_ON, META_SHIFT_MASK, 582 META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED, event); 583 } 584 585 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 586 || keyCode == KeyEvent.KEYCODE_NUM) { 587 return release(state, META_ALT_ON, META_ALT_MASK, 588 META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED, event); 589 } 590 591 if (keyCode == KeyEvent.KEYCODE_SYM) { 592 return release(state, META_SYM_ON, META_SYM_MASK, 593 META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED, event); 594 } 595 return state; 596 } 597 598 private static long release(long state, int what, long mask, 599 long pressed, long released, long used, KeyEvent event) { 600 switch (event.getKeyCharacterMap().getModifierBehavior()) { 601 case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED: 602 if ((state & used) != 0) { 603 state &= ~mask; 604 } else if ((state & pressed) != 0) { 605 state |= what | released; 606 } 607 break; 608 609 default: 610 state &= ~mask; 611 break; 612 } 613 return state; 614 } 615 616 /** 617 * Clears the state of the specified meta key if it is locked. 618 * @param state the meta key state 619 * @param which meta keys to clear, may be a combination of {@link #META_SHIFT_ON}, 620 * {@link #META_ALT_ON} or {@link #META_SYM_ON}. 621 */ 622 public long clearMetaKeyState(long state, int which) { 623 if ((which & META_SHIFT_ON) != 0 && (state & META_CAP_LOCKED) != 0) { 624 state &= ~META_SHIFT_MASK; 625 } 626 if ((which & META_ALT_ON) != 0 && (state & META_ALT_LOCKED) != 0) { 627 state &= ~META_ALT_MASK; 628 } 629 if ((which & META_SYM_ON) != 0 && (state & META_SYM_LOCKED) != 0) { 630 state &= ~META_SYM_MASK; 631 } 632 return state; 633 } 634 635 /** 636 * The meta key has been pressed but has not yet been used. 637 */ 638 private static final int PRESSED = 639 Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT); 640 641 /** 642 * The meta key has been pressed and released but has still 643 * not yet been used. 644 */ 645 private static final int RELEASED = 646 Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT); 647 648 /** 649 * The meta key has been pressed and used but has not yet been released. 650 */ 651 private static final int USED = 652 Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT); 653 654 /** 655 * The meta key has been pressed and released without use, and then 656 * pressed again; it may also have been released again. 657 */ 658 private static final int LOCKED = 659 Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT); 660 } 661