VoiceInteractor.java revision 16036f26847f3f1a88a093fb776bf081008ff8d8
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.app; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.content.Context; 22import android.os.Bundle; 23import android.os.IBinder; 24import android.os.Looper; 25import android.os.Message; 26import android.os.Parcel; 27import android.os.Parcelable; 28import android.os.RemoteException; 29import android.util.ArrayMap; 30import android.util.Log; 31import com.android.internal.app.IVoiceInteractor; 32import com.android.internal.app.IVoiceInteractorCallback; 33import com.android.internal.app.IVoiceInteractorRequest; 34import com.android.internal.os.HandlerCaller; 35import com.android.internal.os.SomeArgs; 36 37import java.util.ArrayList; 38 39/** 40 * Interface for an {@link Activity} to interact with the user through voice. Use 41 * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor} 42 * to retrieve the interface, if the activity is currently involved in a voice interaction. 43 * 44 * <p>The voice interactor revolves around submitting voice interaction requests to the 45 * back-end voice interaction service that is working with the user. These requests are 46 * submitted with {@link #submitRequest}, providing a new instance of a 47 * {@link Request} subclass describing the type of operation to perform -- currently the 48 * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}. 49 * 50 * <p>Once a request is submitted, the voice system will process it and eventually deliver 51 * the result to the request object. The application can cancel a pending request at any 52 * time. 53 * 54 * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that 55 * if an activity is being restarted with retained state, it will retain the current 56 * VoiceInteractor and any outstanding requests. Because of this, you should always use 57 * {@link Request#getActivity() Request.getActivity} to get back to the activity of a 58 * request, rather than holding on to the activity instance yourself, either explicitly 59 * or implicitly through a non-static inner class. 60 */ 61public final class VoiceInteractor { 62 static final String TAG = "VoiceInteractor"; 63 static final boolean DEBUG = false; 64 65 static final Request[] NO_REQUESTS = new Request[0]; 66 67 final IVoiceInteractor mInteractor; 68 69 Context mContext; 70 Activity mActivity; 71 72 final HandlerCaller mHandlerCaller; 73 final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() { 74 @Override 75 public void executeMessage(Message msg) { 76 SomeArgs args = (SomeArgs)msg.obj; 77 Request request; 78 boolean complete; 79 switch (msg.what) { 80 case MSG_CONFIRMATION_RESULT: 81 request = pullRequest((IVoiceInteractorRequest)args.arg1, true); 82 if (DEBUG) Log.d(TAG, "onConfirmResult: req=" 83 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request 84 + " confirmed=" + msg.arg1 + " result=" + args.arg2); 85 if (request != null) { 86 ((ConfirmationRequest)request).onConfirmationResult(msg.arg1 != 0, 87 (Bundle) args.arg2); 88 request.clear(); 89 } 90 break; 91 case MSG_PICK_OPTION_RESULT: 92 complete = msg.arg1 != 0; 93 request = pullRequest((IVoiceInteractorRequest)args.arg1, complete); 94 if (DEBUG) Log.d(TAG, "onPickOptionResult: req=" 95 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request 96 + " finished=" + complete + " selection=" + args.arg2 97 + " result=" + args.arg3); 98 if (request != null) { 99 ((PickOptionRequest)request).onPickOptionResult(complete, 100 (PickOptionRequest.Option[]) args.arg2, (Bundle) args.arg3); 101 if (complete) { 102 request.clear(); 103 } 104 } 105 break; 106 case MSG_COMPLETE_VOICE_RESULT: 107 request = pullRequest((IVoiceInteractorRequest)args.arg1, true); 108 if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" 109 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request 110 + " result=" + args.arg2); 111 if (request != null) { 112 ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2); 113 request.clear(); 114 } 115 break; 116 case MSG_ABORT_VOICE_RESULT: 117 request = pullRequest((IVoiceInteractorRequest)args.arg1, true); 118 if (DEBUG) Log.d(TAG, "onAbortVoice: req=" 119 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request 120 + " result=" + args.arg2); 121 if (request != null) { 122 ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2); 123 request.clear(); 124 } 125 break; 126 case MSG_COMMAND_RESULT: 127 complete = msg.arg1 != 0; 128 request = pullRequest((IVoiceInteractorRequest)args.arg1, complete); 129 if (DEBUG) Log.d(TAG, "onCommandResult: req=" 130 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request 131 + " completed=" + msg.arg1 + " result=" + args.arg2); 132 if (request != null) { 133 ((CommandRequest)request).onCommandResult(msg.arg1 != 0, 134 (Bundle) args.arg2); 135 if (complete) { 136 request.clear(); 137 } 138 } 139 break; 140 case MSG_CANCEL_RESULT: 141 request = pullRequest((IVoiceInteractorRequest)args.arg1, true); 142 if (DEBUG) Log.d(TAG, "onCancelResult: req=" 143 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request); 144 if (request != null) { 145 request.onCancel(); 146 request.clear(); 147 } 148 break; 149 } 150 } 151 }; 152 153 final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() { 154 @Override 155 public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean finished, 156 Bundle result) { 157 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( 158 MSG_CONFIRMATION_RESULT, finished ? 1 : 0, request, result)); 159 } 160 161 @Override 162 public void deliverPickOptionResult(IVoiceInteractorRequest request, 163 boolean finished, PickOptionRequest.Option[] options, Bundle result) { 164 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO( 165 MSG_PICK_OPTION_RESULT, finished ? 1 : 0, request, options, result)); 166 } 167 168 @Override 169 public void deliverCompleteVoiceResult(IVoiceInteractorRequest request, Bundle result) { 170 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO( 171 MSG_COMPLETE_VOICE_RESULT, request, result)); 172 } 173 174 @Override 175 public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) { 176 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO( 177 MSG_ABORT_VOICE_RESULT, request, result)); 178 } 179 180 @Override 181 public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, 182 Bundle result) { 183 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( 184 MSG_COMMAND_RESULT, complete ? 1 : 0, request, result)); 185 } 186 187 @Override 188 public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException { 189 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO( 190 MSG_CANCEL_RESULT, request, null)); 191 } 192 }; 193 194 final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<>(); 195 196 static final int MSG_CONFIRMATION_RESULT = 1; 197 static final int MSG_PICK_OPTION_RESULT = 2; 198 static final int MSG_COMPLETE_VOICE_RESULT = 3; 199 static final int MSG_ABORT_VOICE_RESULT = 4; 200 static final int MSG_COMMAND_RESULT = 5; 201 static final int MSG_CANCEL_RESULT = 6; 202 203 /** 204 * Base class for voice interaction requests that can be submitted to the interactor. 205 * Do not instantiate this directly -- instead, use the appropriate subclass. 206 */ 207 public static abstract class Request { 208 IVoiceInteractorRequest mRequestInterface; 209 Context mContext; 210 Activity mActivity; 211 String mName; 212 213 Request() { 214 } 215 216 /** 217 * Return the name this request was submitted through 218 * {@link #submitRequest(android.app.VoiceInteractor.Request, String)}. 219 */ 220 public String getName() { 221 return mName; 222 } 223 224 /** 225 * Cancel this active request. 226 */ 227 public void cancel() { 228 if (mRequestInterface == null) { 229 throw new IllegalStateException("Request " + this + " is no longer active"); 230 } 231 try { 232 mRequestInterface.cancel(); 233 } catch (RemoteException e) { 234 Log.w(TAG, "Voice interactor has died", e); 235 } 236 } 237 238 /** 239 * Return the current {@link Context} this request is associated with. May change 240 * if the activity hosting it goes through a configuration change. 241 */ 242 public Context getContext() { 243 return mContext; 244 } 245 246 /** 247 * Return the current {@link Activity} this request is associated with. Will change 248 * if the activity is restarted such as through a configuration change. 249 */ 250 public Activity getActivity() { 251 return mActivity; 252 } 253 254 /** 255 * Report from voice interaction service: this operation has been canceled, typically 256 * as a completion of a previous call to {@link #cancel}. 257 */ 258 public void onCancel() { 259 } 260 261 /** 262 * The request is now attached to an activity, or being re-attached to a new activity 263 * after a configuration change. 264 */ 265 public void onAttached(Activity activity) { 266 } 267 268 /** 269 * The request is being detached from an activity. 270 */ 271 public void onDetached() { 272 } 273 274 void clear() { 275 mRequestInterface = null; 276 mContext = null; 277 mActivity = null; 278 mName = null; 279 } 280 281 abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor, 282 String packageName, IVoiceInteractorCallback callback) throws RemoteException; 283 } 284 285 /** 286 * Confirms an operation with the user via the trusted system 287 * VoiceInteractionService. This allows an Activity to complete an unsafe operation that 288 * would require the user to touch the screen when voice interaction mode is not enabled. 289 * The result of the confirmation will be returned through an asynchronous call to 290 * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or 291 * {@link #onCancel()}. 292 * 293 * <p>In some cases this may be a simple yes / no confirmation or the confirmation could 294 * include context information about how the action will be completed 295 * (e.g. booking a cab might include details about how long until the cab arrives) 296 * so the user can give a confirmation. 297 */ 298 public static class ConfirmationRequest extends Request { 299 final Prompt mPrompt; 300 final Bundle mExtras; 301 302 /** 303 * Create a new confirmation request. 304 * @param prompt Optional confirmation to speak to the user or null if nothing 305 * should be spoken. 306 * @param extras Additional optional information or null. 307 */ 308 public ConfirmationRequest(@Nullable Prompt prompt, @Nullable Bundle extras) { 309 mPrompt = prompt; 310 mExtras = extras; 311 } 312 313 /** 314 * Create a new confirmation request. 315 * @param prompt Optional confirmation to speak to the user or null if nothing 316 * should be spoken. 317 * @param extras Additional optional information or null. 318 * @deprecated Prefer the version that takes a {@link Prompt}. 319 */ 320 public ConfirmationRequest(CharSequence prompt, Bundle extras) { 321 mPrompt = (prompt != null ? new Prompt(prompt) : null); 322 mExtras = extras; 323 } 324 325 public void onConfirmationResult(boolean confirmed, Bundle result) { 326 } 327 328 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, 329 IVoiceInteractorCallback callback) throws RemoteException { 330 return interactor.startConfirmation(packageName, callback, mPrompt, mExtras); 331 } 332 } 333 334 /** 335 * Select a single option from multiple potential options with the user via the trusted system 336 * VoiceInteractionService. Typically, the application would present this visually as 337 * a list view to allow selecting the option by touch. 338 * The result of the confirmation will be returned through an asynchronous call to 339 * either {@link #onPickOptionResult} or {@link #onCancel()}. 340 */ 341 public static class PickOptionRequest extends Request { 342 final Prompt mPrompt; 343 final Option[] mOptions; 344 final Bundle mExtras; 345 346 /** 347 * Represents a single option that the user may select using their voice. 348 */ 349 public static final class Option implements Parcelable { 350 final CharSequence mLabel; 351 final int mIndex; 352 ArrayList<CharSequence> mSynonyms; 353 Bundle mExtras; 354 355 /** 356 * Creates an option that a user can select with their voice by matching the label 357 * or one of several synonyms. 358 * @param label The label that will both be matched against what the user speaks 359 * and displayed visually. 360 */ 361 public Option(CharSequence label) { 362 mLabel = label; 363 mIndex = -1; 364 } 365 366 /** 367 * Creates an option that a user can select with their voice by matching the label 368 * or one of several synonyms. 369 * @param label The label that will both be matched against what the user speaks 370 * and displayed visually. 371 * @param index The location of this option within the overall set of options. 372 * Can be used to help identify the option when it is returned from the 373 * voice interactor. 374 */ 375 public Option(CharSequence label, int index) { 376 mLabel = label; 377 mIndex = index; 378 } 379 380 /** 381 * Add a synonym term to the option to indicate an alternative way the content 382 * may be matched. 383 * @param synonym The synonym that will be matched against what the user speaks, 384 * but not displayed. 385 */ 386 public Option addSynonym(CharSequence synonym) { 387 if (mSynonyms == null) { 388 mSynonyms = new ArrayList<>(); 389 } 390 mSynonyms.add(synonym); 391 return this; 392 } 393 394 public CharSequence getLabel() { 395 return mLabel; 396 } 397 398 /** 399 * Return the index that was supplied in the constructor. 400 * If the option was constructed without an index, -1 is returned. 401 */ 402 public int getIndex() { 403 return mIndex; 404 } 405 406 public int countSynonyms() { 407 return mSynonyms != null ? mSynonyms.size() : 0; 408 } 409 410 public CharSequence getSynonymAt(int index) { 411 return mSynonyms != null ? mSynonyms.get(index) : null; 412 } 413 414 /** 415 * Set optional extra information associated with this option. Note that this 416 * method takes ownership of the supplied extras Bundle. 417 */ 418 public void setExtras(Bundle extras) { 419 mExtras = extras; 420 } 421 422 /** 423 * Return any optional extras information associated with this option, or null 424 * if there is none. Note that this method returns a reference to the actual 425 * extras Bundle in the option, so modifications to it will directly modify the 426 * extras in the option. 427 */ 428 public Bundle getExtras() { 429 return mExtras; 430 } 431 432 Option(Parcel in) { 433 mLabel = in.readCharSequence(); 434 mIndex = in.readInt(); 435 mSynonyms = in.readCharSequenceList(); 436 mExtras = in.readBundle(); 437 } 438 439 @Override 440 public int describeContents() { 441 return 0; 442 } 443 444 @Override 445 public void writeToParcel(Parcel dest, int flags) { 446 dest.writeCharSequence(mLabel); 447 dest.writeInt(mIndex); 448 dest.writeCharSequenceList(mSynonyms); 449 dest.writeBundle(mExtras); 450 } 451 452 public static final Parcelable.Creator<Option> CREATOR 453 = new Parcelable.Creator<Option>() { 454 public Option createFromParcel(Parcel in) { 455 return new Option(in); 456 } 457 458 public Option[] newArray(int size) { 459 return new Option[size]; 460 } 461 }; 462 }; 463 464 /** 465 * Create a new pick option request. 466 * @param prompt Optional question to be asked of the user when the options are 467 * presented or null if nothing should be asked. 468 * @param options The set of {@link Option}s the user is selecting from. 469 * @param extras Additional optional information or null. 470 */ 471 public PickOptionRequest(@Nullable Prompt prompt, Option[] options, 472 @Nullable Bundle extras) { 473 mPrompt = prompt; 474 mOptions = options; 475 mExtras = extras; 476 } 477 478 /** 479 * Create a new pick option request. 480 * @param prompt Optional question to be asked of the user when the options are 481 * presented or null if nothing should be asked. 482 * @param options The set of {@link Option}s the user is selecting from. 483 * @param extras Additional optional information or null. 484 * @deprecated Prefer the version that takes a {@link Prompt}. 485 */ 486 public PickOptionRequest(CharSequence prompt, Option[] options, Bundle extras) { 487 mPrompt = (prompt != null ? new Prompt(prompt) : null); 488 mOptions = options; 489 mExtras = extras; 490 } 491 492 /** 493 * Called when a single option is confirmed or narrowed to one of several options. 494 * @param finished True if the voice interaction has finished making a selection, in 495 * which case {@code selections} contains the final result. If false, this request is 496 * still active and you will continue to get calls on it. 497 * @param selections Either a single {@link Option} or one of several {@link Option}s the 498 * user has narrowed the choices down to. 499 * @param result Additional optional information. 500 */ 501 public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { 502 } 503 504 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, 505 IVoiceInteractorCallback callback) throws RemoteException { 506 return interactor.startPickOption(packageName, callback, mPrompt, mOptions, mExtras); 507 } 508 } 509 510 /** 511 * Reports that the current interaction was successfully completed with voice, so the 512 * application can report the final status to the user. When the response comes back, the 513 * voice system has handled the request and is ready to switch; at that point the 514 * application can start a new non-voice activity or finish. Be sure when starting the new 515 * activity to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK 516 * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice 517 * interaction task. 518 */ 519 public static class CompleteVoiceRequest extends Request { 520 final Prompt mPrompt; 521 final Bundle mExtras; 522 523 /** 524 * Create a new completed voice interaction request. 525 * @param prompt Optional message to speak to the user about the completion status of 526 * the task or null if nothing should be spoken. 527 * @param extras Additional optional information or null. 528 */ 529 public CompleteVoiceRequest(@Nullable Prompt prompt, @Nullable Bundle extras) { 530 mPrompt = prompt; 531 mExtras = extras; 532 } 533 534 /** 535 * Create a new completed voice interaction request. 536 * @param message Optional message to speak to the user about the completion status of 537 * the task or null if nothing should be spoken. 538 * @param extras Additional optional information or null. 539 * @deprecated Prefer the version that takes a {@link Prompt}. 540 */ 541 public CompleteVoiceRequest(CharSequence message, Bundle extras) { 542 mPrompt = (message != null ? new Prompt(message) : null); 543 mExtras = extras; 544 } 545 546 public void onCompleteResult(Bundle result) { 547 } 548 549 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, 550 IVoiceInteractorCallback callback) throws RemoteException { 551 return interactor.startCompleteVoice(packageName, callback, mPrompt, mExtras); 552 } 553 } 554 555 /** 556 * Reports that the current interaction can not be complete with voice, so the 557 * application will need to switch to a traditional input UI. Applications should 558 * only use this when they need to completely bail out of the voice interaction 559 * and switch to a traditional UI. When the response comes back, the voice 560 * system has handled the request and is ready to switch; at that point the application 561 * can start a new non-voice activity. Be sure when starting the new activity 562 * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK 563 * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice 564 * interaction task. 565 */ 566 public static class AbortVoiceRequest extends Request { 567 final Prompt mPrompt; 568 final Bundle mExtras; 569 570 /** 571 * Create a new voice abort request. 572 * @param prompt Optional message to speak to the user indicating why the task could 573 * not be completed by voice or null if nothing should be spoken. 574 * @param extras Additional optional information or null. 575 */ 576 public AbortVoiceRequest(@Nullable Prompt prompt, @Nullable Bundle extras) { 577 mPrompt = prompt; 578 mExtras = extras; 579 } 580 581 /** 582 * Create a new voice abort request. 583 * @param message Optional message to speak to the user indicating why the task could 584 * not be completed by voice or null if nothing should be spoken. 585 * @param extras Additional optional information or null. 586 * @deprecated Prefer the version that takes a {@link Prompt}. 587 */ 588 public AbortVoiceRequest(CharSequence message, Bundle extras) { 589 mPrompt = (message != null ? new Prompt(message) : null); 590 mExtras = extras; 591 } 592 593 public void onAbortResult(Bundle result) { 594 } 595 596 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, 597 IVoiceInteractorCallback callback) throws RemoteException { 598 return interactor.startAbortVoice(packageName, callback, mPrompt, mExtras); 599 } 600 } 601 602 /** 603 * Execute a vendor-specific command using the trusted system VoiceInteractionService. 604 * This allows an Activity to request additional information from the user needed to 605 * complete an action (e.g. booking a table might have several possible times that the 606 * user could select from or an app might need the user to agree to a terms of service). 607 * The result of the confirmation will be returned through an asynchronous call to 608 * either {@link #onCommandResult(boolean, android.os.Bundle)} or 609 * {@link #onCancel()}. 610 * 611 * <p>The command is a string that describes the generic operation to be performed. 612 * The command will determine how the properties in extras are interpreted and the set of 613 * available commands is expected to grow over time. An example might be 614 * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of 615 * airline check-in. (This is not an actual working example.) 616 */ 617 public static class CommandRequest extends Request { 618 final String mCommand; 619 final Bundle mArgs; 620 621 /** 622 * Create a new generic command request. 623 * @param command The desired command to perform. 624 * @param args Additional arguments to control execution of the command. 625 */ 626 public CommandRequest(String command, Bundle args) { 627 mCommand = command; 628 mArgs = args; 629 } 630 631 /** 632 * Results for CommandRequest can be returned in partial chunks. 633 * The isCompleted is set to true iff all results have been returned, indicating the 634 * CommandRequest has completed. 635 */ 636 public void onCommandResult(boolean isCompleted, Bundle result) { 637 } 638 639 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, 640 IVoiceInteractorCallback callback) throws RemoteException { 641 return interactor.startCommand(packageName, callback, mCommand, mArgs); 642 } 643 } 644 645 /** 646 * A set of voice prompts to use with the voice interaction system to confirm an action, select 647 * an option, or do similar operations. Multiple voice prompts may be provided for variety. A 648 * visual prompt must be provided, which might not match the spoken version. For example, the 649 * confirmation "Are you sure you want to purchase this item?" might use a visual label like 650 * "Purchase item". 651 */ 652 public static class Prompt implements Parcelable { 653 // Mandatory voice prompt. Must contain at least one item, which must not be null. 654 private final CharSequence[] mVoicePrompts; 655 656 // Mandatory visual prompt. 657 private final CharSequence mVisualPrompt; 658 659 /** 660 * Constructs a prompt set. 661 * @param voicePrompts An array of one or more voice prompts. Must not be empty or null. 662 * @param visualPrompt A prompt to display on the screen. Must not be null. 663 */ 664 public Prompt(@NonNull CharSequence[] voicePrompts, @NonNull CharSequence visualPrompt) { 665 if (voicePrompts == null) { 666 throw new NullPointerException("voicePrompts must not be null"); 667 } 668 if (voicePrompts.length == 0) { 669 throw new IllegalArgumentException("voicePrompts must not be empty"); 670 } 671 if (visualPrompt == null) { 672 throw new NullPointerException("visualPrompt must not be null"); 673 } 674 this.mVoicePrompts = voicePrompts; 675 this.mVisualPrompt = visualPrompt; 676 } 677 678 /** 679 * Constructs a prompt set with single prompt used for all interactions. This is most useful 680 * in test apps. Non-trivial apps should prefer the detailed constructor. 681 */ 682 public Prompt(@NonNull CharSequence prompt) { 683 this.mVoicePrompts = new CharSequence[] { prompt }; 684 this.mVisualPrompt = prompt; 685 } 686 687 /** 688 * Returns a prompt to use for voice interactions. 689 */ 690 @NonNull 691 public CharSequence getVoicePromptAt(int index) { 692 return mVoicePrompts[index]; 693 } 694 695 /** 696 * Returns the number of different voice prompts. 697 */ 698 public int countVoicePrompts() { 699 return mVoicePrompts.length; 700 } 701 702 /** 703 * Returns the prompt to use for visual display. 704 */ 705 @NonNull 706 public CharSequence getVisualPrompt() { 707 return mVisualPrompt; 708 } 709 710 /** Constructor to support Parcelable behavior. */ 711 Prompt(Parcel in) { 712 mVoicePrompts = in.readCharSequenceArray(); 713 mVisualPrompt = in.readCharSequence(); 714 } 715 716 @Override 717 public int describeContents() { 718 return 0; 719 } 720 721 @Override 722 public void writeToParcel(Parcel dest, int flags) { 723 dest.writeCharSequenceArray(mVoicePrompts); 724 dest.writeCharSequence(mVisualPrompt); 725 } 726 727 public static final Creator<Prompt> CREATOR 728 = new Creator<Prompt>() { 729 public Prompt createFromParcel(Parcel in) { 730 return new Prompt(in); 731 } 732 733 public Prompt[] newArray(int size) { 734 return new Prompt[size]; 735 } 736 }; 737 } 738 739 VoiceInteractor(IVoiceInteractor interactor, Context context, Activity activity, 740 Looper looper) { 741 mInteractor = interactor; 742 mContext = context; 743 mActivity = activity; 744 mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true); 745 } 746 747 Request pullRequest(IVoiceInteractorRequest request, boolean complete) { 748 synchronized (mActiveRequests) { 749 Request req = mActiveRequests.get(request.asBinder()); 750 if (req != null && complete) { 751 mActiveRequests.remove(request.asBinder()); 752 } 753 return req; 754 } 755 } 756 757 private ArrayList<Request> makeRequestList() { 758 final int N = mActiveRequests.size(); 759 if (N < 1) { 760 return null; 761 } 762 ArrayList<Request> list = new ArrayList<Request>(N); 763 for (int i=0; i<N; i++) { 764 list.add(mActiveRequests.valueAt(i)); 765 } 766 return list; 767 } 768 769 void attachActivity(Activity activity) { 770 if (mActivity == activity) { 771 return; 772 } 773 mContext = activity; 774 mActivity = activity; 775 ArrayList<Request> reqs = makeRequestList(); 776 if (reqs != null) { 777 for (int i=0; i<reqs.size(); i++) { 778 Request req = reqs.get(i); 779 req.mContext = activity; 780 req.mActivity = activity; 781 req.onAttached(activity); 782 } 783 } 784 } 785 786 void detachActivity() { 787 ArrayList<Request> reqs = makeRequestList(); 788 if (reqs != null) { 789 for (int i=0; i<reqs.size(); i++) { 790 Request req = reqs.get(i); 791 req.onDetached(); 792 req.mActivity = null; 793 req.mContext = null; 794 } 795 } 796 mContext = null; 797 mActivity = null; 798 } 799 800 public boolean submitRequest(Request request) { 801 return submitRequest(request, null); 802 } 803 804 /** 805 * Submit a new {@link Request} to the voice interaction service. The request must be 806 * one of the available subclasses -- {@link ConfirmationRequest}, {@link PickOptionRequest}, 807 * {@link CompleteVoiceRequest}, {@link AbortVoiceRequest}, or {@link CommandRequest}. 808 * 809 * @param request The desired request to submit. 810 * @param name An optional name for this request, or null. This can be used later with 811 * {@link #getActiveRequests} and {@link #getActiveRequest} to find the request. 812 * 813 * @return Returns true of the request was successfully submitted, else false. 814 */ 815 public boolean submitRequest(Request request, String name) { 816 try { 817 if (request.mRequestInterface != null) { 818 throw new IllegalStateException("Given " + request + " is already active"); 819 } 820 IVoiceInteractorRequest ireq = request.submit(mInteractor, 821 mContext.getOpPackageName(), mCallback); 822 request.mRequestInterface = ireq; 823 request.mContext = mContext; 824 request.mActivity = mActivity; 825 request.mName = name; 826 synchronized (mActiveRequests) { 827 mActiveRequests.put(ireq.asBinder(), request); 828 } 829 return true; 830 } catch (RemoteException e) { 831 Log.w(TAG, "Remove voice interactor service died", e); 832 return false; 833 } 834 } 835 836 /** 837 * Return all currently active requests. 838 */ 839 public Request[] getActiveRequests() { 840 synchronized (mActiveRequests) { 841 final int N = mActiveRequests.size(); 842 if (N <= 0) { 843 return NO_REQUESTS; 844 } 845 Request[] requests = new Request[N]; 846 for (int i=0; i<N; i++) { 847 requests[i] = mActiveRequests.valueAt(i); 848 } 849 return requests; 850 } 851 } 852 853 /** 854 * Return any currently active request that was submitted with the given name. 855 * 856 * @param name The name used to submit the request, as per 857 * {@link #submitRequest(android.app.VoiceInteractor.Request, String)}. 858 * @return Returns the active request with that name, or null if there was none. 859 */ 860 public Request getActiveRequest(String name) { 861 synchronized (mActiveRequests) { 862 final int N = mActiveRequests.size(); 863 for (int i=0; i<N; i++) { 864 Request req = mActiveRequests.valueAt(i); 865 if (name == req.getName() || (name != null && name.equals(req.getName()))) { 866 return req; 867 } 868 } 869 } 870 return null; 871 } 872 873 /** 874 * Queries the supported commands available from the VoiceInteractionService. 875 * The command is a string that describes the generic operation to be performed. 876 * An example might be "org.example.commands.PICK_DATE" to ask the user to pick 877 * a date. (Note: This is not an actual working example.) 878 * 879 * @param commands The array of commands to query for support. 880 * @return Array of booleans indicating whether each command is supported or not. 881 */ 882 public boolean[] supportsCommands(String[] commands) { 883 try { 884 boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands); 885 if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res); 886 return res; 887 } catch (RemoteException e) { 888 throw new RuntimeException("Voice interactor has died", e); 889 } 890 } 891} 892