VoiceInteractionSession.java revision a2c076d54048258cf88ab14551ce5fdf5a09c6e8
1/**
2 * Copyright (C) 2014 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.service.voice;
18
19import android.app.Dialog;
20import android.app.Instrumentation;
21import android.content.Context;
22import android.content.Intent;
23import android.content.res.TypedArray;
24import android.graphics.Rect;
25import android.graphics.Region;
26import android.inputmethodservice.SoftInputWindow;
27import android.os.Binder;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Message;
32import android.os.RemoteException;
33import android.util.ArrayMap;
34import android.util.Log;
35import android.view.Gravity;
36import android.view.KeyEvent;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.ViewTreeObserver;
41import android.view.WindowManager;
42import android.widget.FrameLayout;
43import com.android.internal.app.IVoiceInteractionManagerService;
44import com.android.internal.app.IVoiceInteractor;
45import com.android.internal.app.IVoiceInteractorCallback;
46import com.android.internal.app.IVoiceInteractorRequest;
47import com.android.internal.os.HandlerCaller;
48import com.android.internal.os.SomeArgs;
49
50import java.lang.ref.WeakReference;
51
52import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
53import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
54
55/**
56 * An active voice interaction session, providing a facility for the implementation
57 * to interact with the user in the voice interaction layer.  This interface is no shown
58 * by default, but you can request that it be shown with {@link #showWindow()}, which
59 * will result in a later call to {@link #onCreateContentView()} in which the UI can be
60 * built
61 *
62 * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
63 * when done.  It can also initiate voice interactions with applications by calling
64 * {@link #startVoiceActivity}</p>.
65 */
66public abstract class VoiceInteractionSession implements KeyEvent.Callback {
67    static final String TAG = "VoiceInteractionSession";
68    static final boolean DEBUG = true;
69
70    final Context mContext;
71    final HandlerCaller mHandlerCaller;
72
73    final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
74
75    IVoiceInteractionManagerService mSystemService;
76    IBinder mToken;
77
78    int mTheme = 0;
79    LayoutInflater mInflater;
80    TypedArray mThemeAttrs;
81    View mRootView;
82    FrameLayout mContentFrame;
83    SoftInputWindow mWindow;
84
85    boolean mInitialized;
86    boolean mWindowAdded;
87    boolean mWindowVisible;
88    boolean mWindowWasVisible;
89    boolean mInShowWindow;
90
91    final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
92
93    final Insets mTmpInsets = new Insets();
94    final int[] mTmpLocation = new int[2];
95
96    final WeakReference<VoiceInteractionSession> mWeakRef
97            = new WeakReference<VoiceInteractionSession>(this);
98
99    final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
100        @Override
101        public IVoiceInteractorRequest startConfirmation(String callingPackage,
102                IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) {
103            Request request = newRequest(callback);
104            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION,
105                    new Caller(callingPackage, Binder.getCallingUid()), request,
106                    prompt, extras));
107            return request.mInterface;
108        }
109
110        @Override
111        public IVoiceInteractorRequest startAbortVoice(String callingPackage,
112                IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
113            Request request = newRequest(callback);
114            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE,
115                    new Caller(callingPackage, Binder.getCallingUid()), request,
116                    message, extras));
117            return request.mInterface;
118        }
119
120        @Override
121        public IVoiceInteractorRequest startCommand(String callingPackage,
122                IVoiceInteractorCallback callback, String command, Bundle extras) {
123            Request request = newRequest(callback);
124            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND,
125                    new Caller(callingPackage, Binder.getCallingUid()), request,
126                    command, extras));
127            return request.mInterface;
128        }
129
130        @Override
131        public boolean[] supportsCommands(String callingPackage, String[] commands) {
132            Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS,
133                    0, new Caller(callingPackage, Binder.getCallingUid()), commands);
134            SomeArgs args = mHandlerCaller.sendMessageAndWait(msg);
135            if (args != null) {
136                boolean[] res = (boolean[])args.arg1;
137                args.recycle();
138                return res;
139            }
140            return new boolean[commands.length];
141        }
142    };
143
144    final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
145        @Override
146        public void taskStarted(Intent intent, int taskId) {
147            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED,
148                    taskId, intent));
149        }
150
151        @Override
152        public void taskFinished(Intent intent, int taskId) {
153            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_FINISHED,
154                    taskId, intent));
155        }
156
157        @Override
158        public void closeSystemDialogs() {
159            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CLOSE_SYSTEM_DIALOGS));
160        }
161
162        @Override
163        public void destroy() {
164            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY));
165        }
166    };
167
168    public static class Request {
169        final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
170            @Override
171            public void cancel() throws RemoteException {
172                VoiceInteractionSession session = mSession.get();
173                if (session != null) {
174                    session.mHandlerCaller.sendMessage(
175                            session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
176                }
177            }
178        };
179        final IVoiceInteractorCallback mCallback;
180        final WeakReference<VoiceInteractionSession> mSession;
181
182        Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) {
183            mCallback = callback;
184            mSession = session.mWeakRef;
185        }
186
187        void finishRequest() {
188            VoiceInteractionSession session = mSession.get();
189            if (session == null) {
190                throw new IllegalStateException("VoiceInteractionSession has been destroyed");
191            }
192            Request req = session.removeRequest(mInterface.asBinder());
193            if (req == null) {
194                throw new IllegalStateException("Request not active: " + this);
195            } else if (req != this) {
196                throw new IllegalStateException("Current active request " + req
197                        + " not same as calling request " + this);
198            }
199        }
200
201        public void sendConfirmResult(boolean confirmed, Bundle result) {
202            try {
203                if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
204                        + " confirmed=" + confirmed + " result=" + result);
205                finishRequest();
206                mCallback.deliverConfirmationResult(mInterface, confirmed, result);
207            } catch (RemoteException e) {
208            }
209        }
210
211        public void sendAbortVoiceResult(Bundle result) {
212            try {
213                if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
214                        + " result=" + result);
215                finishRequest();
216                mCallback.deliverAbortVoiceResult(mInterface, result);
217            } catch (RemoteException e) {
218            }
219        }
220
221        public void sendCommandResult(boolean complete, Bundle result) {
222            try {
223                if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
224                        + " result=" + result);
225                finishRequest();
226                mCallback.deliverCommandResult(mInterface, complete, result);
227            } catch (RemoteException e) {
228            }
229        }
230
231        public void sendCancelResult() {
232            try {
233                if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
234                finishRequest();
235                mCallback.deliverCancel(mInterface);
236            } catch (RemoteException e) {
237            }
238        }
239    }
240
241    public static class Caller {
242        final String packageName;
243        final int uid;
244
245        Caller(String _packageName, int _uid) {
246            packageName = _packageName;
247            uid = _uid;
248        }
249    }
250
251    static final int MSG_START_CONFIRMATION = 1;
252    static final int MSG_START_ABORT_VOICE = 2;
253    static final int MSG_START_COMMAND = 3;
254    static final int MSG_SUPPORTS_COMMANDS = 4;
255    static final int MSG_CANCEL = 5;
256
257    static final int MSG_TASK_STARTED = 100;
258    static final int MSG_TASK_FINISHED = 101;
259    static final int MSG_CLOSE_SYSTEM_DIALOGS = 102;
260    static final int MSG_DESTROY = 103;
261
262    class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback {
263        @Override
264        public void executeMessage(Message msg) {
265            SomeArgs args;
266            switch (msg.what) {
267                case MSG_START_CONFIRMATION:
268                    args = (SomeArgs)msg.obj;
269                    if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface
270                            + " prompt=" + args.arg3 + " extras=" + args.arg4);
271                    onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
272                            (Bundle)args.arg4);
273                    break;
274                case MSG_START_ABORT_VOICE:
275                    args = (SomeArgs)msg.obj;
276                    if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface
277                            + " message=" + args.arg3 + " extras=" + args.arg4);
278                    onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3,
279                            (Bundle) args.arg4);
280                    break;
281                case MSG_START_COMMAND:
282                    args = (SomeArgs)msg.obj;
283                    if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface
284                            + " command=" + args.arg3 + " extras=" + args.arg4);
285                    onCommand((Caller) args.arg1, (Request) args.arg2, (String) args.arg3,
286                            (Bundle) args.arg4);
287                    break;
288                case MSG_SUPPORTS_COMMANDS:
289                    args = (SomeArgs)msg.obj;
290                    if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2);
291                    args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2);
292                    break;
293                case MSG_CANCEL:
294                    args = (SomeArgs)msg.obj;
295                    if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface);
296                    onCancel((Request)args.arg1);
297                    break;
298                case MSG_TASK_STARTED:
299                    if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj
300                            + " taskId=" + msg.arg1);
301                    onTaskStarted((Intent) msg.obj, msg.arg1);
302                    break;
303                case MSG_TASK_FINISHED:
304                    if (DEBUG) Log.d(TAG, "onTaskFinished: intent=" + msg.obj
305                            + " taskId=" + msg.arg1);
306                    onTaskFinished((Intent) msg.obj, msg.arg1);
307                    break;
308                case MSG_CLOSE_SYSTEM_DIALOGS:
309                    if (DEBUG) Log.d(TAG, "onCloseSystemDialogs");
310                    onCloseSystemDialogs();
311                    break;
312                case MSG_DESTROY:
313                    if (DEBUG) Log.d(TAG, "doDestroy");
314                    doDestroy();
315                    break;
316            }
317        }
318
319        @Override
320        public void onBackPressed() {
321            VoiceInteractionSession.this.onBackPressed();
322        }
323    }
324
325    final MyCallbacks mCallbacks = new MyCallbacks();
326
327    /**
328     * Information about where interesting parts of the input method UI appear.
329     */
330    public static final class Insets {
331        /**
332         * This is the part of the UI that is the main content.  It is
333         * used to determine the basic space needed, to resize/pan the
334         * application behind.  It is assumed that this inset does not
335         * change very much, since any change will cause a full resize/pan
336         * of the application behind.  This value is relative to the top edge
337         * of the input method window.
338         */
339        public final Rect contentInsets = new Rect();
340
341        /**
342         * This is the region of the UI that is touchable.  It is used when
343         * {@link #touchableInsets} is set to {@link #TOUCHABLE_INSETS_REGION}.
344         * The region should be specified relative to the origin of the window frame.
345         */
346        public final Region touchableRegion = new Region();
347
348        /**
349         * Option for {@link #touchableInsets}: the entire window frame
350         * can be touched.
351         */
352        public static final int TOUCHABLE_INSETS_FRAME
353                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
354
355        /**
356         * Option for {@link #touchableInsets}: the area inside of
357         * the content insets can be touched.
358         */
359        public static final int TOUCHABLE_INSETS_CONTENT
360                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
361
362        /**
363         * Option for {@link #touchableInsets}: the region specified by
364         * {@link #touchableRegion} can be touched.
365         */
366        public static final int TOUCHABLE_INSETS_REGION
367                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
368
369        /**
370         * Determine which area of the window is touchable by the user.  May
371         * be one of: {@link #TOUCHABLE_INSETS_FRAME},
372         * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_REGION}.
373         */
374        public int touchableInsets;
375    }
376
377    final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
378            new ViewTreeObserver.OnComputeInternalInsetsListener() {
379        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
380            onComputeInsets(mTmpInsets);
381            info.contentInsets.set(mTmpInsets.contentInsets);
382            info.visibleInsets.set(mTmpInsets.contentInsets);
383            info.touchableRegion.set(mTmpInsets.touchableRegion);
384            info.setTouchableInsets(mTmpInsets.touchableInsets);
385        }
386    };
387
388    public VoiceInteractionSession(Context context) {
389        this(context, new Handler());
390    }
391
392    public VoiceInteractionSession(Context context, Handler handler) {
393        mContext = context;
394        mHandlerCaller = new HandlerCaller(context, handler.getLooper(),
395                mCallbacks, true);
396    }
397
398    Request newRequest(IVoiceInteractorCallback callback) {
399        synchronized (this) {
400            Request req = new Request(callback, this);
401            mActiveRequests.put(req.mInterface.asBinder(), req);
402            return req;
403        }
404    }
405
406    Request removeRequest(IBinder reqInterface) {
407        synchronized (this) {
408            Request req = mActiveRequests.get(reqInterface);
409            if (req != null) {
410                mActiveRequests.remove(req);
411            }
412            return req;
413        }
414    }
415
416    void doCreate(IVoiceInteractionManagerService service, IBinder token, Bundle args) {
417        mSystemService = service;
418        mToken = token;
419        onCreate(args);
420    }
421
422    void doDestroy() {
423        if (mInitialized) {
424            mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
425                    mInsetsComputer);
426            if (mWindowAdded) {
427                mWindow.dismiss();
428                mWindowAdded = false;
429            }
430            mInitialized = false;
431        }
432    }
433
434    void initViews() {
435        mInitialized = true;
436
437        mThemeAttrs = mContext.obtainStyledAttributes(android.R.styleable.VoiceInteractionSession);
438        mRootView = mInflater.inflate(
439                com.android.internal.R.layout.voice_interaction_session, null);
440        mRootView.setSystemUiVisibility(
441                View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
442        mWindow.setContentView(mRootView);
443        mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
444
445        mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content);
446    }
447
448    public void showWindow() {
449        if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded
450                + " mWindowVisible=" + mWindowVisible);
451
452        if (mInShowWindow) {
453            Log.w(TAG, "Re-entrance in to showWindow");
454            return;
455        }
456
457        try {
458            mInShowWindow = true;
459            if (!mWindowVisible) {
460                mWindowVisible = true;
461                if (!mWindowAdded) {
462                    mWindowAdded = true;
463                    View v = onCreateContentView();
464                    if (v != null) {
465                        setContentView(v);
466                    }
467                }
468                mWindow.show();
469            }
470        } finally {
471            mWindowWasVisible = true;
472            mInShowWindow = false;
473        }
474    }
475
476    public void hideWindow() {
477        if (mWindowVisible) {
478            mWindow.hide();
479            mWindowVisible = false;
480        }
481    }
482
483    /**
484     * You can call this to customize the theme used by your IME's window.
485     * This must be set before {@link #onCreate}, so you
486     * will typically call it in your constructor with the resource ID
487     * of your custom theme.
488     */
489    public void setTheme(int theme) {
490        if (mWindow != null) {
491            throw new IllegalStateException("Must be called before onCreate()");
492        }
493        mTheme = theme;
494    }
495
496    /**
497     * Ask that a new activity be started for voice interaction.  This will create a
498     * new dedicated task in the activity manager for this voice interaction session;
499     * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
500     * will be set for you to make it a new task.
501     *
502     * <p>The newly started activity will be displayed to the user in a special way, as
503     * a layer under the voice interaction UI.</p>
504     *
505     * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor}
506     * through which it can perform voice interactions through your session.  These requests
507     * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands},
508     * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}.
509     *
510     * <p>You will receive a call to {@link #onTaskStarted} when the task starts up
511     * and {@link #onTaskFinished} when the last activity has finished.
512     *
513     * @param intent The Intent to start this voice interaction.  The given Intent will
514     * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
515     * this is part of a voice interaction.
516     */
517    public void startVoiceActivity(Intent intent) {
518        if (mToken == null) {
519            throw new IllegalStateException("Can't call before onCreate()");
520        }
521        try {
522            intent.migrateExtraStreamToClipData();
523            intent.prepareToLeaveProcess();
524            int res = mSystemService.startVoiceActivity(mToken, intent,
525                    intent.resolveType(mContext.getContentResolver()));
526            Instrumentation.checkStartActivityResult(res, intent);
527        } catch (RemoteException e) {
528        }
529    }
530
531    /**
532     * Convenience for inflating views.
533     */
534    public LayoutInflater getLayoutInflater() {
535        return mInflater;
536    }
537
538    /**
539     * Retrieve the window being used to show the session's UI.
540     */
541    public Dialog getWindow() {
542        return mWindow;
543    }
544
545    /**
546     * Finish the session.
547     */
548    public void finish() {
549        if (mToken == null) {
550            throw new IllegalStateException("Can't call before onCreate()");
551        }
552        hideWindow();
553        try {
554            mSystemService.finish(mToken);
555        } catch (RemoteException e) {
556        }
557    }
558
559    /**
560     * Initiatize a new session.
561     *
562     * @param args The arguments that were supplied to
563     * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}.
564     */
565    public void onCreate(Bundle args) {
566        mTheme = mTheme != 0 ? mTheme
567                : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession;
568        mInflater = (LayoutInflater)mContext.getSystemService(
569                Context.LAYOUT_INFLATER_SERVICE);
570        mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
571                mCallbacks, this, mDispatcherState,
572                WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.TOP, true);
573        mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
574        initViews();
575        mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
576        mWindow.setToken(mToken);
577    }
578
579    /**
580     * Last callback to the session as it is being finished.
581     */
582    public void onDestroy() {
583    }
584
585    /**
586     * Hook in which to create the session's UI.
587     */
588    public View onCreateContentView() {
589        return null;
590    }
591
592    public void setContentView(View view) {
593        mContentFrame.removeAllViews();
594        mContentFrame.addView(view, new FrameLayout.LayoutParams(
595                ViewGroup.LayoutParams.MATCH_PARENT,
596                ViewGroup.LayoutParams.WRAP_CONTENT));
597
598    }
599
600    public boolean onKeyDown(int keyCode, KeyEvent event) {
601        return false;
602    }
603
604    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
605        return false;
606    }
607
608    public boolean onKeyUp(int keyCode, KeyEvent event) {
609        return false;
610    }
611
612    public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
613        return false;
614    }
615
616    public void onBackPressed() {
617        finish();
618    }
619
620    /**
621     * Sessions automatically watch for requests that all system UI be closed (such as when
622     * the user presses HOME), which will appear here.  The default implementation always
623     * calls {@link #finish}.
624     */
625    public void onCloseSystemDialogs() {
626        finish();
627    }
628
629    /**
630     * Compute the interesting insets into your UI.  The default implementation
631     * uses the entire window frame as the insets.  The default touchable
632     * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}.
633     *
634     * @param outInsets Fill in with the current UI insets.
635     */
636    public void onComputeInsets(Insets outInsets) {
637        int[] loc = mTmpLocation;
638        View decor = getWindow().getWindow().getDecorView();
639        decor.getLocationInWindow(loc);
640        outInsets.contentInsets.top = 0;
641        outInsets.contentInsets.left = 0;
642        outInsets.contentInsets.right = 0;
643        outInsets.contentInsets.bottom = 0;
644        outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME;
645        outInsets.touchableRegion.setEmpty();
646    }
647
648    /**
649     * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
650     * has actually started.
651     *
652     * @param intent The original {@link Intent} supplied to
653     * {@link #startVoiceActivity(android.content.Intent)}.
654     * @param taskId Unique ID of the now running task.
655     */
656    public void onTaskStarted(Intent intent, int taskId) {
657    }
658
659    /**
660     * Called when the last activity of a task initiated by
661     * {@link #startVoiceActivity(android.content.Intent)} has finished.  The default
662     * implementation calls {@link #finish()} on the assumption that this represents
663     * the completion of a voice action.  You can override the implementation if you would
664     * like a different behavior.
665     *
666     * @param intent The original {@link Intent} supplied to
667     * {@link #startVoiceActivity(android.content.Intent)}.
668     * @param taskId Unique ID of the finished task.
669     */
670    public void onTaskFinished(Intent intent, int taskId) {
671        finish();
672    }
673
674    /**
675     * Request to query for what extended commands the session supports.
676     *
677     * @param caller Who is making the request.
678     * @param commands An array of commands that are being queried.
679     * @return Return an array of booleans indicating which of each entry in the
680     * command array is supported.  A true entry in the array indicates the command
681     * is supported; false indicates it is not.  The default implementation returns
682     * an array of all false entries.
683     */
684    public boolean[] onGetSupportedCommands(Caller caller, String[] commands) {
685        return new boolean[commands.length];
686    }
687
688    /**
689     * Request to confirm with the user before proceeding with an unrecoverable operation,
690     * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
691     * VoiceInteractor.ConfirmationRequest}.
692     *
693     * @param caller Who is making the request.
694     * @param request The active request.
695     * @param prompt The prompt informing the user of what will happen, as per
696     * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
697     * @param extras Any additional information, as per
698     * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
699     */
700    public abstract void onConfirm(Caller caller, Request request, CharSequence prompt,
701            Bundle extras);
702
703    /**
704     * Request to abort the voice interaction session because the voice activity can not
705     * complete its interaction using voice.  Corresponds to
706     * {@link android.app.VoiceInteractor.AbortVoiceRequest
707     * VoiceInteractor.AbortVoiceRequest}.  The default implementation just sends an empty
708     * confirmation back to allow the activity to exit.
709     *
710     * @param caller Who is making the request.
711     * @param request The active request.
712     * @param message The message informing the user of the problem, as per
713     * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
714     * @param extras Any additional information, as per
715     * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
716     */
717    public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
718        request.sendAbortVoiceResult(null);
719    }
720
721    /**
722     * Process an arbitrary extended command from the caller,
723     * corresponding to a {@link android.app.VoiceInteractor.CommandRequest
724     * VoiceInteractor.CommandRequest}.
725     *
726     * @param caller Who is making the request.
727     * @param request The active request.
728     * @param command The command that is being executed, as per
729     * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
730     * @param extras Any additional information, as per
731     * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
732     */
733    public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
734
735    /**
736     * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
737     * that was previously delivered to {@link #onConfirm} or {@link #onCommand}.
738     *
739     * @param request The request that is being canceled.
740     */
741    public abstract void onCancel(Request request);
742}
743