InputManager.java revision 2f0957607411b99810226ad38d59cf18718b86d0
1/* 2 * Copyright (C) 2012 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.hardware.input; 18 19import android.annotation.SdkConstant; 20import android.annotation.SdkConstant.SdkConstantType; 21import android.content.Context; 22import android.os.Binder; 23import android.os.Handler; 24import android.os.IBinder; 25import android.os.Looper; 26import android.os.Message; 27import android.os.RemoteException; 28import android.os.ServiceManager; 29import android.os.Vibrator; 30import android.provider.Settings; 31import android.provider.Settings.SettingNotFoundException; 32import android.util.Log; 33import android.util.SparseArray; 34import android.view.InputDevice; 35import android.view.InputEvent; 36 37import java.util.ArrayList; 38 39/** 40 * Provides information about input devices and available key layouts. 41 * <p> 42 * Get an instance of this class by calling 43 * {@link android.content.Context#getSystemService(java.lang.String) 44 * Context.getSystemService()} with the argument 45 * {@link android.content.Context#INPUT_SERVICE}. 46 * </p> 47 */ 48public final class InputManager { 49 private static final String TAG = "InputManager"; 50 private static final boolean DEBUG = false; 51 52 private static final int MSG_DEVICE_ADDED = 1; 53 private static final int MSG_DEVICE_REMOVED = 2; 54 private static final int MSG_DEVICE_CHANGED = 3; 55 56 private static InputManager sInstance; 57 58 private final IInputManager mIm; 59 60 // Guarded by mInputDevicesLock 61 private final Object mInputDevicesLock = new Object(); 62 private SparseArray<InputDevice> mInputDevices; 63 private InputDevicesChangedListener mInputDevicesChangedListener; 64 private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = 65 new ArrayList<InputDeviceListenerDelegate>(); 66 67 /** 68 * Broadcast Action: Query available keyboard layouts. 69 * <p> 70 * The input manager service locates available keyboard layouts 71 * by querying broadcast receivers that are registered for this action. 72 * An application can offer additional keyboard layouts to the user 73 * by declaring a suitable broadcast receiver in its manifest. 74 * </p><p> 75 * Here is an example broadcast receiver declaration that an application 76 * might include in its AndroidManifest.xml to advertise keyboard layouts. 77 * The meta-data specifies a resource that contains a description of each keyboard 78 * layout that is provided by the application. 79 * <pre><code> 80 * <receiver android:name=".InputDeviceReceiver"> 81 * <intent-filter> 82 * <action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" /> 83 * </intent-filter> 84 * <meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS" 85 * android:resource="@xml/keyboard_layouts" /> 86 * </receiver> 87 * </code></pre> 88 * </p><p> 89 * In the above example, the <code>@xml/keyboard_layouts</code> resource refers to 90 * an XML resource whose root element is <code><keyboard-layouts></code> that 91 * contains zero or more <code><keyboard-layout></code> elements. 92 * Each <code><keyboard-layout></code> element specifies the name, label, and location 93 * of a key character map for a particular keyboard layout. 94 * <pre></code> 95 * <?xml version="1.0" encoding="utf-8"?> 96 * <keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android"> 97 * <keyboard-layout android:name="keyboard_layout_english_us" 98 * android:label="@string/keyboard_layout_english_us_label" 99 * android:keyboardLayout="@raw/keyboard_layout_english_us" /> 100 * </keyboard-layouts> 101 * </p><p> 102 * The <code>android:name</code> attribute specifies an identifier by which 103 * the keyboard layout will be known in the package. 104 * The <code>android:label</code> attributes specifies a human-readable descriptive 105 * label to describe the keyboard layout in the user interface, such as "English (US)". 106 * The <code>android:keyboardLayout</code> attribute refers to a 107 * <a href="http://source.android.com/tech/input/key-character-map-files.html"> 108 * key character map</a> resource that defines the keyboard layout. 109 * </p> 110 */ 111 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 112 public static final String ACTION_QUERY_KEYBOARD_LAYOUTS = 113 "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS"; 114 115 /** 116 * Metadata Key: Keyboard layout metadata associated with 117 * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS}. 118 * <p> 119 * Specifies the resource id of a XML resource that describes the keyboard 120 * layouts that are provided by the application. 121 * </p> 122 */ 123 public static final String META_DATA_KEYBOARD_LAYOUTS = 124 "android.hardware.input.metadata.KEYBOARD_LAYOUTS"; 125 126 /** 127 * Pointer Speed: The minimum (slowest) pointer speed (-7). 128 * @hide 129 */ 130 public static final int MIN_POINTER_SPEED = -7; 131 132 /** 133 * Pointer Speed: The maximum (fastest) pointer speed (7). 134 * @hide 135 */ 136 public static final int MAX_POINTER_SPEED = 7; 137 138 /** 139 * Pointer Speed: The default pointer speed (0). 140 * @hide 141 */ 142 public static final int DEFAULT_POINTER_SPEED = 0; 143 144 /** 145 * Input Event Injection Synchronization Mode: None. 146 * Never blocks. Injection is asynchronous and is assumed always to be successful. 147 * @hide 148 */ 149 public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; // see InputDispatcher.h 150 151 /** 152 * Input Event Injection Synchronization Mode: Wait for result. 153 * Waits for previous events to be dispatched so that the input dispatcher can 154 * determine whether input event injection will be permitted based on the current 155 * input focus. Does not wait for the input event to finish being handled 156 * by the application. 157 * @hide 158 */ 159 public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; // see InputDispatcher.h 160 161 /** 162 * Input Event Injection Synchronization Mode: Wait for finish. 163 * Waits for the event to be delivered to the application and handled. 164 * @hide 165 */ 166 public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; // see InputDispatcher.h 167 168 private InputManager(IInputManager im) { 169 mIm = im; 170 } 171 172 /** 173 * Gets an instance of the input manager. 174 * 175 * @return The input manager instance. 176 * 177 * @hide 178 */ 179 public static InputManager getInstance() { 180 synchronized (InputManager.class) { 181 if (sInstance == null) { 182 IBinder b = ServiceManager.getService(Context.INPUT_SERVICE); 183 sInstance = new InputManager(IInputManager.Stub.asInterface(b)); 184 } 185 return sInstance; 186 } 187 } 188 189 /** 190 * Gets information about the input device with the specified id. 191 * @param id The device id. 192 * @return The input device or null if not found. 193 */ 194 public InputDevice getInputDevice(int id) { 195 synchronized (mInputDevicesLock) { 196 populateInputDevicesLocked(); 197 198 int index = mInputDevices.indexOfKey(id); 199 if (index < 0) { 200 return null; 201 } 202 203 InputDevice inputDevice = mInputDevices.valueAt(index); 204 if (inputDevice == null) { 205 try { 206 inputDevice = mIm.getInputDevice(id); 207 } catch (RemoteException ex) { 208 throw new RuntimeException("Could not get input device information.", ex); 209 } 210 } 211 mInputDevices.setValueAt(index, inputDevice); 212 return inputDevice; 213 } 214 } 215 216 /** 217 * Gets the ids of all input devices in the system. 218 * @return The input device ids. 219 */ 220 public int[] getInputDeviceIds() { 221 synchronized (mInputDevicesLock) { 222 populateInputDevicesLocked(); 223 224 final int count = mInputDevices.size(); 225 final int[] ids = new int[count]; 226 for (int i = 0; i < count; i++) { 227 ids[i] = mInputDevices.keyAt(i); 228 } 229 return ids; 230 } 231 } 232 233 /** 234 * Registers an input device listener to receive notifications about when 235 * input devices are added, removed or changed. 236 * 237 * @param listener The listener to register. 238 * @param handler The handler on which the listener should be invoked, or null 239 * if the listener should be invoked on the calling thread's looper. 240 * 241 * @see #unregisterInputDeviceListener 242 */ 243 public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) { 244 if (listener == null) { 245 throw new IllegalArgumentException("listener must not be null"); 246 } 247 248 synchronized (mInputDevicesLock) { 249 int index = findInputDeviceListenerLocked(listener); 250 if (index < 0) { 251 mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler)); 252 } 253 } 254 } 255 256 /** 257 * Unregisters an input device listener. 258 * 259 * @param listener The listener to unregister. 260 * 261 * @see #registerInputDeviceListener 262 */ 263 public void unregisterInputDeviceListener(InputDeviceListener listener) { 264 if (listener == null) { 265 throw new IllegalArgumentException("listener must not be null"); 266 } 267 268 synchronized (mInputDevicesLock) { 269 int index = findInputDeviceListenerLocked(listener); 270 if (index >= 0) { 271 InputDeviceListenerDelegate d = mInputDeviceListeners.get(index); 272 d.removeCallbacksAndMessages(null); 273 mInputDeviceListeners.remove(index); 274 } 275 } 276 } 277 278 private int findInputDeviceListenerLocked(InputDeviceListener listener) { 279 final int numListeners = mInputDeviceListeners.size(); 280 for (int i = 0; i < numListeners; i++) { 281 if (mInputDeviceListeners.get(i).mListener == listener) { 282 return i; 283 } 284 } 285 return -1; 286 } 287 288 /** 289 * Gets information about all supported keyboard layouts. 290 * <p> 291 * The input manager consults the built-in keyboard layouts as well 292 * as all keyboard layouts advertised by applications using a 293 * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver. 294 * </p> 295 * 296 * @return A list of all supported keyboard layouts. 297 * 298 * @hide 299 */ 300 public KeyboardLayout[] getKeyboardLayouts() { 301 try { 302 return mIm.getKeyboardLayouts(); 303 } catch (RemoteException ex) { 304 Log.w(TAG, "Could not get list of keyboard layout informations.", ex); 305 return new KeyboardLayout[0]; 306 } 307 } 308 309 /** 310 * Gets the keyboard layout with the specified descriptor. 311 * 312 * @param keyboardLayoutDescriptor The keyboard layout descriptor, as returned by 313 * {@link KeyboardLayout#getDescriptor()}. 314 * @return The keyboard layout, or null if it could not be loaded. 315 * 316 * @hide 317 */ 318 public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) { 319 if (keyboardLayoutDescriptor == null) { 320 throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); 321 } 322 323 try { 324 return mIm.getKeyboardLayout(keyboardLayoutDescriptor); 325 } catch (RemoteException ex) { 326 Log.w(TAG, "Could not get keyboard layout information.", ex); 327 return null; 328 } 329 } 330 331 /** 332 * Gets the keyboard layout descriptor for the specified input device. 333 * 334 * @param inputDeviceDescriptor The input device descriptor. 335 * @return The keyboard layout descriptor, or null if unknown or if the default 336 * keyboard layout will be used. 337 * 338 * @hide 339 */ 340 public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) { 341 if (inputDeviceDescriptor == null) { 342 throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); 343 } 344 345 try { 346 return mIm.getKeyboardLayoutForInputDevice(inputDeviceDescriptor); 347 } catch (RemoteException ex) { 348 Log.w(TAG, "Could not get keyboard layout for input device.", ex); 349 return null; 350 } 351 } 352 353 /** 354 * Sets the keyboard layout descriptor for the specified input device. 355 * <p> 356 * This method may have the side-effect of causing the input device in question 357 * to be reconfigured. 358 * </p> 359 * 360 * @param inputDeviceDescriptor The input device descriptor. 361 * @param keyboardLayoutDescriptor The keyboard layout descriptor, or null to remove 362 * the mapping so that the default keyboard layout will be used for the input device. 363 * 364 * @hide 365 */ 366 public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor, 367 String keyboardLayoutDescriptor) { 368 if (inputDeviceDescriptor == null) { 369 throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); 370 } 371 372 try { 373 mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor); 374 } catch (RemoteException ex) { 375 Log.w(TAG, "Could not set keyboard layout for input device.", ex); 376 } 377 } 378 379 /** 380 * Gets the mouse pointer speed. 381 * <p> 382 * Only returns the permanent mouse pointer speed. Ignores any temporary pointer 383 * speed set by {@link #tryPointerSpeed}. 384 * </p> 385 * 386 * @param context The application context. 387 * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and 388 * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. 389 * 390 * @hide 391 */ 392 public int getPointerSpeed(Context context) { 393 int speed = DEFAULT_POINTER_SPEED; 394 try { 395 speed = Settings.System.getInt(context.getContentResolver(), 396 Settings.System.POINTER_SPEED); 397 } catch (SettingNotFoundException snfe) { 398 } 399 return speed; 400 } 401 402 /** 403 * Sets the mouse pointer speed. 404 * <p> 405 * Requires {@link android.Manifest.permissions.WRITE_SETTINGS}. 406 * </p> 407 * 408 * @param context The application context. 409 * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and 410 * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. 411 * 412 * @hide 413 */ 414 public void setPointerSpeed(Context context, int speed) { 415 if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) { 416 throw new IllegalArgumentException("speed out of range"); 417 } 418 419 Settings.System.putInt(context.getContentResolver(), 420 Settings.System.POINTER_SPEED, speed); 421 } 422 423 /** 424 * Changes the mouse pointer speed temporarily, but does not save the setting. 425 * <p> 426 * Requires {@link android.Manifest.permission.SET_POINTER_SPEED}. 427 * </p> 428 * 429 * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and 430 * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. 431 * 432 * @hide 433 */ 434 public void tryPointerSpeed(int speed) { 435 if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) { 436 throw new IllegalArgumentException("speed out of range"); 437 } 438 439 try { 440 mIm.tryPointerSpeed(speed); 441 } catch (RemoteException ex) { 442 Log.w(TAG, "Could not set temporary pointer speed.", ex); 443 } 444 } 445 446 /** 447 * Queries the framework about whether any physical keys exist on the 448 * any keyboard attached to the device that are capable of producing the given 449 * array of key codes. 450 * 451 * @param keyCodes The array of key codes to query. 452 * @return A new array of the same size as the key codes array whose elements 453 * are set to true if at least one attached keyboard supports the corresponding key code 454 * at the same index in the key codes array. 455 * 456 * @hide 457 */ 458 public boolean[] deviceHasKeys(int[] keyCodes) { 459 boolean[] ret = new boolean[keyCodes.length]; 460 try { 461 mIm.hasKeys(-1, InputDevice.SOURCE_ANY, keyCodes, ret); 462 } catch (RemoteException e) { 463 // no fallback; just return the empty array 464 } 465 return ret; 466 } 467 468 /** 469 * Injects an input event into the event system on behalf of an application. 470 * The synchronization mode determines whether the method blocks while waiting for 471 * input injection to proceed. 472 * <p> 473 * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into 474 * windows that are owned by other applications. 475 * </p><p> 476 * Make sure you correctly set the event time and input source of the event 477 * before calling this method. 478 * </p> 479 * 480 * @param event The event to inject. 481 * @param mode The synchronization mode. One of: 482 * {@link #INJECT_INPUT_EVENT_MODE_ASYNC}, 483 * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or 484 * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}. 485 * @return True if input event injection succeeded. 486 * 487 * @hide 488 */ 489 public boolean injectInputEvent(InputEvent event, int mode) { 490 if (event == null) { 491 throw new IllegalArgumentException("event must not be null"); 492 } 493 if (mode != INJECT_INPUT_EVENT_MODE_ASYNC 494 && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH 495 && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) { 496 throw new IllegalArgumentException("mode is invalid"); 497 } 498 499 try { 500 return mIm.injectInputEvent(event, mode); 501 } catch (RemoteException ex) { 502 return false; 503 } 504 } 505 506 private void populateInputDevicesLocked() { 507 if (mInputDevicesChangedListener == null) { 508 final InputDevicesChangedListener listener = new InputDevicesChangedListener(); 509 try { 510 mIm.registerInputDevicesChangedListener(listener); 511 } catch (RemoteException ex) { 512 throw new RuntimeException( 513 "Could not get register input device changed listener", ex); 514 } 515 mInputDevicesChangedListener = listener; 516 } 517 518 if (mInputDevices == null) { 519 final int[] ids; 520 try { 521 ids = mIm.getInputDeviceIds(); 522 } catch (RemoteException ex) { 523 throw new RuntimeException("Could not get input device ids.", ex); 524 } 525 526 mInputDevices = new SparseArray<InputDevice>(); 527 for (int i = 0; i < ids.length; i++) { 528 mInputDevices.put(ids[i], null); 529 } 530 } 531 } 532 533 private void onInputDevicesChanged(int[] deviceIdAndGeneration) { 534 if (DEBUG) { 535 Log.d(TAG, "Received input devices changed."); 536 } 537 538 synchronized (mInputDevicesLock) { 539 for (int i = mInputDevices.size(); --i > 0; ) { 540 final int deviceId = mInputDevices.keyAt(i); 541 if (!containsDeviceId(deviceIdAndGeneration, deviceId)) { 542 if (DEBUG) { 543 Log.d(TAG, "Device removed: " + deviceId); 544 } 545 mInputDevices.removeAt(i); 546 sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId); 547 } 548 } 549 550 for (int i = 0; i < deviceIdAndGeneration.length; i += 2) { 551 final int deviceId = deviceIdAndGeneration[i]; 552 int index = mInputDevices.indexOfKey(deviceId); 553 if (index >= 0) { 554 final InputDevice device = mInputDevices.valueAt(index); 555 if (device != null) { 556 final int generation = deviceIdAndGeneration[i + 1]; 557 if (device.getGeneration() != generation) { 558 if (DEBUG) { 559 Log.d(TAG, "Device changed: " + deviceId); 560 } 561 mInputDevices.setValueAt(index, null); 562 sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId); 563 } 564 } 565 } else { 566 if (DEBUG) { 567 Log.d(TAG, "Device added: " + deviceId); 568 } 569 mInputDevices.put(deviceId, null); 570 sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId); 571 } 572 } 573 } 574 } 575 576 private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) { 577 final int numListeners = mInputDeviceListeners.size(); 578 for (int i = 0; i < numListeners; i++) { 579 InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i); 580 listener.sendMessage(listener.obtainMessage(what, deviceId, 0)); 581 } 582 } 583 584 private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) { 585 for (int i = 0; i < deviceIdAndGeneration.length; i += 2) { 586 if (deviceIdAndGeneration[i] == deviceId) { 587 return true; 588 } 589 } 590 return false; 591 } 592 593 /** 594 * Gets a vibrator service associated with an input device, assuming it has one. 595 * @return The vibrator, never null. 596 * @hide 597 */ 598 public Vibrator getInputDeviceVibrator(int deviceId) { 599 return new InputDeviceVibrator(deviceId); 600 } 601 602 /** 603 * Listens for changes in input devices. 604 */ 605 public interface InputDeviceListener { 606 /** 607 * Called whenever an input device has been added to the system. 608 * Use {@link InputManager#getInputDevice} to get more information about the device. 609 * 610 * @param deviceId The id of the input device that was added. 611 */ 612 void onInputDeviceAdded(int deviceId); 613 614 /** 615 * Called whenever an input device has been removed from the system. 616 * 617 * @param deviceId The id of the input device that was removed. 618 */ 619 void onInputDeviceRemoved(int deviceId); 620 621 /** 622 * Called whenever the properties of an input device have changed since they 623 * were last queried. Use {@link InputManager#getInputDevice} to get 624 * a fresh {@link InputDevice} object with the new properties. 625 * 626 * @param deviceId The id of the input device that changed. 627 */ 628 void onInputDeviceChanged(int deviceId); 629 } 630 631 private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub { 632 @Override 633 public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException { 634 InputManager.this.onInputDevicesChanged(deviceIdAndGeneration); 635 } 636 } 637 638 private static final class InputDeviceListenerDelegate extends Handler { 639 public final InputDeviceListener mListener; 640 641 public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) { 642 super(handler != null ? handler.getLooper() : Looper.myLooper()); 643 mListener = listener; 644 } 645 646 @Override 647 public void handleMessage(Message msg) { 648 switch (msg.what) { 649 case MSG_DEVICE_ADDED: 650 mListener.onInputDeviceAdded(msg.arg1); 651 break; 652 case MSG_DEVICE_REMOVED: 653 mListener.onInputDeviceRemoved(msg.arg1); 654 break; 655 case MSG_DEVICE_CHANGED: 656 mListener.onInputDeviceChanged(msg.arg1); 657 break; 658 } 659 } 660 } 661 662 private final class InputDeviceVibrator extends Vibrator { 663 private final int mDeviceId; 664 private final Binder mToken; 665 666 public InputDeviceVibrator(int deviceId) { 667 mDeviceId = deviceId; 668 mToken = new Binder(); 669 } 670 671 @Override 672 public boolean hasVibrator() { 673 return true; 674 } 675 676 @Override 677 public void vibrate(long milliseconds) { 678 vibrate(new long[] { 0, milliseconds}, -1); 679 } 680 681 @Override 682 public void vibrate(long[] pattern, int repeat) { 683 if (repeat >= pattern.length) { 684 throw new ArrayIndexOutOfBoundsException(); 685 } 686 try { 687 mIm.vibrate(mDeviceId, pattern, repeat, mToken); 688 } catch (RemoteException ex) { 689 Log.w(TAG, "Failed to vibrate.", ex); 690 } 691 } 692 693 @Override 694 public void cancel() { 695 try { 696 mIm.cancelVibrate(mDeviceId, mToken); 697 } catch (RemoteException ex) { 698 Log.w(TAG, "Failed to cancel vibration.", ex); 699 } 700 } 701 } 702} 703