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