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.view.KeyEvent; 20 import android.view.View; 21 import android.text.*; 22 23 /** 24 * This base class encapsulates the behavior for handling the meta keys 25 * (shift and alt) and the pseudo-meta state of selecting text. 26 * Key listeners that care about meta state should 27 * inherit from it; you should not instantiate this class directly in a client. 28 */ 29 30 public abstract class MetaKeyKeyListener { 31 public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON; 32 public static final int META_ALT_ON = KeyEvent.META_ALT_ON; 33 public static final int META_SYM_ON = KeyEvent.META_SYM_ON; 34 35 private static final int LOCKED_SHIFT = 8; 36 37 public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << LOCKED_SHIFT; 38 public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << LOCKED_SHIFT; 39 public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << LOCKED_SHIFT; 40 41 /** 42 * @hide pending API review 43 */ 44 public static final int META_SELECTING = 1 << 16; 45 46 private static final int USED_SHIFT = 24; 47 48 private static final long META_CAP_USED = ((long)KeyEvent.META_SHIFT_ON) << USED_SHIFT; 49 private static final long META_ALT_USED = ((long)KeyEvent.META_ALT_ON) << USED_SHIFT; 50 private static final long META_SYM_USED = ((long)KeyEvent.META_SYM_ON) << USED_SHIFT; 51 52 private static final int PRESSED_SHIFT = 32; 53 54 private static final long META_CAP_PRESSED = ((long)KeyEvent.META_SHIFT_ON) << PRESSED_SHIFT; 55 private static final long META_ALT_PRESSED = ((long)KeyEvent.META_ALT_ON) << PRESSED_SHIFT; 56 private static final long META_SYM_PRESSED = ((long)KeyEvent.META_SYM_ON) << PRESSED_SHIFT; 57 58 private static final int RELEASED_SHIFT = 40; 59 60 private static final long META_CAP_RELEASED = ((long)KeyEvent.META_SHIFT_ON) << RELEASED_SHIFT; 61 private static final long META_ALT_RELEASED = ((long)KeyEvent.META_ALT_ON) << RELEASED_SHIFT; 62 private static final long META_SYM_RELEASED = ((long)KeyEvent.META_SYM_ON) << RELEASED_SHIFT; 63 64 private static final long META_SHIFT_MASK = META_SHIFT_ON 65 | META_CAP_LOCKED | META_CAP_USED 66 | META_CAP_PRESSED | META_CAP_RELEASED; 67 private static final long META_ALT_MASK = META_ALT_ON 68 | META_ALT_LOCKED | META_ALT_USED 69 | META_ALT_PRESSED | META_ALT_RELEASED; 70 private static final long META_SYM_MASK = META_SYM_ON 71 | META_SYM_LOCKED | META_SYM_USED 72 | META_SYM_PRESSED | META_SYM_RELEASED; 73 74 private static final Object CAP = new NoCopySpan.Concrete(); 75 private static final Object ALT = new NoCopySpan.Concrete(); 76 private static final Object SYM = new NoCopySpan.Concrete(); 77 private static final Object SELECTING = new NoCopySpan.Concrete(); 78 79 /** 80 * Resets all meta state to inactive. 81 */ 82 public static void resetMetaState(Spannable text) { 83 text.removeSpan(CAP); 84 text.removeSpan(ALT); 85 text.removeSpan(SYM); 86 text.removeSpan(SELECTING); 87 } 88 89 /** 90 * Gets the state of the meta keys. 91 * 92 * @param text the buffer in which the meta key would have been pressed. 93 * 94 * @return an integer in which each bit set to one represents a pressed 95 * or locked meta key. 96 */ 97 public static final int getMetaState(CharSequence text) { 98 return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) | 99 getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) | 100 getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) | 101 getActive(text, SELECTING, META_SELECTING, META_SELECTING); 102 } 103 104 /** 105 * Gets the state of a particular meta key. 106 * 107 * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING 108 * @param text the buffer in which the meta key would have been pressed. 109 * 110 * @return 0 if inactive, 1 if active, 2 if locked. 111 */ 112 public static final int getMetaState(CharSequence text, int meta) { 113 switch (meta) { 114 case META_SHIFT_ON: 115 return getActive(text, CAP, 1, 2); 116 117 case META_ALT_ON: 118 return getActive(text, ALT, 1, 2); 119 120 case META_SYM_ON: 121 return getActive(text, SYM, 1, 2); 122 123 case META_SELECTING: 124 return getActive(text, SELECTING, 1, 2); 125 126 default: 127 return 0; 128 } 129 } 130 131 private static int getActive(CharSequence text, Object meta, 132 int on, int lock) { 133 if (!(text instanceof Spanned)) { 134 return 0; 135 } 136 137 Spanned sp = (Spanned) text; 138 int flag = sp.getSpanFlags(meta); 139 140 if (flag == LOCKED) { 141 return lock; 142 } else if (flag != 0) { 143 return on; 144 } else { 145 return 0; 146 } 147 } 148 149 /** 150 * Call this method after you handle a keypress so that the meta 151 * state will be reset to unshifted (if it is not still down) 152 * or primed to be reset to unshifted (once it is released). 153 */ 154 public static void adjustMetaAfterKeypress(Spannable content) { 155 adjust(content, CAP); 156 adjust(content, ALT); 157 adjust(content, SYM); 158 } 159 160 /** 161 * Returns true if this object is one that this class would use to 162 * keep track of any meta state in the specified text. 163 */ 164 public static boolean isMetaTracker(CharSequence text, Object what) { 165 return what == CAP || what == ALT || what == SYM || 166 what == SELECTING; 167 } 168 169 /** 170 * Returns true if this object is one that this class would use to 171 * keep track of the selecting meta state in the specified text. 172 */ 173 public static boolean isSelectingMetaTracker(CharSequence text, Object what) { 174 return what == SELECTING; 175 } 176 177 private static void adjust(Spannable content, Object what) { 178 int current = content.getSpanFlags(what); 179 180 if (current == PRESSED) 181 content.setSpan(what, 0, 0, USED); 182 else if (current == RELEASED) 183 content.removeSpan(what); 184 } 185 186 /** 187 * Call this if you are a method that ignores the locked meta state 188 * (arrow keys, for example) and you handle a key. 189 */ 190 protected static void resetLockedMeta(Spannable content) { 191 resetLock(content, CAP); 192 resetLock(content, ALT); 193 resetLock(content, SYM); 194 resetLock(content, SELECTING); 195 } 196 197 private static void resetLock(Spannable content, Object what) { 198 int current = content.getSpanFlags(what); 199 200 if (current == LOCKED) 201 content.removeSpan(what); 202 } 203 204 /** 205 * Handles presses of the meta keys. 206 */ 207 public boolean onKeyDown(View view, Editable content, 208 int keyCode, KeyEvent event) { 209 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 210 press(content, CAP); 211 return true; 212 } 213 214 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 215 || keyCode == KeyEvent.KEYCODE_NUM) { 216 press(content, ALT); 217 return true; 218 } 219 220 if (keyCode == KeyEvent.KEYCODE_SYM) { 221 press(content, SYM); 222 return true; 223 } 224 225 return false; // no super to call through to 226 } 227 228 private void press(Editable content, Object what) { 229 int state = content.getSpanFlags(what); 230 231 if (state == PRESSED) 232 ; // repeat before use 233 else if (state == RELEASED) 234 content.setSpan(what, 0, 0, LOCKED); 235 else if (state == USED) 236 ; // repeat after use 237 else if (state == LOCKED) 238 content.removeSpan(what); 239 else 240 content.setSpan(what, 0, 0, PRESSED); 241 } 242 243 /** 244 * Start selecting text. 245 * @hide pending API review 246 */ 247 public static void startSelecting(View view, Spannable content) { 248 content.setSpan(SELECTING, 0, 0, PRESSED); 249 } 250 251 /** 252 * Stop selecting text. This does not actually collapse the selection; 253 * call {@link android.text.Selection#setSelection} too. 254 * @hide pending API review 255 */ 256 public static void stopSelecting(View view, Spannable content) { 257 content.removeSpan(SELECTING); 258 } 259 260 /** 261 * Handles release of the meta keys. 262 */ 263 public boolean onKeyUp(View view, Editable content, int keyCode, 264 KeyEvent event) { 265 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 266 release(content, CAP); 267 return true; 268 } 269 270 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 271 || keyCode == KeyEvent.KEYCODE_NUM) { 272 release(content, ALT); 273 return true; 274 } 275 276 if (keyCode == KeyEvent.KEYCODE_SYM) { 277 release(content, SYM); 278 return true; 279 } 280 281 return false; // no super to call through to 282 } 283 284 private void release(Editable content, Object what) { 285 int current = content.getSpanFlags(what); 286 287 if (current == USED) 288 content.removeSpan(what); 289 else if (current == PRESSED) 290 content.setSpan(what, 0, 0, RELEASED); 291 } 292 293 public void clearMetaKeyState(View view, Editable content, int states) { 294 clearMetaKeyState(content, states); 295 } 296 297 public static void clearMetaKeyState(Editable content, int states) { 298 if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP); 299 if ((states&META_ALT_ON) != 0) content.removeSpan(ALT); 300 if ((states&META_SYM_ON) != 0) content.removeSpan(SYM); 301 if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING); 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 public static long resetLockedMeta(long state) { 309 state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK); 310 state = resetLock(state, META_ALT_ON, META_ALT_MASK); 311 state = resetLock(state, META_SYM_ON, META_SYM_MASK); 312 return state; 313 } 314 315 private static long resetLock(long state, int what, long mask) { 316 if ((state&(((long)what)<<LOCKED_SHIFT)) != 0) { 317 state &= ~mask; 318 } 319 return state; 320 } 321 322 // --------------------------------------------------------------------- 323 // Version of API that operates on a state bit mask 324 // --------------------------------------------------------------------- 325 326 /** 327 * Gets the state of the meta keys. 328 * 329 * @param state the current meta state bits. 330 * 331 * @return an integer in which each bit set to one represents a pressed 332 * or locked meta key. 333 */ 334 public static final int getMetaState(long state) { 335 return getActive(state, META_SHIFT_ON, META_SHIFT_ON, META_CAP_LOCKED) | 336 getActive(state, META_ALT_ON, META_ALT_ON, META_ALT_LOCKED) | 337 getActive(state, META_SYM_ON, META_SYM_ON, META_SYM_LOCKED); 338 } 339 340 /** 341 * Gets the state of a particular meta key. 342 * 343 * @param state the current state bits. 344 * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON 345 * 346 * @return 0 if inactive, 1 if active, 2 if locked. 347 */ 348 public static final int getMetaState(long state, int meta) { 349 switch (meta) { 350 case META_SHIFT_ON: 351 return getActive(state, meta, 1, 2); 352 353 case META_ALT_ON: 354 return getActive(state, meta, 1, 2); 355 356 case META_SYM_ON: 357 return getActive(state, meta, 1, 2); 358 359 default: 360 return 0; 361 } 362 } 363 364 private static int getActive(long state, int meta, int on, int lock) { 365 if ((state&(meta<<LOCKED_SHIFT)) != 0) { 366 return lock; 367 } else if ((state&meta) != 0) { 368 return on; 369 } else { 370 return 0; 371 } 372 } 373 374 /** 375 * Call this method after you handle a keypress so that the meta 376 * state will be reset to unshifted (if it is not still down) 377 * or primed to be reset to unshifted (once it is released). Takes 378 * the current state, returns the new state. 379 */ 380 public static long adjustMetaAfterKeypress(long state) { 381 state = adjust(state, META_SHIFT_ON, META_SHIFT_MASK); 382 state = adjust(state, META_ALT_ON, META_ALT_MASK); 383 state = adjust(state, META_SYM_ON, META_SYM_MASK); 384 return state; 385 } 386 387 private static long adjust(long state, int what, long mask) { 388 if ((state&(((long)what)<<PRESSED_SHIFT)) != 0) 389 return (state&~mask) | what | ((long)what)<<USED_SHIFT; 390 else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0) 391 return state & ~mask; 392 return state; 393 } 394 395 /** 396 * Handles presses of the meta keys. 397 */ 398 public static long handleKeyDown(long state, int keyCode, KeyEvent event) { 399 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 400 return press(state, META_SHIFT_ON, META_SHIFT_MASK); 401 } 402 403 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 404 || keyCode == KeyEvent.KEYCODE_NUM) { 405 return press(state, META_ALT_ON, META_ALT_MASK); 406 } 407 408 if (keyCode == KeyEvent.KEYCODE_SYM) { 409 return press(state, META_SYM_ON, META_SYM_MASK); 410 } 411 412 return state; 413 } 414 415 private static long press(long state, int what, long mask) { 416 if ((state&(((long)what)<<PRESSED_SHIFT)) != 0) 417 ; // repeat before use 418 else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0) 419 state = (state&~mask) | what | (((long)what) << LOCKED_SHIFT); 420 else if ((state&(((long)what)<<USED_SHIFT)) != 0) 421 ; // repeat after use 422 else if ((state&(((long)what)<<LOCKED_SHIFT)) != 0) 423 state = state&~mask; 424 else 425 state = state | what | (((long)what)<<PRESSED_SHIFT); 426 return state; 427 } 428 429 /** 430 * Handles release of the meta keys. 431 */ 432 public static long handleKeyUp(long state, int keyCode, KeyEvent event) { 433 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 434 return release(state, META_SHIFT_ON, META_SHIFT_MASK); 435 } 436 437 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT 438 || keyCode == KeyEvent.KEYCODE_NUM) { 439 return release(state, META_ALT_ON, META_ALT_MASK); 440 } 441 442 if (keyCode == KeyEvent.KEYCODE_SYM) { 443 return release(state, META_SYM_ON, META_SYM_MASK); 444 } 445 446 return state; 447 } 448 449 private static long release(long state, int what, long mask) { 450 if ((state&(((long)what)<<USED_SHIFT)) != 0) 451 state = state&~mask; 452 else if ((state&(((long)what)<<PRESSED_SHIFT)) != 0) 453 state = state | what | (((long)what)<<RELEASED_SHIFT); 454 return state; 455 } 456 457 public long clearMetaKeyState(long state, int which) { 458 if ((which&META_SHIFT_ON) != 0) 459 state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK); 460 if ((which&META_ALT_ON) != 0) 461 state = resetLock(state, META_ALT_ON, META_ALT_MASK); 462 if ((which&META_SYM_ON) != 0) 463 state = resetLock(state, META_SYM_ON, META_SYM_MASK); 464 return state; 465 } 466 467 /** 468 * The meta key has been pressed but has not yet been used. 469 */ 470 private static final int PRESSED = 471 Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT); 472 473 /** 474 * The meta key has been pressed and released but has still 475 * not yet been used. 476 */ 477 private static final int RELEASED = 478 Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT); 479 480 /** 481 * The meta key has been pressed and used but has not yet been released. 482 */ 483 private static final int USED = 484 Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT); 485 486 /** 487 * The meta key has been pressed and released without use, and then 488 * pressed again; it may also have been released again. 489 */ 490 private static final int LOCKED = 491 Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT); 492 } 493 494