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