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