VoiceInteractionSession.java revision 4e88bcd39918197c78b148afe40a08b6adcace1e
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.annotation.Nullable;
20import android.app.Dialog;
21import android.app.Instrumentation;
22import android.app.VoiceInteractor;
23import android.app.assist.AssistContent;
24import android.app.assist.AssistStructure;
25import android.content.ComponentCallbacks2;
26import android.content.Context;
27import android.content.Intent;
28import android.content.res.Configuration;
29import android.content.res.TypedArray;
30import android.graphics.Bitmap;
31import android.graphics.Rect;
32import android.graphics.Region;
33import android.inputmethodservice.SoftInputWindow;
34import android.os.Binder;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.IBinder;
38import android.os.Message;
39import android.os.RemoteException;
40import android.util.ArrayMap;
41import android.util.Log;
42import android.view.Gravity;
43import android.view.KeyEvent;
44import android.view.LayoutInflater;
45import android.view.View;
46import android.view.ViewGroup;
47import android.view.ViewTreeObserver;
48import android.view.WindowManager;
49import android.widget.FrameLayout;
50import com.android.internal.app.IVoiceInteractionManagerService;
51import com.android.internal.app.IVoiceInteractionSessionShowCallback;
52import com.android.internal.app.IVoiceInteractor;
53import com.android.internal.app.IVoiceInteractorCallback;
54import com.android.internal.app.IVoiceInteractorRequest;
55import com.android.internal.os.HandlerCaller;
56import com.android.internal.os.SomeArgs;
57
58import java.lang.ref.WeakReference;
59
60import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
61
62/**
63 * An active voice interaction session, providing a facility for the implementation
64 * to interact with the user in the voice interaction layer.  The user interface is
65 * initially shown by default, and can be created be overriding {@link #onCreateContentView()}
66 * in which the UI can be built.
67 *
68 * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
69 * when done.  It can also initiate voice interactions with applications by calling
70 * {@link #startVoiceActivity}</p>.
71 */
72public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCallbacks2 {
73    static final String TAG = "VoiceInteractionSession";
74    static final boolean DEBUG = false;
75
76    /**
77     * Flag received in {@link #onShow}: originator requested that the session be started with
78     * assist data from the currently focused activity.
79     */
80    public static final int SHOW_WITH_ASSIST = 1<<0;
81
82    /**
83     * Flag received in {@link #onShow}: originator requested that the session be started with
84     * a screen shot of the currently focused activity.
85     */
86    public static final int SHOW_WITH_SCREENSHOT = 1<<1;
87
88    /**
89     * Flag for use with {@link #onShow}: indicates that the session has been started from the
90     * system assist gesture.
91     */
92    public static final int SHOW_SOURCE_ASSIST_GESTURE = 1<<2;
93
94    final Context mContext;
95    final HandlerCaller mHandlerCaller;
96
97    final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
98
99    IVoiceInteractionManagerService mSystemService;
100    IBinder mToken;
101
102    int mTheme = 0;
103    LayoutInflater mInflater;
104    TypedArray mThemeAttrs;
105    View mRootView;
106    FrameLayout mContentFrame;
107    SoftInputWindow mWindow;
108
109    boolean mInitialized;
110    boolean mWindowAdded;
111    boolean mWindowVisible;
112    boolean mWindowWasVisible;
113    boolean mInShowWindow;
114
115    final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
116
117    final Insets mTmpInsets = new Insets();
118
119    final WeakReference<VoiceInteractionSession> mWeakRef
120            = new WeakReference<VoiceInteractionSession>(this);
121
122    final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
123        @Override
124        public IVoiceInteractorRequest startConfirmation(String callingPackage,
125                IVoiceInteractorCallback callback, VoiceInteractor.Prompt prompt, Bundle extras) {
126            ConfirmationRequest request = new ConfirmationRequest(callingPackage,
127                    Binder.getCallingUid(), callback, VoiceInteractionSession.this,
128                    prompt, extras);
129            addRequest(request);
130            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_CONFIRMATION,
131                    request));
132            return request.mInterface;
133        }
134
135        @Override
136        public IVoiceInteractorRequest startPickOption(String callingPackage,
137                IVoiceInteractorCallback callback, VoiceInteractor.Prompt prompt,
138                VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) {
139            PickOptionRequest request = new PickOptionRequest(callingPackage,
140                    Binder.getCallingUid(), callback, VoiceInteractionSession.this,
141                    prompt, options, extras);
142            addRequest(request);
143            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_PICK_OPTION,
144                    request));
145            return request.mInterface;
146        }
147
148        @Override
149        public IVoiceInteractorRequest startCompleteVoice(String callingPackage,
150                IVoiceInteractorCallback callback, VoiceInteractor.Prompt message, Bundle extras) {
151            CompleteVoiceRequest request = new CompleteVoiceRequest(callingPackage,
152                    Binder.getCallingUid(), callback, VoiceInteractionSession.this,
153                    message, extras);
154            addRequest(request);
155            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_COMPLETE_VOICE,
156                    request));
157            return request.mInterface;
158        }
159
160        @Override
161        public IVoiceInteractorRequest startAbortVoice(String callingPackage,
162                IVoiceInteractorCallback callback, VoiceInteractor.Prompt message, Bundle extras) {
163            AbortVoiceRequest request = new AbortVoiceRequest(callingPackage,
164                    Binder.getCallingUid(), callback, VoiceInteractionSession.this,
165                    message, extras);
166            addRequest(request);
167            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_ABORT_VOICE,
168                    request));
169            return request.mInterface;
170        }
171
172        @Override
173        public IVoiceInteractorRequest startCommand(String callingPackage,
174                IVoiceInteractorCallback callback, String command, Bundle extras) {
175            CommandRequest request = new CommandRequest(callingPackage,
176                    Binder.getCallingUid(), callback, VoiceInteractionSession.this,
177                    command, extras);
178            addRequest(request);
179            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_COMMAND,
180                    request));
181            return request.mInterface;
182        }
183
184        @Override
185        public boolean[] supportsCommands(String callingPackage, String[] commands) {
186            Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS,
187                    0, commands, null);
188            SomeArgs args = mHandlerCaller.sendMessageAndWait(msg);
189            if (args != null) {
190                boolean[] res = (boolean[])args.arg1;
191                args.recycle();
192                return res;
193            }
194            return new boolean[commands.length];
195        }
196    };
197
198    final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
199        @Override
200        public void show(Bundle sessionArgs, int flags,
201                IVoiceInteractionSessionShowCallback showCallback) {
202            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW,
203                    flags, sessionArgs, showCallback));
204        }
205
206        @Override
207        public void hide() {
208            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_HIDE));
209        }
210
211        @Override
212        public void handleAssist(Bundle data, AssistStructure structure,
213                AssistContent content) {
214            // We want to pre-warm the AssistStructure before handing it off to the main
215            // thread.  There is a strong argument to be made that it should be handed
216            // through as a separate param rather than part of the assistBundle.
217            if (structure != null) {
218                structure.ensureData();
219            }
220            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOO(MSG_HANDLE_ASSIST,
221                    data, structure, content));
222        }
223
224        @Override
225        public void handleScreenshot(Bitmap screenshot) {
226            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_SCREENSHOT,
227                    screenshot));
228        }
229
230        @Override
231        public void taskStarted(Intent intent, int taskId) {
232            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED,
233                    taskId, intent));
234        }
235
236        @Override
237        public void taskFinished(Intent intent, int taskId) {
238            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_FINISHED,
239                    taskId, intent));
240        }
241
242        @Override
243        public void closeSystemDialogs() {
244            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CLOSE_SYSTEM_DIALOGS));
245        }
246
247        @Override
248        public void destroy() {
249            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY));
250        }
251    };
252
253    /**
254     * Base class representing a request from a voice-driver app to perform a particular
255     * voice operation with the user.  See related subclasses for the types of requests
256     * that are possible.
257     */
258    public static class Request {
259        final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
260            @Override
261            public void cancel() throws RemoteException {
262                VoiceInteractionSession session = mSession.get();
263                if (session != null) {
264                    session.mHandlerCaller.sendMessage(
265                            session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
266                }
267            }
268        };
269        final String mCallingPackage;
270        final int mCallingUid;
271        final IVoiceInteractorCallback mCallback;
272        final WeakReference<VoiceInteractionSession> mSession;
273        final Bundle mExtras;
274
275        Request(String packageName, int uid, IVoiceInteractorCallback callback,
276                VoiceInteractionSession session, Bundle extras) {
277            mCallingPackage = packageName;
278            mCallingUid = uid;
279            mCallback = callback;
280            mSession = session.mWeakRef;
281            mExtras = extras;
282        }
283
284        /**
285         * Return the uid of the application that initiated the request.
286         */
287        public int getCallingUid() {
288            return mCallingUid;
289        }
290
291        /**
292         * Return the package name of the application that initiated the request.
293         */
294        public String getCallingPackage() {
295            return mCallingPackage;
296        }
297
298        /**
299         * Return any additional extra information that was supplied as part of the request.
300         */
301        public Bundle getExtras() {
302            return mExtras;
303        }
304
305        void finishRequest() {
306            VoiceInteractionSession session = mSession.get();
307            if (session == null) {
308                throw new IllegalStateException("VoiceInteractionSession has been destroyed");
309            }
310            Request req = session.removeRequest(mInterface.asBinder());
311            if (req == null) {
312                throw new IllegalStateException("Request not active: " + this);
313            } else if (req != this) {
314                throw new IllegalStateException("Current active request " + req
315                        + " not same as calling request " + this);
316            }
317        }
318
319        /**
320         * Ask the app to cancel this current request.
321         */
322        public void cancel() {
323            try {
324                if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
325                finishRequest();
326                mCallback.deliverCancel(mInterface);
327            } catch (RemoteException e) {
328            }
329        }
330    }
331
332    /**
333     * A request for confirmation from the user of an operation, as per
334     * {@link android.app.VoiceInteractor.ConfirmationRequest
335     * VoiceInteractor.ConfirmationRequest}.
336     */
337    public static final class ConfirmationRequest extends Request {
338        final VoiceInteractor.Prompt mPrompt;
339
340        ConfirmationRequest(String packageName, int uid, IVoiceInteractorCallback callback,
341                VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) {
342            super(packageName, uid, callback, session, extras);
343            mPrompt = prompt;
344        }
345
346        /**
347         * Return the prompt informing the user of what will happen, as per
348         * {@link android.app.VoiceInteractor.ConfirmationRequest
349         * VoiceInteractor.ConfirmationRequest}.
350         */
351        @Nullable
352        public VoiceInteractor.Prompt getVoicePrompt() {
353            return mPrompt;
354        }
355
356        /**
357         * Return the prompt informing the user of what will happen, as per
358         * {@link android.app.VoiceInteractor.ConfirmationRequest
359         * VoiceInteractor.ConfirmationRequest}.
360         * @deprecated Prefer {@link #getVoicePrompt()} which allows multiple voice prompts.
361         */
362        @Nullable
363        public CharSequence getPrompt() {
364            return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null);
365        }
366
367        /**
368         * Report that the voice interactor has confirmed the operation with the user, resulting
369         * in a call to
370         * {@link android.app.VoiceInteractor.ConfirmationRequest#onConfirmationResult
371         * VoiceInteractor.ConfirmationRequest.onConfirmationResult}.
372         */
373        public void sendConfirmationResult(boolean confirmed, Bundle result) {
374            try {
375                if (DEBUG) Log.d(TAG, "sendConfirmationResult: req=" + mInterface
376                        + " confirmed=" + confirmed + " result=" + result);
377                finishRequest();
378                mCallback.deliverConfirmationResult(mInterface, confirmed, result);
379            } catch (RemoteException e) {
380            }
381        }
382    }
383
384    /**
385     * A request for the user to pick from a set of option, as per
386     * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
387     */
388    public static final class PickOptionRequest extends Request {
389        final VoiceInteractor.Prompt mPrompt;
390        final VoiceInteractor.PickOptionRequest.Option[] mOptions;
391
392        PickOptionRequest(String packageName, int uid, IVoiceInteractorCallback callback,
393                VoiceInteractionSession session, VoiceInteractor.Prompt prompt,
394                VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) {
395            super(packageName, uid, callback, session, extras);
396            mPrompt = prompt;
397            mOptions = options;
398        }
399
400        /**
401         * Return the prompt informing the user of what they are picking, as per
402         * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
403         */
404        @Nullable
405        public VoiceInteractor.Prompt getVoicePrompt() {
406            return mPrompt;
407        }
408
409        /**
410         * Return the prompt informing the user of what they are picking, as per
411         * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
412         * @deprecated Prefer {@link #getVoicePrompt()} which allows multiple voice prompts.
413         */
414        @Nullable
415        public CharSequence getPrompt() {
416            return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null);
417        }
418
419        /**
420         * Return the set of options the user is picking from, as per
421         * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
422         */
423        public VoiceInteractor.PickOptionRequest.Option[] getOptions() {
424            return mOptions;
425        }
426
427        void sendPickOptionResult(boolean finished,
428                VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) {
429            try {
430                if (DEBUG) Log.d(TAG, "sendPickOptionResult: req=" + mInterface
431                        + " finished=" + finished + " selections=" + selections
432                        + " result=" + result);
433                if (finished) {
434                    finishRequest();
435                }
436                mCallback.deliverPickOptionResult(mInterface, finished, selections, result);
437            } catch (RemoteException e) {
438            }
439        }
440
441        /**
442         * Report an intermediate option selection from the request, without completing it (the
443         * request is still active and the app is waiting for the final option selection),
444         * resulting in a call to
445         * {@link android.app.VoiceInteractor.PickOptionRequest#onPickOptionResult
446         * VoiceInteractor.PickOptionRequest.onPickOptionResult} with false for finished.
447         */
448        public void sendIntermediatePickOptionResult(
449                VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) {
450            sendPickOptionResult(false, selections, result);
451        }
452
453        /**
454         * Report the final option selection for the request, completing the request
455         * and resulting in a call to
456         * {@link android.app.VoiceInteractor.PickOptionRequest#onPickOptionResult
457         * VoiceInteractor.PickOptionRequest.onPickOptionResult} with false for finished.
458         */
459        public void sendPickOptionResult(
460                VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) {
461            sendPickOptionResult(true, selections, result);
462        }
463    }
464
465    /**
466     * A request to simply inform the user that the voice operation has completed, as per
467     * {@link android.app.VoiceInteractor.CompleteVoiceRequest
468     * VoiceInteractor.CompleteVoiceRequest}.
469     */
470    public static final class CompleteVoiceRequest extends Request {
471        final VoiceInteractor.Prompt mPrompt;
472
473        CompleteVoiceRequest(String packageName, int uid, IVoiceInteractorCallback callback,
474                VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) {
475            super(packageName, uid, callback, session, extras);
476            mPrompt = prompt;
477        }
478
479        /**
480         * Return the message informing the user of the completion, as per
481         * {@link android.app.VoiceInteractor.CompleteVoiceRequest
482         * VoiceInteractor.CompleteVoiceRequest}.
483         */
484        @Nullable
485        public VoiceInteractor.Prompt getVoicePrompt() {
486            return mPrompt;
487        }
488
489        /**
490         * Return the message informing the user of the completion, as per
491         * {@link android.app.VoiceInteractor.CompleteVoiceRequest
492         * VoiceInteractor.CompleteVoiceRequest}.
493         * @deprecated Prefer {@link #getVoicePrompt()} which allows a separate visual message.
494         */
495        @Nullable
496        public CharSequence getMessage() {
497            return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null);
498        }
499
500        /**
501         * Report that the voice interactor has finished completing the voice operation, resulting
502         * in a call to
503         * {@link android.app.VoiceInteractor.CompleteVoiceRequest#onCompleteResult
504         * VoiceInteractor.CompleteVoiceRequest.onCompleteResult}.
505         */
506        public void sendCompleteResult(Bundle result) {
507            try {
508                if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface
509                        + " result=" + result);
510                finishRequest();
511                mCallback.deliverCompleteVoiceResult(mInterface, result);
512            } catch (RemoteException e) {
513            }
514        }
515    }
516
517    /**
518     * A request to report that the current user interaction can not be completed with voice, as per
519     * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
520     */
521    public static final class AbortVoiceRequest extends Request {
522        final VoiceInteractor.Prompt mPrompt;
523
524        AbortVoiceRequest(String packageName, int uid, IVoiceInteractorCallback callback,
525                VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) {
526            super(packageName, uid, callback, session, extras);
527            mPrompt = prompt;
528        }
529
530        /**
531         * Return the message informing the user of the problem, as per
532         * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
533         */
534        @Nullable
535        public VoiceInteractor.Prompt getVoicePrompt() {
536            return mPrompt;
537        }
538
539        /**
540         * Return the message informing the user of the problem, as per
541         * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
542         * @deprecated Prefer {@link #getVoicePrompt()} which allows a separate visual message.
543         */
544        @Nullable
545        public CharSequence getMessage() {
546            return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null);
547        }
548
549        /**
550         * Report that the voice interactor has finished aborting the voice operation, resulting
551         * in a call to
552         * {@link android.app.VoiceInteractor.AbortVoiceRequest#onAbortResult
553         * VoiceInteractor.AbortVoiceRequest.onAbortResult}.
554         */
555        public void sendAbortResult(Bundle result) {
556            try {
557                if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
558                        + " result=" + result);
559                finishRequest();
560                mCallback.deliverAbortVoiceResult(mInterface, result);
561            } catch (RemoteException e) {
562            }
563        }
564    }
565
566    /**
567     * A generic vendor-specific request, as per
568     * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
569     */
570    public static final class CommandRequest extends Request {
571        final String mCommand;
572
573        CommandRequest(String packageName, int uid, IVoiceInteractorCallback callback,
574                VoiceInteractionSession session, String command, Bundle extras) {
575            super(packageName, uid, callback, session, extras);
576            mCommand = command;
577        }
578
579        /**
580         * Return the command that is being executed, as per
581         * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
582         */
583        public String getCommand() {
584            return mCommand;
585        }
586
587        void sendCommandResult(boolean finished, Bundle result) {
588            try {
589                if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
590                        + " result=" + result);
591                if (finished) {
592                    finishRequest();
593                }
594                mCallback.deliverCommandResult(mInterface, finished, result);
595            } catch (RemoteException e) {
596            }
597        }
598
599        /**
600         * Report an intermediate result of the request, without completing it (the request
601         * is still active and the app is waiting for the final result), resulting in a call to
602         * {@link android.app.VoiceInteractor.CommandRequest#onCommandResult
603         * VoiceInteractor.CommandRequest.onCommandResult} with false for isCompleted.
604         */
605        public void sendIntermediateResult(Bundle result) {
606            sendCommandResult(false, result);
607        }
608
609        /**
610         * Report the final result of the request, completing the request and resulting in a call to
611         * {@link android.app.VoiceInteractor.CommandRequest#onCommandResult
612         * VoiceInteractor.CommandRequest.onCommandResult} with true for isCompleted.
613         */
614        public void sendResult(Bundle result) {
615            sendCommandResult(true, result);
616        }
617    }
618
619    static final int MSG_START_CONFIRMATION = 1;
620    static final int MSG_START_PICK_OPTION = 2;
621    static final int MSG_START_COMPLETE_VOICE = 3;
622    static final int MSG_START_ABORT_VOICE = 4;
623    static final int MSG_START_COMMAND = 5;
624    static final int MSG_SUPPORTS_COMMANDS = 6;
625    static final int MSG_CANCEL = 7;
626
627    static final int MSG_TASK_STARTED = 100;
628    static final int MSG_TASK_FINISHED = 101;
629    static final int MSG_CLOSE_SYSTEM_DIALOGS = 102;
630    static final int MSG_DESTROY = 103;
631    static final int MSG_HANDLE_ASSIST = 104;
632    static final int MSG_HANDLE_SCREENSHOT = 105;
633    static final int MSG_SHOW = 106;
634    static final int MSG_HIDE = 107;
635
636    class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback {
637        @Override
638        public void executeMessage(Message msg) {
639            SomeArgs args;
640            switch (msg.what) {
641                case MSG_START_CONFIRMATION:
642                    if (DEBUG) Log.d(TAG, "onConfirm: req=" + msg.obj);
643                    onRequestConfirmation((ConfirmationRequest) msg.obj);
644                    break;
645                case MSG_START_PICK_OPTION:
646                    if (DEBUG) Log.d(TAG, "onPickOption: req=" + msg.obj);
647                    onRequestPickOption((PickOptionRequest) msg.obj);
648                    break;
649                case MSG_START_COMPLETE_VOICE:
650                    if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + msg.obj);
651                    onRequestCompleteVoice((CompleteVoiceRequest) msg.obj);
652                    break;
653                case MSG_START_ABORT_VOICE:
654                    if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + msg.obj);
655                    onRequestAbortVoice((AbortVoiceRequest) msg.obj);
656                    break;
657                case MSG_START_COMMAND:
658                    if (DEBUG) Log.d(TAG, "onCommand: req=" + msg.obj);
659                    onRequestCommand((CommandRequest) msg.obj);
660                    break;
661                case MSG_SUPPORTS_COMMANDS:
662                    args = (SomeArgs)msg.obj;
663                    if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg1);
664                    args.arg1 = onGetSupportedCommands((String[]) args.arg1);
665                    break;
666                case MSG_CANCEL:
667                    if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request)msg.obj));
668                    onCancelRequest((Request) msg.obj);
669                    break;
670                case MSG_TASK_STARTED:
671                    if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj
672                            + " taskId=" + msg.arg1);
673                    onTaskStarted((Intent) msg.obj, msg.arg1);
674                    break;
675                case MSG_TASK_FINISHED:
676                    if (DEBUG) Log.d(TAG, "onTaskFinished: intent=" + msg.obj
677                            + " taskId=" + msg.arg1);
678                    onTaskFinished((Intent) msg.obj, msg.arg1);
679                    break;
680                case MSG_CLOSE_SYSTEM_DIALOGS:
681                    if (DEBUG) Log.d(TAG, "onCloseSystemDialogs");
682                    onCloseSystemDialogs();
683                    break;
684                case MSG_DESTROY:
685                    if (DEBUG) Log.d(TAG, "doDestroy");
686                    doDestroy();
687                    break;
688                case MSG_HANDLE_ASSIST:
689                    args = (SomeArgs)msg.obj;
690                    if (DEBUG) Log.d(TAG, "onHandleAssist: data=" + args.arg1
691                            + " structure=" + args.arg2 + " content=" + args.arg3);
692                    onHandleAssist((Bundle) args.arg1, (AssistStructure) args.arg2,
693                            (AssistContent) args.arg3);
694                    break;
695                case MSG_HANDLE_SCREENSHOT:
696                    if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj);
697                    onHandleScreenshot((Bitmap) msg.obj);
698                    break;
699                case MSG_SHOW:
700                    args = (SomeArgs)msg.obj;
701                    if (DEBUG) Log.d(TAG, "doShow: args=" + args.arg1
702                            + " flags=" + msg.arg1
703                            + " showCallback=" + args.arg2);
704                    doShow((Bundle) args.arg1, msg.arg1,
705                            (IVoiceInteractionSessionShowCallback) args.arg2);
706                    break;
707                case MSG_HIDE:
708                    if (DEBUG) Log.d(TAG, "doHide");
709                    doHide();
710                    break;
711            }
712        }
713
714        @Override
715        public void onBackPressed() {
716            VoiceInteractionSession.this.onBackPressed();
717        }
718    }
719
720    final MyCallbacks mCallbacks = new MyCallbacks();
721
722    /**
723     * Information about where interesting parts of the input method UI appear.
724     */
725    public static final class Insets {
726        /**
727         * This is the part of the UI that is the main content.  It is
728         * used to determine the basic space needed, to resize/pan the
729         * application behind.  It is assumed that this inset does not
730         * change very much, since any change will cause a full resize/pan
731         * of the application behind.  This value is relative to the top edge
732         * of the input method window.
733         */
734        public final Rect contentInsets = new Rect();
735
736        /**
737         * This is the region of the UI that is touchable.  It is used when
738         * {@link #touchableInsets} is set to {@link #TOUCHABLE_INSETS_REGION}.
739         * The region should be specified relative to the origin of the window frame.
740         */
741        public final Region touchableRegion = new Region();
742
743        /**
744         * Option for {@link #touchableInsets}: the entire window frame
745         * can be touched.
746         */
747        public static final int TOUCHABLE_INSETS_FRAME
748                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
749
750        /**
751         * Option for {@link #touchableInsets}: the area inside of
752         * the content insets can be touched.
753         */
754        public static final int TOUCHABLE_INSETS_CONTENT
755                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
756
757        /**
758         * Option for {@link #touchableInsets}: the region specified by
759         * {@link #touchableRegion} can be touched.
760         */
761        public static final int TOUCHABLE_INSETS_REGION
762                = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
763
764        /**
765         * Determine which area of the window is touchable by the user.  May
766         * be one of: {@link #TOUCHABLE_INSETS_FRAME},
767         * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_REGION}.
768         */
769        public int touchableInsets;
770    }
771
772    final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
773            new ViewTreeObserver.OnComputeInternalInsetsListener() {
774        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
775            onComputeInsets(mTmpInsets);
776            info.contentInsets.set(mTmpInsets.contentInsets);
777            info.visibleInsets.set(mTmpInsets.contentInsets);
778            info.touchableRegion.set(mTmpInsets.touchableRegion);
779            info.setTouchableInsets(mTmpInsets.touchableInsets);
780        }
781    };
782
783    public VoiceInteractionSession(Context context) {
784        this(context, new Handler());
785    }
786
787    public VoiceInteractionSession(Context context, Handler handler) {
788        mContext = context;
789        mHandlerCaller = new HandlerCaller(context, handler.getLooper(),
790                mCallbacks, true);
791    }
792
793    public Context getContext() {
794        return mContext;
795    }
796
797    void addRequest(Request req) {
798        mActiveRequests.put(req.mInterface.asBinder(), req);
799    }
800
801    Request removeRequest(IBinder reqInterface) {
802        synchronized (this) {
803            return mActiveRequests.remove(reqInterface);
804        }
805    }
806
807    void doCreate(IVoiceInteractionManagerService service, IBinder token) {
808        mSystemService = service;
809        mToken = token;
810        onCreate();
811    }
812
813    void doShow(Bundle args, int flags, final IVoiceInteractionSessionShowCallback showCallback) {
814        if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded
815                + " mWindowVisible=" + mWindowVisible);
816
817        if (mInShowWindow) {
818            Log.w(TAG, "Re-entrance in to showWindow");
819            return;
820        }
821
822        try {
823            mInShowWindow = true;
824            if (!mWindowVisible) {
825                if (!mWindowAdded) {
826                    mWindowAdded = true;
827                    View v = onCreateContentView();
828                    if (v != null) {
829                        setContentView(v);
830                    }
831                }
832            }
833            onShow(args, flags);
834            if (!mWindowVisible) {
835                mWindowVisible = true;
836                mWindow.show();
837            }
838            if (showCallback != null) {
839                mRootView.invalidate();
840                mRootView.getViewTreeObserver().addOnPreDrawListener(
841                        new ViewTreeObserver.OnPreDrawListener() {
842                            @Override
843                            public boolean onPreDraw() {
844                                mRootView.getViewTreeObserver().removeOnPreDrawListener(this);
845                                try {
846                                    showCallback.onShown();
847                                } catch (RemoteException e) {
848                                    Log.w(TAG, "Error calling onShown", e);
849                                }
850                                return true;
851                            }
852                        });
853            }
854        } finally {
855            mWindowWasVisible = true;
856            mInShowWindow = false;
857        }
858    }
859
860    void doHide() {
861        if (mWindowVisible) {
862            mWindow.hide();
863            mWindowVisible = false;
864            onHide();
865        }
866    }
867
868    void doDestroy() {
869        onDestroy();
870        if (mInitialized) {
871            mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
872                    mInsetsComputer);
873            if (mWindowAdded) {
874                mWindow.dismiss();
875                mWindowAdded = false;
876            }
877            mInitialized = false;
878        }
879    }
880
881    void initViews() {
882        mInitialized = true;
883
884        mThemeAttrs = mContext.obtainStyledAttributes(android.R.styleable.VoiceInteractionSession);
885        mRootView = mInflater.inflate(
886                com.android.internal.R.layout.voice_interaction_session, null);
887        mRootView.setSystemUiVisibility(
888                View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
889                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
890        mWindow.setContentView(mRootView);
891        mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
892
893        mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content);
894    }
895
896    /**
897     * Show the UI for this session.  This asks the system to go through the process of showing
898     * your UI, which will eventually culminate in {@link #onShow}.  This is similar to calling
899     * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
900     * @param args Arbitrary arguments that will be propagated {@link #onShow}.
901     * @param flags Indicates additional optional behavior that should be performed.  May
902     * be {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST}
903     * to request that the system generate and deliver assist data on the current foreground
904     * app as part of showing the session UI.
905     */
906    public void show(Bundle args, int flags) {
907        if (mToken == null) {
908            throw new IllegalStateException("Can't call before onCreate()");
909        }
910        try {
911            mSystemService.showSessionFromSession(mToken, args, flags);
912        } catch (RemoteException e) {
913        }
914    }
915
916    /**
917     * Hide the session's UI, if currently shown.  Call this when you are done with your
918     * user interaction.
919     */
920    public void hide() {
921        if (mToken == null) {
922            throw new IllegalStateException("Can't call before onCreate()");
923        }
924        try {
925            mSystemService.hideSessionFromSession(mToken);
926        } catch (RemoteException e) {
927        }
928    }
929
930    /**
931     * You can call this to customize the theme used by your IME's window.
932     * This must be set before {@link #onCreate}, so you
933     * will typically call it in your constructor with the resource ID
934     * of your custom theme.
935     */
936    public void setTheme(int theme) {
937        if (mWindow != null) {
938            throw new IllegalStateException("Must be called before onCreate()");
939        }
940        mTheme = theme;
941    }
942
943    /**
944     * Ask that a new activity be started for voice interaction.  This will create a
945     * new dedicated task in the activity manager for this voice interaction session;
946     * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
947     * will be set for you to make it a new task.
948     *
949     * <p>The newly started activity will be displayed to the user in a special way, as
950     * a layer under the voice interaction UI.</p>
951     *
952     * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor}
953     * through which it can perform voice interactions through your session.  These requests
954     * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands},
955     * {@link #onRequestConfirmation}, {@link #onRequestPickOption},
956     * {@link #onRequestCompleteVoice}, {@link #onRequestAbortVoice},
957     * or {@link #onRequestCommand}
958     *
959     * <p>You will receive a call to {@link #onTaskStarted} when the task starts up
960     * and {@link #onTaskFinished} when the last activity has finished.
961     *
962     * @param intent The Intent to start this voice interaction.  The given Intent will
963     * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
964     * this is part of a voice interaction.
965     */
966    public void startVoiceActivity(Intent intent) {
967        if (mToken == null) {
968            throw new IllegalStateException("Can't call before onCreate()");
969        }
970        try {
971            intent.migrateExtraStreamToClipData();
972            intent.prepareToLeaveProcess();
973            int res = mSystemService.startVoiceActivity(mToken, intent,
974                    intent.resolveType(mContext.getContentResolver()));
975            Instrumentation.checkStartActivityResult(res, intent);
976        } catch (RemoteException e) {
977        }
978    }
979
980    /**
981     * Set whether this session will keep the device awake while it is running a voice
982     * activity.  By default, the system holds a wake lock for it while in this state,
983     * so that it can work even if the screen is off.  Setting this to false removes that
984     * wake lock, allowing the CPU to go to sleep.  This is typically used if the
985     * session decides it has been waiting too long for a response from the user and
986     * doesn't want to let this continue to drain the battery.
987     *
988     * <p>Passing false here will release the wake lock, and you can call later with
989     * true to re-acquire it.  It will also be automatically re-acquired for you each
990     * time you start a new voice activity task -- that is when you call
991     * {@link #startVoiceActivity}.</p>
992     */
993    public void setKeepAwake(boolean keepAwake) {
994        if (mToken == null) {
995            throw new IllegalStateException("Can't call before onCreate()");
996        }
997        try {
998            mSystemService.setKeepAwake(mToken, keepAwake);
999        } catch (RemoteException e) {
1000        }
1001    }
1002
1003    /**
1004     * Request that all system dialogs (and status bar shade etc) be closed, allowing
1005     * access to the session's UI.  This will <em>not</em> cause the lock screen to be
1006     * dismissed.
1007     */
1008    public void closeSystemDialogs() {
1009        if (mToken == null) {
1010            throw new IllegalStateException("Can't call before onCreate()");
1011        }
1012        try {
1013            mSystemService.closeSystemDialogs(mToken);
1014        } catch (RemoteException e) {
1015        }
1016    }
1017
1018    /**
1019     * Convenience for inflating views.
1020     */
1021    public LayoutInflater getLayoutInflater() {
1022        return mInflater;
1023    }
1024
1025    /**
1026     * Retrieve the window being used to show the session's UI.
1027     */
1028    public Dialog getWindow() {
1029        return mWindow;
1030    }
1031
1032    /**
1033     * Finish the session.  This completely destroys the session -- the next time it is shown,
1034     * an entirely new one will be created.  You do not normally call this function; instead,
1035     * use {@link #hide} and allow the system to destroy your session if it needs its RAM.
1036     */
1037    public void finish() {
1038        if (mToken == null) {
1039            throw new IllegalStateException("Can't call before onCreate()");
1040        }
1041        try {
1042            mSystemService.finish(mToken);
1043        } catch (RemoteException e) {
1044        }
1045    }
1046
1047    /**
1048     * Initiatize a new session.  At this point you don't know exactly what this
1049     * session will be used for; you will find that out in {@link #onShow}.
1050     */
1051    public void onCreate() {
1052        doOnCreate();
1053    }
1054
1055    private void doOnCreate() {
1056        mTheme = mTheme != 0 ? mTheme
1057                : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession;
1058        mInflater = (LayoutInflater)mContext.getSystemService(
1059                Context.LAYOUT_INFLATER_SERVICE);
1060        mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
1061                mCallbacks, this, mDispatcherState,
1062                WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true);
1063        mWindow.getWindow().addFlags(
1064                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED |
1065                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
1066                WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
1067        initViews();
1068        mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT);
1069        mWindow.setToken(mToken);
1070    }
1071
1072    /**
1073     * Called when the session UI is going to be shown.  This is called after
1074     * {@link #onCreateContentView} (if the session's content UI needed to be created) and
1075     * immediately prior to the window being shown.  This may be called while the window
1076     * is already shown, if a show request has come in while it is shown, to allow you to
1077     * update the UI to match the new show arguments.
1078     *
1079     * @param args The arguments that were supplied to
1080     * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
1081     * @param showFlags The show flags originally provided to
1082     * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
1083     */
1084    public void onShow(Bundle args, int showFlags) {
1085    }
1086
1087    /**
1088     * Called immediately after stopping to show the session UI.
1089     */
1090    public void onHide() {
1091    }
1092
1093    /**
1094     * Last callback to the session as it is being finished.
1095     */
1096    public void onDestroy() {
1097    }
1098
1099    /**
1100     * Hook in which to create the session's UI.
1101     */
1102    public View onCreateContentView() {
1103        return null;
1104    }
1105
1106    public void setContentView(View view) {
1107        mContentFrame.removeAllViews();
1108        mContentFrame.addView(view, new FrameLayout.LayoutParams(
1109                ViewGroup.LayoutParams.MATCH_PARENT,
1110                ViewGroup.LayoutParams.MATCH_PARENT));
1111        mContentFrame.requestApplyInsets();
1112    }
1113
1114    public void onHandleAssist(Bundle data, AssistStructure structure, AssistContent content) {
1115    }
1116
1117    public void onHandleScreenshot(Bitmap screenshot) {
1118    }
1119
1120    public boolean onKeyDown(int keyCode, KeyEvent event) {
1121        return false;
1122    }
1123
1124    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
1125        return false;
1126    }
1127
1128    public boolean onKeyUp(int keyCode, KeyEvent event) {
1129        return false;
1130    }
1131
1132    public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
1133        return false;
1134    }
1135
1136    /**
1137     * Called when the user presses the back button while focus is in the session UI.  Note
1138     * that this will only happen if the session UI has requested input focus in its window;
1139     * otherwise, the back key will go to whatever window has focus and do whatever behavior
1140     * it normally has there.  The default implementation simply calls {@link #hide}.
1141     */
1142    public void onBackPressed() {
1143        hide();
1144    }
1145
1146    /**
1147     * Sessions automatically watch for requests that all system UI be closed (such as when
1148     * the user presses HOME), which will appear here.  The default implementation always
1149     * calls {@link #hide}.
1150     */
1151    public void onCloseSystemDialogs() {
1152        hide();
1153    }
1154
1155    @Override
1156    public void onConfigurationChanged(Configuration newConfig) {
1157    }
1158
1159    @Override
1160    public void onLowMemory() {
1161    }
1162
1163    @Override
1164    public void onTrimMemory(int level) {
1165    }
1166
1167    /**
1168     * Compute the interesting insets into your UI.  The default implementation
1169     * sets {@link Insets#contentInsets outInsets.contentInsets.top} to the height
1170     * of the window, meaning it should not adjust content underneath.  The default touchable
1171     * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}, meaning it consumes all touch
1172     * events within its window frame.
1173     *
1174     * @param outInsets Fill in with the current UI insets.
1175     */
1176    public void onComputeInsets(Insets outInsets) {
1177        outInsets.contentInsets.left = 0;
1178        outInsets.contentInsets.bottom = 0;
1179        outInsets.contentInsets.right = 0;
1180        View decor = getWindow().getWindow().getDecorView();
1181        outInsets.contentInsets.top = decor.getHeight();
1182        outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME;
1183        outInsets.touchableRegion.setEmpty();
1184    }
1185
1186    /**
1187     * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
1188     * has actually started.
1189     *
1190     * @param intent The original {@link Intent} supplied to
1191     * {@link #startVoiceActivity(android.content.Intent)}.
1192     * @param taskId Unique ID of the now running task.
1193     */
1194    public void onTaskStarted(Intent intent, int taskId) {
1195    }
1196
1197    /**
1198     * Called when the last activity of a task initiated by
1199     * {@link #startVoiceActivity(android.content.Intent)} has finished.  The default
1200     * implementation calls {@link #finish()} on the assumption that this represents
1201     * the completion of a voice action.  You can override the implementation if you would
1202     * like a different behavior.
1203     *
1204     * @param intent The original {@link Intent} supplied to
1205     * {@link #startVoiceActivity(android.content.Intent)}.
1206     * @param taskId Unique ID of the finished task.
1207     */
1208    public void onTaskFinished(Intent intent, int taskId) {
1209        hide();
1210    }
1211
1212    /**
1213     * Request to query for what extended commands the session supports.
1214     *
1215     * @param commands An array of commands that are being queried.
1216     * @return Return an array of booleans indicating which of each entry in the
1217     * command array is supported.  A true entry in the array indicates the command
1218     * is supported; false indicates it is not.  The default implementation returns
1219     * an array of all false entries.
1220     */
1221    public boolean[] onGetSupportedCommands(String[] commands) {
1222        return new boolean[commands.length];
1223    }
1224
1225    /**
1226     * Request to confirm with the user before proceeding with an unrecoverable operation,
1227     * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
1228     * VoiceInteractor.ConfirmationRequest}.
1229     *
1230     * @param request The active request.
1231     */
1232    public void onRequestConfirmation(ConfirmationRequest request) {
1233    }
1234
1235    /**
1236     * Request for the user to pick one of N options, corresponding to a
1237     * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
1238     *
1239     * @param request The active request.
1240     */
1241    public void onRequestPickOption(PickOptionRequest request) {
1242    }
1243
1244    /**
1245     * Request to complete the voice interaction session because the voice activity successfully
1246     * completed its interaction using voice.  Corresponds to
1247     * {@link android.app.VoiceInteractor.CompleteVoiceRequest
1248     * VoiceInteractor.CompleteVoiceRequest}.  The default implementation just sends an empty
1249     * confirmation back to allow the activity to exit.
1250     *
1251     * @param request The active request.
1252     */
1253    public void onRequestCompleteVoice(CompleteVoiceRequest request) {
1254    }
1255
1256    /**
1257     * Request to abort the voice interaction session because the voice activity can not
1258     * complete its interaction using voice.  Corresponds to
1259     * {@link android.app.VoiceInteractor.AbortVoiceRequest
1260     * VoiceInteractor.AbortVoiceRequest}.  The default implementation just sends an empty
1261     * confirmation back to allow the activity to exit.
1262     *
1263     * @param request The active request.
1264     */
1265    public void onRequestAbortVoice(AbortVoiceRequest request) {
1266    }
1267
1268    /**
1269     * Process an arbitrary extended command from the caller,
1270     * corresponding to a {@link android.app.VoiceInteractor.CommandRequest
1271     * VoiceInteractor.CommandRequest}.
1272     *
1273     * @param request The active request.
1274     */
1275    public void onRequestCommand(CommandRequest request) {
1276    }
1277
1278    /**
1279     * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
1280     * that was previously delivered to {@link #onRequestConfirmation},
1281     * {@link #onRequestPickOption}, {@link #onRequestCompleteVoice}, {@link #onRequestAbortVoice},
1282     * or {@link #onRequestCommand}.
1283     *
1284     * @param request The request that is being canceled.
1285     */
1286    public void onCancelRequest(Request request) {
1287    }
1288}
1289