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     * &lt;receiver android:name=".InputDeviceReceiver">
81     *     &lt;intent-filter>
82     *         &lt;action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" />
83     *     &lt;/intent-filter>
84     *     &lt;meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS"
85     *             android:resource="@xml/keyboard_layouts" />
86     * &lt;/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>&lt;keyboard-layouts></code> that
91     * contains zero or more <code>&lt;keyboard-layout></code> elements.
92     * Each <code>&lt;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     * &lt;?xml version="1.0" encoding="utf-8"?>
96     * &lt;keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
97     *     &lt;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     * &lt;/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