blob: 823c4271adf2df8fd9d87660c535b242c4759b63 [file] [log] [blame]
Dianne Hackborn91097de2014-04-04 18:02:06 -07001/*
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
James Cook6cf39752015-06-04 14:18:43 -070019import android.annotation.NonNull;
20import android.annotation.Nullable;
Dianne Hackborn91097de2014-04-04 18:02:06 -070021import android.content.Context;
22import android.os.Bundle;
23import android.os.IBinder;
24import android.os.Looper;
25import android.os.Message;
Dianne Hackborn3d07c942015-03-13 18:02:54 -070026import android.os.Parcel;
27import android.os.Parcelable;
Dianne Hackborn91097de2014-04-04 18:02:06 -070028import android.os.RemoteException;
Dianne Hackborn18f0d352014-04-25 17:06:18 -070029import android.util.ArrayMap;
Dianne Hackborn57dd7372015-07-27 18:11:14 -070030import android.util.DebugUtils;
Dianne Hackborn91097de2014-04-04 18:02:06 -070031import android.util.Log;
32import com.android.internal.app.IVoiceInteractor;
33import com.android.internal.app.IVoiceInteractorCallback;
34import com.android.internal.app.IVoiceInteractorRequest;
35import com.android.internal.os.HandlerCaller;
36import com.android.internal.os.SomeArgs;
37
Dianne Hackborn57dd7372015-07-27 18:11:14 -070038import java.io.FileDescriptor;
39import java.io.PrintWriter;
Dianne Hackborn20d94742014-05-29 18:35:45 -070040import java.util.ArrayList;
Dianne Hackborn91097de2014-04-04 18:02:06 -070041
42/**
Dianne Hackborna2c076d2014-05-30 16:42:57 -070043 * 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 *
Fabrice Di Meglioca376022014-05-30 18:20:50 -070053 * <p>Once a request is submitted, the voice system will process it and eventually deliver
Dianne Hackborna2c076d2014-05-30 16:42:57 -070054 * 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
Fabrice Di Meglioca376022014-05-30 18:20:50 -070061 * request, rather than holding on to the activity instance yourself, either explicitly
Dianne Hackborna2c076d2014-05-30 16:42:57 -070062 * or implicitly through a non-static inner class.
Dianne Hackborn91097de2014-04-04 18:02:06 -070063 */
Dianne Hackborna3acdb32015-06-08 17:07:40 -070064public final class VoiceInteractor {
Dianne Hackborn91097de2014-04-04 18:02:06 -070065 static final String TAG = "VoiceInteractor";
Dianne Hackborna3acdb32015-06-08 17:07:40 -070066 static final boolean DEBUG = false;
67
68 static final Request[] NO_REQUESTS = new Request[0];
Dianne Hackborn91097de2014-04-04 18:02:06 -070069
Dianne Hackborn91097de2014-04-04 18:02:06 -070070 final IVoiceInteractor mInteractor;
Dianne Hackborn20d94742014-05-29 18:35:45 -070071
72 Context mContext;
73 Activity mActivity;
Dianne Hackborn57dd7372015-07-27 18:11:14 -070074 boolean mRetaining;
Dianne Hackborn20d94742014-05-29 18:35:45 -070075
Dianne Hackborn91097de2014-04-04 18:02:06 -070076 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;
Dianne Hackborn18f0d352014-04-25 17:06:18 -070081 Request request;
Dianne Hackborn3d07c942015-03-13 18:02:54 -070082 boolean complete;
Dianne Hackborn91097de2014-04-04 18:02:06 -070083 switch (msg.what) {
84 case MSG_CONFIRMATION_RESULT:
Dianne Hackborn18f0d352014-04-25 17:06:18 -070085 request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
Dianne Hackborn91097de2014-04-04 18:02:06 -070086 if (DEBUG) Log.d(TAG, "onConfirmResult: req="
Dianne Hackborn18f0d352014-04-25 17:06:18 -070087 + ((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 }
Dianne Hackborn91097de2014-04-04 18:02:06 -070094 break;
Dianne Hackborn3d07c942015-03-13 18:02:54 -070095 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;
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700110 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
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700114 + " result=" + args.arg2);
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700115 if (request != null) {
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700116 ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2);
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700117 request.clear();
118 }
119 break;
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700120 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
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700124 + " result=" + args.arg2);
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700125 if (request != null) {
126 ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2);
127 request.clear();
128 }
129 break;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700130 case MSG_COMMAND_RESULT:
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700131 complete = msg.arg1 != 0;
132 request = pullRequest((IVoiceInteractorRequest)args.arg1, complete);
Dianne Hackborn91097de2014-04-04 18:02:06 -0700133 if (DEBUG) Log.d(TAG, "onCommandResult: req="
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700134 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
Johnson Hsiehdd329462015-01-20 17:11:40 -0800135 + " completed=" + msg.arg1 + " result=" + args.arg2);
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700136 if (request != null) {
Johnson Hsiehdd329462015-01-20 17:11:40 -0800137 ((CommandRequest)request).onCommandResult(msg.arg1 != 0,
138 (Bundle) args.arg2);
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700139 if (complete) {
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700140 request.clear();
141 }
142 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700143 break;
144 case MSG_CANCEL_RESULT:
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700145 request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
Dianne Hackborn91097de2014-04-04 18:02:06 -0700146 if (DEBUG) Log.d(TAG, "onCancelResult: req="
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700147 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request);
148 if (request != null) {
149 request.onCancel();
150 request.clear();
151 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700152 break;
153 }
154 }
155 };
156
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700157 final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() {
158 @Override
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700159 public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean finished,
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700160 Bundle result) {
161 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700162 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));
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700170 }
171
172 @Override
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700173 public void deliverCompleteVoiceResult(IVoiceInteractorRequest request, Bundle result) {
174 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
175 MSG_COMPLETE_VOICE_RESULT, request, result));
176 }
177
178 @Override
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700179 public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) {
180 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
181 MSG_ABORT_VOICE_RESULT, request, result));
182 }
183
184 @Override
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700185 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 {
Dianne Hackbornffeecb12015-02-25 11:08:11 -0800193 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
194 MSG_CANCEL_RESULT, request, null));
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700195 }
196 };
197
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700198 final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<>();
Dianne Hackborn91097de2014-04-04 18:02:06 -0700199
200 static final int MSG_CONFIRMATION_RESULT = 1;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700201 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;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700206
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700207 /**
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 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700211 public static abstract class Request {
212 IVoiceInteractorRequest mRequestInterface;
213 Context mContext;
214 Activity mActivity;
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700215 String mName;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700216
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700217 Request() {
Dianne Hackborn91097de2014-04-04 18:02:06 -0700218 }
219
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700220 /**
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 */
Dianne Hackborn91097de2014-04-04 18:02:06 -0700231 public void cancel() {
Dianne Hackborn16036f22015-06-22 14:05:51 -0700232 if (mRequestInterface == null) {
233 throw new IllegalStateException("Request " + this + " is no longer active");
234 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700235 try {
236 mRequestInterface.cancel();
237 } catch (RemoteException e) {
238 Log.w(TAG, "Voice interactor has died", e);
239 }
240 }
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700241
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700242 /**
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 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700246 public Context getContext() {
247 return mContext;
248 }
249
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700250 /**
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 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700254 public Activity getActivity() {
255 return mActivity;
256 }
257
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700258 /**
259 * Report from voice interaction service: this operation has been canceled, typically
Barnaby James371a2382015-06-26 22:19:46 -0700260 * as a completion of a previous call to {@link #cancel} or when the user explicitly
261 * cancelled.
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700262 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700263 public void onCancel() {
264 }
265
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700266 /**
267 * The request is now attached to an activity, or being re-attached to a new activity
268 * after a configuration change.
269 */
Dianne Hackborn20d94742014-05-29 18:35:45 -0700270 public void onAttached(Activity activity) {
271 }
272
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700273 /**
274 * The request is being detached from an activity.
275 */
Dianne Hackborn20d94742014-05-29 18:35:45 -0700276 public void onDetached() {
277 }
278
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700279 @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
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700302 void clear() {
303 mRequestInterface = null;
304 mContext = null;
305 mActivity = null;
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700306 mName = null;
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700307 }
308
309 abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor,
310 String packageName, IVoiceInteractorCallback callback) throws RemoteException;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700311 }
312
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700313 /**
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
Barnaby James371a2382015-06-26 22:19:46 -0700319 * {@link #onCancel()} - these methods should be overridden to define the application specific
320 * behavior.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700321 *
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 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700327 public static class ConfirmationRequest extends Request {
James Cook6cf39752015-06-04 14:18:43 -0700328 final Prompt mPrompt;
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700329 final Bundle mExtras;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700330
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700331 /**
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700332 * Create a new confirmation request.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700333 * @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.
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700336 */
James Cook6cf39752015-06-04 14:18:43 -0700337 public ConfirmationRequest(@Nullable Prompt prompt, @Nullable Bundle extras) {
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700338 mPrompt = prompt;
339 mExtras = extras;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700340 }
341
James Cook6cf39752015-06-04 14:18:43 -0700342 /**
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.
Barnaby James371a2382015-06-26 22:19:46 -0700347 * @hide
James Cook6cf39752015-06-04 14:18:43 -0700348 */
349 public ConfirmationRequest(CharSequence prompt, Bundle extras) {
350 mPrompt = (prompt != null ? new Prompt(prompt) : null);
351 mExtras = extras;
352 }
353
Barnaby James371a2382015-06-26 22:19:46 -0700354 /**
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 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700360 public void onConfirmationResult(boolean confirmed, Bundle result) {
Dianne Hackborn91097de2014-04-04 18:02:06 -0700361 }
362
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700363 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
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700375 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
376 IVoiceInteractorCallback callback) throws RemoteException {
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700377 return interactor.startConfirmation(packageName, callback, mPrompt, mExtras);
Dianne Hackborn91097de2014-04-04 18:02:06 -0700378 }
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700379 }
380
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700381 /**
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
Barnaby James371a2382015-06-26 22:19:46 -0700386 * either {@link #onPickOptionResult} or {@link #onCancel()} - these methods should
387 * be overridden to define the application specific behavior.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700388 */
389 public static class PickOptionRequest extends Request {
James Cook6cf39752015-06-04 14:18:43 -0700390 final Prompt mPrompt;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700391 final Option[] mOptions;
392 final Bundle mExtras;
393
394 /**
Barnaby James371a2382015-06-26 22:19:46 -0700395 * 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.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700398 */
399 public static final class Option implements Parcelable {
400 final CharSequence mLabel;
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700401 final int mIndex;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700402 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
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700409 * and displayed visually.
Barnaby James371a2382015-06-26 22:19:46 -0700410 * @hide
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700411 */
412 public Option(CharSequence label) {
413 mLabel = label;
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700414 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
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700421 * and displayed visually.
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700422 * @param index The location of this option within the overall set of options.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700423 * Can be used to help identify the option when it is returned from the
424 * voice interactor.
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700425 */
426 public Option(CharSequence label, int index) {
427 mLabel = label;
428 mIndex = index;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700429 }
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,
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700435 * but not displayed.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700436 */
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
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700449 /**
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
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700457 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();
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700485 mIndex = in.readInt();
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700486 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);
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700498 dest.writeInt(mIndex);
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700499 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.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700517 * @param prompt Optional question to be asked of the user when the options are
518 * presented or null if nothing should be asked.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700519 * @param options The set of {@link Option}s the user is selecting from.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700520 * @param extras Additional optional information or null.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700521 */
James Cook6cf39752015-06-04 14:18:43 -0700522 public PickOptionRequest(@Nullable Prompt prompt, Option[] options,
523 @Nullable Bundle extras) {
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700524 mPrompt = prompt;
525 mOptions = options;
526 mExtras = extras;
527 }
528
529 /**
James Cook6cf39752015-06-04 14:18:43 -0700530 * 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.
Barnaby James371a2382015-06-26 22:19:46 -0700535 * @hide
James Cook6cf39752015-06-04 14:18:43 -0700536 */
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 /**
Barnaby James371a2382015-06-26 22:19:46 -0700544 * 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.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700547 * @param finished True if the voice interaction has finished making a selection, in
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700548 * 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.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700550 * @param selections Either a single {@link Option} or one of several {@link Option}s the
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700551 * user has narrowed the choices down to.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700552 * @param result Additional optional information.
553 */
554 public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
555 }
556
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700557 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
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700589 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 */
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700604 public static class CompleteVoiceRequest extends Request {
James Cook6cf39752015-06-04 14:18:43 -0700605 final Prompt mPrompt;
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700606 final Bundle mExtras;
607
608 /**
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700609 * Create a new completed voice interaction request.
James Cook6cf39752015-06-04 14:18:43 -0700610 * @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.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700621 * @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.
Barnaby James371a2382015-06-26 22:19:46 -0700624 * @hide
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700625 */
626 public CompleteVoiceRequest(CharSequence message, Bundle extras) {
James Cook6cf39752015-06-04 14:18:43 -0700627 mPrompt = (message != null ? new Prompt(message) : null);
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700628 mExtras = extras;
629 }
630
631 public void onCompleteResult(Bundle result) {
632 }
633
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700634 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
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700646 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
647 IVoiceInteractorCallback callback) throws RemoteException {
James Cook6cf39752015-06-04 14:18:43 -0700648 return interactor.startCompleteVoice(packageName, callback, mPrompt, mExtras);
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700649 }
650 }
651
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700652 /**
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 */
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700663 public static class AbortVoiceRequest extends Request {
James Cook6cf39752015-06-04 14:18:43 -0700664 final Prompt mPrompt;
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700665 final Bundle mExtras;
666
667 /**
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700668 * Create a new voice abort request.
James Cook6cf39752015-06-04 14:18:43 -0700669 * @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.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700680 * @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.
Barnaby James371a2382015-06-26 22:19:46 -0700683 * @hide
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700684 */
685 public AbortVoiceRequest(CharSequence message, Bundle extras) {
James Cook6cf39752015-06-04 14:18:43 -0700686 mPrompt = (message != null ? new Prompt(message) : null);
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700687 mExtras = extras;
688 }
689
690 public void onAbortResult(Bundle result) {
691 }
692
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700693 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
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700705 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
706 IVoiceInteractorCallback callback) throws RemoteException {
James Cook6cf39752015-06-04 14:18:43 -0700707 return interactor.startAbortVoice(packageName, callback, mPrompt, mExtras);
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700708 }
709 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700710
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700711 /**
Dianne Hackborn2ee5c362015-05-29 17:58:53 -0700712 * Execute a vendor-specific command using the trusted system VoiceInteractionService.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700713 * 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 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700726 public static class CommandRequest extends Request {
727 final String mCommand;
728 final Bundle mArgs;
729
730 /**
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700731 * Create a new generic command request.
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700732 * @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
Johnson Hsiehdd329462015-01-20 17:11:40 -0800740 /**
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) {
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700746 }
747
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700748 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
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700760 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
761 IVoiceInteractorCallback callback) throws RemoteException {
Dianne Hackborn94d93702014-06-11 13:23:18 -0700762 return interactor.startCommand(packageName, callback, mCommand, mArgs);
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700763 }
James Cook6cf39752015-06-04 14:18:43 -0700764 }
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
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700831 @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
James Cook6cf39752015-06-04 14:18:43 -0700855 /** 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 }
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700883
Dianne Hackborn20d94742014-05-29 18:35:45 -0700884 VoiceInteractor(IVoiceInteractor interactor, Context context, Activity activity,
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700885 Looper looper) {
Dianne Hackborn20d94742014-05-29 18:35:45 -0700886 mInteractor = interactor;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700887 mContext = context;
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700888 mActivity = activity;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700889 mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
890 }
891
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700892 Request pullRequest(IVoiceInteractorRequest request, boolean complete) {
Dianne Hackborn91097de2014-04-04 18:02:06 -0700893 synchronized (mActiveRequests) {
894 Request req = mActiveRequests.get(request.asBinder());
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700895 if (req != null && complete) {
896 mActiveRequests.remove(request.asBinder());
Dianne Hackborn91097de2014-04-04 18:02:06 -0700897 }
898 return req;
899 }
900 }
901
Dianne Hackborn20d94742014-05-29 18:35:45 -0700902 private ArrayList<Request> makeRequestList() {
903 final int N = mActiveRequests.size();
904 if (N < 1) {
905 return null;
906 }
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700907 ArrayList<Request> list = new ArrayList<>(N);
Dianne Hackborn20d94742014-05-29 18:35:45 -0700908 for (int i=0; i<N; i++) {
909 list.add(mActiveRequests.valueAt(i));
910 }
911 return list;
912 }
913
914 void attachActivity(Activity activity) {
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700915 mRetaining = false;
Dianne Hackborn20d94742014-05-29 18:35:45 -0700916 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
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700932 void retainInstance() {
933 mRetaining = true;
934 }
935
Dianne Hackborn20d94742014-05-29 18:35:45 -0700936 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 }
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700946 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 }
Dianne Hackborn20d94742014-05-29 18:35:45 -0700956 mContext = null;
957 mActivity = null;
958 }
959
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700960 public boolean submitRequest(Request request) {
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700961 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) {
Dianne Hackborn91097de2014-04-04 18:02:06 -0700976 try {
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700977 if (request.mRequestInterface != null) {
978 throw new IllegalStateException("Given " + request + " is already active");
979 }
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700980 IVoiceInteractorRequest ireq = request.submit(mInteractor,
981 mContext.getOpPackageName(), mCallback);
982 request.mRequestInterface = ireq;
983 request.mContext = mContext;
984 request.mActivity = mActivity;
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700985 request.mName = name;
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700986 synchronized (mActiveRequests) {
987 mActiveRequests.put(ireq.asBinder(), request);
988 }
989 return true;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700990 } catch (RemoteException e) {
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700991 Log.w(TAG, "Remove voice interactor service died", e);
992 return false;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700993 }
994 }
995
996 /**
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700997 * 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 /**
James Cook6cf39752015-06-04 14:18:43 -07001034 * Queries the supported commands available from the VoiceInteractionService.
Dianne Hackborn91097de2014-04-04 18:02:06 -07001035 * The command is a string that describes the generic operation to be performed.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -07001036 * 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.)
Dianne Hackborn91097de2014-04-04 18:02:06 -07001038 *
Barnaby Jamesb47c9e12015-05-24 10:22:47 -07001039 * @param commands The array of commands to query for support.
1040 * @return Array of booleans indicating whether each command is supported or not.
Dianne Hackborn91097de2014-04-04 18:02:06 -07001041 */
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 }
Dianne Hackborn57dd7372015-07-27 18:11:14 -07001051
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 }
Dianne Hackborn91097de2014-04-04 18:02:06 -07001069}