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