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