blob: b37120faf2813486c905739f328c8d9c6265f2b6 [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
Sunny Goyald40c3452019-03-20 12:46:55 -070019import android.annotation.CallbackExecutor;
James Cook6cf39752015-06-04 14:18:43 -070020import android.annotation.NonNull;
21import android.annotation.Nullable;
Dianne Hackborn91097de2014-04-04 18:02:06 -070022import android.content.Context;
23import android.os.Bundle;
24import android.os.IBinder;
Sunny Goyald40c3452019-03-20 12:46:55 -070025import android.os.ICancellationSignal;
Dianne Hackborn91097de2014-04-04 18:02:06 -070026import android.os.Looper;
27import android.os.Message;
Dianne Hackborn3d07c942015-03-13 18:02:54 -070028import android.os.Parcel;
29import android.os.Parcelable;
Dianne Hackborn91097de2014-04-04 18:02:06 -070030import android.os.RemoteException;
Dianne Hackborn18f0d352014-04-25 17:06:18 -070031import android.util.ArrayMap;
Dianne Hackborn57dd7372015-07-27 18:11:14 -070032import android.util.DebugUtils;
Dianne Hackborn91097de2014-04-04 18:02:06 -070033import android.util.Log;
Sunny Goyald40c3452019-03-20 12:46:55 -070034
Dianne Hackborn91097de2014-04-04 18:02:06 -070035import com.android.internal.app.IVoiceInteractor;
36import com.android.internal.app.IVoiceInteractorCallback;
37import com.android.internal.app.IVoiceInteractorRequest;
38import com.android.internal.os.HandlerCaller;
39import com.android.internal.os.SomeArgs;
Sunny Goyald40c3452019-03-20 12:46:55 -070040import com.android.internal.util.Preconditions;
41import com.android.internal.util.function.pooled.PooledLambda;
Dianne Hackborn91097de2014-04-04 18:02:06 -070042
Dianne Hackborn57dd7372015-07-27 18:11:14 -070043import java.io.FileDescriptor;
44import java.io.PrintWriter;
Sunny Goyald40c3452019-03-20 12:46:55 -070045import java.lang.ref.WeakReference;
Dianne Hackborn20d94742014-05-29 18:35:45 -070046import java.util.ArrayList;
Sunny Goyald40c3452019-03-20 12:46:55 -070047import java.util.concurrent.Executor;
Dianne Hackborn91097de2014-04-04 18:02:06 -070048
49/**
Dianne Hackborna2c076d2014-05-30 16:42:57 -070050 * Interface for an {@link Activity} to interact with the user through voice. Use
51 * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor}
52 * to retrieve the interface, if the activity is currently involved in a voice interaction.
53 *
54 * <p>The voice interactor revolves around submitting voice interaction requests to the
55 * back-end voice interaction service that is working with the user. These requests are
56 * submitted with {@link #submitRequest}, providing a new instance of a
57 * {@link Request} subclass describing the type of operation to perform -- currently the
58 * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}.
59 *
Fabrice Di Meglioca376022014-05-30 18:20:50 -070060 * <p>Once a request is submitted, the voice system will process it and eventually deliver
Dianne Hackborna2c076d2014-05-30 16:42:57 -070061 * the result to the request object. The application can cancel a pending request at any
62 * time.
63 *
64 * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that
65 * if an activity is being restarted with retained state, it will retain the current
66 * VoiceInteractor and any outstanding requests. Because of this, you should always use
67 * {@link Request#getActivity() Request.getActivity} to get back to the activity of a
Fabrice Di Meglioca376022014-05-30 18:20:50 -070068 * request, rather than holding on to the activity instance yourself, either explicitly
Dianne Hackborna2c076d2014-05-30 16:42:57 -070069 * or implicitly through a non-static inner class.
Dianne Hackborn91097de2014-04-04 18:02:06 -070070 */
Dianne Hackborna3acdb32015-06-08 17:07:40 -070071public final class VoiceInteractor {
Dianne Hackborn91097de2014-04-04 18:02:06 -070072 static final String TAG = "VoiceInteractor";
Dianne Hackborna3acdb32015-06-08 17:07:40 -070073 static final boolean DEBUG = false;
74
75 static final Request[] NO_REQUESTS = new Request[0];
Dianne Hackborn91097de2014-04-04 18:02:06 -070076
Sunny Goyald40c3452019-03-20 12:46:55 -070077 /** @hide */
78 public static final String KEY_CANCELLATION_SIGNAL = "key_cancellation_signal";
79 /** @hide */
80 public static final String KEY_KILL_SIGNAL = "key_kill_signal";
81
Felipe Leme8092cda2019-05-14 10:14:02 -070082 @Nullable IVoiceInteractor mInteractor;
Dianne Hackborn20d94742014-05-29 18:35:45 -070083
Felipe Leme8092cda2019-05-14 10:14:02 -070084 @Nullable Context mContext;
85 @Nullable Activity mActivity;
Dianne Hackborn57dd7372015-07-27 18:11:14 -070086 boolean mRetaining;
Dianne Hackborn20d94742014-05-29 18:35:45 -070087
Dianne Hackborn91097de2014-04-04 18:02:06 -070088 final HandlerCaller mHandlerCaller;
89 final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
90 @Override
91 public void executeMessage(Message msg) {
92 SomeArgs args = (SomeArgs)msg.obj;
Dianne Hackborn18f0d352014-04-25 17:06:18 -070093 Request request;
Dianne Hackborn3d07c942015-03-13 18:02:54 -070094 boolean complete;
Dianne Hackborn91097de2014-04-04 18:02:06 -070095 switch (msg.what) {
96 case MSG_CONFIRMATION_RESULT:
Dianne Hackborn18f0d352014-04-25 17:06:18 -070097 request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
Dianne Hackborn91097de2014-04-04 18:02:06 -070098 if (DEBUG) Log.d(TAG, "onConfirmResult: req="
Dianne Hackborn18f0d352014-04-25 17:06:18 -070099 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
100 + " confirmed=" + msg.arg1 + " result=" + args.arg2);
101 if (request != null) {
102 ((ConfirmationRequest)request).onConfirmationResult(msg.arg1 != 0,
103 (Bundle) args.arg2);
104 request.clear();
105 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700106 break;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700107 case MSG_PICK_OPTION_RESULT:
108 complete = msg.arg1 != 0;
109 request = pullRequest((IVoiceInteractorRequest)args.arg1, complete);
110 if (DEBUG) Log.d(TAG, "onPickOptionResult: req="
111 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
112 + " finished=" + complete + " selection=" + args.arg2
113 + " result=" + args.arg3);
114 if (request != null) {
115 ((PickOptionRequest)request).onPickOptionResult(complete,
116 (PickOptionRequest.Option[]) args.arg2, (Bundle) args.arg3);
117 if (complete) {
118 request.clear();
119 }
120 }
121 break;
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700122 case MSG_COMPLETE_VOICE_RESULT:
123 request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
124 if (DEBUG) Log.d(TAG, "onCompleteVoice: req="
125 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700126 + " result=" + args.arg2);
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700127 if (request != null) {
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700128 ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2);
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700129 request.clear();
130 }
131 break;
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700132 case MSG_ABORT_VOICE_RESULT:
133 request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
134 if (DEBUG) Log.d(TAG, "onAbortVoice: req="
135 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700136 + " result=" + args.arg2);
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700137 if (request != null) {
138 ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2);
139 request.clear();
140 }
141 break;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700142 case MSG_COMMAND_RESULT:
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700143 complete = msg.arg1 != 0;
144 request = pullRequest((IVoiceInteractorRequest)args.arg1, complete);
Dianne Hackborn91097de2014-04-04 18:02:06 -0700145 if (DEBUG) Log.d(TAG, "onCommandResult: req="
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700146 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
Johnson Hsiehdd329462015-01-20 17:11:40 -0800147 + " completed=" + msg.arg1 + " result=" + args.arg2);
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700148 if (request != null) {
Johnson Hsiehdd329462015-01-20 17:11:40 -0800149 ((CommandRequest)request).onCommandResult(msg.arg1 != 0,
150 (Bundle) args.arg2);
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700151 if (complete) {
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700152 request.clear();
153 }
154 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700155 break;
156 case MSG_CANCEL_RESULT:
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700157 request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
Dianne Hackborn91097de2014-04-04 18:02:06 -0700158 if (DEBUG) Log.d(TAG, "onCancelResult: req="
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700159 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request);
160 if (request != null) {
161 request.onCancel();
162 request.clear();
163 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700164 break;
165 }
166 }
167 };
168
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700169 final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() {
170 @Override
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700171 public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean finished,
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700172 Bundle result) {
173 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700174 MSG_CONFIRMATION_RESULT, finished ? 1 : 0, request, result));
175 }
176
177 @Override
178 public void deliverPickOptionResult(IVoiceInteractorRequest request,
179 boolean finished, PickOptionRequest.Option[] options, Bundle result) {
180 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO(
181 MSG_PICK_OPTION_RESULT, finished ? 1 : 0, request, options, result));
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700182 }
183
184 @Override
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700185 public void deliverCompleteVoiceResult(IVoiceInteractorRequest request, Bundle result) {
186 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
187 MSG_COMPLETE_VOICE_RESULT, request, result));
188 }
189
190 @Override
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700191 public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) {
192 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
193 MSG_ABORT_VOICE_RESULT, request, result));
194 }
195
196 @Override
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700197 public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
198 Bundle result) {
199 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
200 MSG_COMMAND_RESULT, complete ? 1 : 0, request, result));
201 }
202
203 @Override
Sunny Goyald40c3452019-03-20 12:46:55 -0700204 public void deliverCancel(IVoiceInteractorRequest request) {
Dianne Hackbornffeecb12015-02-25 11:08:11 -0800205 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
206 MSG_CANCEL_RESULT, request, null));
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700207 }
Sunny Goyald40c3452019-03-20 12:46:55 -0700208
209 @Override
210 public void destroy() {
211 mHandlerCaller.getHandler().sendMessage(PooledLambda.obtainMessage(
212 VoiceInteractor::destroy, VoiceInteractor.this));
213 }
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700214 };
215
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700216 final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<>();
Sunny Goyald40c3452019-03-20 12:46:55 -0700217 final ArrayMap<Runnable, Executor> mOnDestroyCallbacks = new ArrayMap<>();
Dianne Hackborn91097de2014-04-04 18:02:06 -0700218
219 static final int MSG_CONFIRMATION_RESULT = 1;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700220 static final int MSG_PICK_OPTION_RESULT = 2;
221 static final int MSG_COMPLETE_VOICE_RESULT = 3;
222 static final int MSG_ABORT_VOICE_RESULT = 4;
223 static final int MSG_COMMAND_RESULT = 5;
224 static final int MSG_CANCEL_RESULT = 6;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700225
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700226 /**
227 * Base class for voice interaction requests that can be submitted to the interactor.
228 * Do not instantiate this directly -- instead, use the appropriate subclass.
229 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700230 public static abstract class Request {
231 IVoiceInteractorRequest mRequestInterface;
232 Context mContext;
233 Activity mActivity;
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700234 String mName;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700235
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700236 Request() {
Dianne Hackborn91097de2014-04-04 18:02:06 -0700237 }
238
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700239 /**
240 * Return the name this request was submitted through
241 * {@link #submitRequest(android.app.VoiceInteractor.Request, String)}.
242 */
243 public String getName() {
244 return mName;
245 }
246
247 /**
248 * Cancel this active request.
249 */
Dianne Hackborn91097de2014-04-04 18:02:06 -0700250 public void cancel() {
Dianne Hackborn16036f22015-06-22 14:05:51 -0700251 if (mRequestInterface == null) {
252 throw new IllegalStateException("Request " + this + " is no longer active");
253 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700254 try {
255 mRequestInterface.cancel();
256 } catch (RemoteException e) {
257 Log.w(TAG, "Voice interactor has died", e);
258 }
259 }
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700260
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700261 /**
262 * Return the current {@link Context} this request is associated with. May change
263 * if the activity hosting it goes through a configuration change.
264 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700265 public Context getContext() {
266 return mContext;
267 }
268
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700269 /**
270 * Return the current {@link Activity} this request is associated with. Will change
271 * if the activity is restarted such as through a configuration change.
272 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700273 public Activity getActivity() {
274 return mActivity;
275 }
276
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700277 /**
278 * Report from voice interaction service: this operation has been canceled, typically
Barnaby James371a2382015-06-26 22:19:46 -0700279 * as a completion of a previous call to {@link #cancel} or when the user explicitly
280 * cancelled.
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700281 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700282 public void onCancel() {
283 }
284
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700285 /**
286 * The request is now attached to an activity, or being re-attached to a new activity
287 * after a configuration change.
288 */
Dianne Hackborn20d94742014-05-29 18:35:45 -0700289 public void onAttached(Activity activity) {
290 }
291
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700292 /**
293 * The request is being detached from an activity.
294 */
Dianne Hackborn20d94742014-05-29 18:35:45 -0700295 public void onDetached() {
296 }
297
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700298 @Override
299 public String toString() {
300 StringBuilder sb = new StringBuilder(128);
301 DebugUtils.buildShortClassTag(this, sb);
302 sb.append(" ");
303 sb.append(getRequestTypeName());
304 sb.append(" name=");
305 sb.append(mName);
306 sb.append('}');
307 return sb.toString();
308 }
309
310 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
311 writer.print(prefix); writer.print("mRequestInterface=");
312 writer.println(mRequestInterface.asBinder());
313 writer.print(prefix); writer.print("mActivity="); writer.println(mActivity);
314 writer.print(prefix); writer.print("mName="); writer.println(mName);
315 }
316
317 String getRequestTypeName() {
318 return "Request";
319 }
320
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700321 void clear() {
322 mRequestInterface = null;
323 mContext = null;
324 mActivity = null;
Dianne Hackborna3acdb32015-06-08 17:07:40 -0700325 mName = null;
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700326 }
327
328 abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor,
329 String packageName, IVoiceInteractorCallback callback) throws RemoteException;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700330 }
331
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700332 /**
333 * Confirms an operation with the user via the trusted system
334 * VoiceInteractionService. This allows an Activity to complete an unsafe operation that
335 * would require the user to touch the screen when voice interaction mode is not enabled.
336 * The result of the confirmation will be returned through an asynchronous call to
337 * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or
Barnaby James371a2382015-06-26 22:19:46 -0700338 * {@link #onCancel()} - these methods should be overridden to define the application specific
339 * behavior.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700340 *
341 * <p>In some cases this may be a simple yes / no confirmation or the confirmation could
342 * include context information about how the action will be completed
343 * (e.g. booking a cab might include details about how long until the cab arrives)
344 * so the user can give a confirmation.
345 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700346 public static class ConfirmationRequest extends Request {
James Cook6cf39752015-06-04 14:18:43 -0700347 final Prompt mPrompt;
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700348 final Bundle mExtras;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700349
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700350 /**
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700351 * Create a new confirmation request.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700352 * @param prompt Optional confirmation to speak to the user or null if nothing
353 * should be spoken.
354 * @param extras Additional optional information or null.
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700355 */
James Cook6cf39752015-06-04 14:18:43 -0700356 public ConfirmationRequest(@Nullable Prompt prompt, @Nullable Bundle extras) {
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700357 mPrompt = prompt;
358 mExtras = extras;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700359 }
360
James Cook6cf39752015-06-04 14:18:43 -0700361 /**
362 * Create a new confirmation request.
363 * @param prompt Optional confirmation to speak to the user or null if nothing
364 * should be spoken.
365 * @param extras Additional optional information or null.
Barnaby James371a2382015-06-26 22:19:46 -0700366 * @hide
James Cook6cf39752015-06-04 14:18:43 -0700367 */
368 public ConfirmationRequest(CharSequence prompt, Bundle extras) {
369 mPrompt = (prompt != null ? new Prompt(prompt) : null);
370 mExtras = extras;
371 }
372
Barnaby James371a2382015-06-26 22:19:46 -0700373 /**
374 * Handle the confirmation result. Override this method to define
375 * the behavior when the user confirms or rejects the operation.
376 * @param confirmed Whether the user confirmed or rejected the operation.
377 * @param result Additional result information or null.
378 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700379 public void onConfirmationResult(boolean confirmed, Bundle result) {
Dianne Hackborn91097de2014-04-04 18:02:06 -0700380 }
381
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700382 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
383 super.dump(prefix, fd, writer, args);
384 writer.print(prefix); writer.print("mPrompt="); writer.println(mPrompt);
385 if (mExtras != null) {
386 writer.print(prefix); writer.print("mExtras="); writer.println(mExtras);
387 }
388 }
389
390 String getRequestTypeName() {
391 return "Confirmation";
392 }
393
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700394 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
395 IVoiceInteractorCallback callback) throws RemoteException {
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700396 return interactor.startConfirmation(packageName, callback, mPrompt, mExtras);
Dianne Hackborn91097de2014-04-04 18:02:06 -0700397 }
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700398 }
399
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700400 /**
401 * Select a single option from multiple potential options with the user via the trusted system
402 * VoiceInteractionService. Typically, the application would present this visually as
403 * a list view to allow selecting the option by touch.
404 * The result of the confirmation will be returned through an asynchronous call to
Barnaby James371a2382015-06-26 22:19:46 -0700405 * either {@link #onPickOptionResult} or {@link #onCancel()} - these methods should
406 * be overridden to define the application specific behavior.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700407 */
408 public static class PickOptionRequest extends Request {
James Cook6cf39752015-06-04 14:18:43 -0700409 final Prompt mPrompt;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700410 final Option[] mOptions;
411 final Bundle mExtras;
412
413 /**
Barnaby James371a2382015-06-26 22:19:46 -0700414 * Represents a single option that the user may select using their voice. The
415 * {@link #getIndex()} method should be used as a unique ID to identify the option
416 * when it is returned from the voice interactor.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700417 */
418 public static final class Option implements Parcelable {
419 final CharSequence mLabel;
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700420 final int mIndex;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700421 ArrayList<CharSequence> mSynonyms;
422 Bundle mExtras;
423
424 /**
425 * Creates an option that a user can select with their voice by matching the label
426 * or one of several synonyms.
427 * @param label The label that will both be matched against what the user speaks
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700428 * and displayed visually.
Barnaby James371a2382015-06-26 22:19:46 -0700429 * @hide
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700430 */
431 public Option(CharSequence label) {
432 mLabel = label;
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700433 mIndex = -1;
434 }
435
436 /**
437 * Creates an option that a user can select with their voice by matching the label
438 * or one of several synonyms.
439 * @param label The label that will both be matched against what the user speaks
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700440 * and displayed visually.
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700441 * @param index The location of this option within the overall set of options.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700442 * Can be used to help identify the option when it is returned from the
443 * voice interactor.
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700444 */
445 public Option(CharSequence label, int index) {
446 mLabel = label;
447 mIndex = index;
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700448 }
449
450 /**
451 * Add a synonym term to the option to indicate an alternative way the content
452 * may be matched.
453 * @param synonym The synonym that will be matched against what the user speaks,
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700454 * but not displayed.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700455 */
456 public Option addSynonym(CharSequence synonym) {
457 if (mSynonyms == null) {
458 mSynonyms = new ArrayList<>();
459 }
460 mSynonyms.add(synonym);
461 return this;
462 }
463
464 public CharSequence getLabel() {
465 return mLabel;
466 }
467
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700468 /**
469 * Return the index that was supplied in the constructor.
470 * If the option was constructed without an index, -1 is returned.
471 */
472 public int getIndex() {
473 return mIndex;
474 }
475
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700476 public int countSynonyms() {
477 return mSynonyms != null ? mSynonyms.size() : 0;
478 }
479
480 public CharSequence getSynonymAt(int index) {
481 return mSynonyms != null ? mSynonyms.get(index) : null;
482 }
483
484 /**
485 * Set optional extra information associated with this option. Note that this
486 * method takes ownership of the supplied extras Bundle.
487 */
488 public void setExtras(Bundle extras) {
489 mExtras = extras;
490 }
491
492 /**
493 * Return any optional extras information associated with this option, or null
494 * if there is none. Note that this method returns a reference to the actual
495 * extras Bundle in the option, so modifications to it will directly modify the
496 * extras in the option.
497 */
498 public Bundle getExtras() {
499 return mExtras;
500 }
501
502 Option(Parcel in) {
503 mLabel = in.readCharSequence();
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700504 mIndex = in.readInt();
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700505 mSynonyms = in.readCharSequenceList();
506 mExtras = in.readBundle();
507 }
508
509 @Override
510 public int describeContents() {
511 return 0;
512 }
513
514 @Override
515 public void writeToParcel(Parcel dest, int flags) {
516 dest.writeCharSequence(mLabel);
Dianne Hackbornd59a5d52015-04-04 14:52:14 -0700517 dest.writeInt(mIndex);
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700518 dest.writeCharSequenceList(mSynonyms);
519 dest.writeBundle(mExtras);
520 }
521
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700522 public static final @android.annotation.NonNull Parcelable.Creator<Option> CREATOR
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700523 = new Parcelable.Creator<Option>() {
524 public Option createFromParcel(Parcel in) {
525 return new Option(in);
526 }
527
528 public Option[] newArray(int size) {
529 return new Option[size];
530 }
531 };
532 };
533
534 /**
535 * Create a new pick option request.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700536 * @param prompt Optional question to be asked of the user when the options are
537 * presented or null if nothing should be asked.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700538 * @param options The set of {@link Option}s the user is selecting from.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700539 * @param extras Additional optional information or null.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700540 */
James Cook6cf39752015-06-04 14:18:43 -0700541 public PickOptionRequest(@Nullable Prompt prompt, Option[] options,
542 @Nullable Bundle extras) {
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700543 mPrompt = prompt;
544 mOptions = options;
545 mExtras = extras;
546 }
547
548 /**
James Cook6cf39752015-06-04 14:18:43 -0700549 * Create a new pick option request.
550 * @param prompt Optional question to be asked of the user when the options are
551 * presented or null if nothing should be asked.
552 * @param options The set of {@link Option}s the user is selecting from.
553 * @param extras Additional optional information or null.
Barnaby James371a2382015-06-26 22:19:46 -0700554 * @hide
James Cook6cf39752015-06-04 14:18:43 -0700555 */
556 public PickOptionRequest(CharSequence prompt, Option[] options, Bundle extras) {
557 mPrompt = (prompt != null ? new Prompt(prompt) : null);
558 mOptions = options;
559 mExtras = extras;
560 }
561
562 /**
Barnaby James371a2382015-06-26 22:19:46 -0700563 * Called when a single option is confirmed or narrowed to one of several options. Override
564 * this method to define the behavior when the user selects an option or narrows down the
565 * set of options.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700566 * @param finished True if the voice interaction has finished making a selection, in
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700567 * which case {@code selections} contains the final result. If false, this request is
568 * still active and you will continue to get calls on it.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700569 * @param selections Either a single {@link Option} or one of several {@link Option}s the
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700570 * user has narrowed the choices down to.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700571 * @param result Additional optional information.
572 */
573 public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
574 }
575
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700576 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
577 super.dump(prefix, fd, writer, args);
578 writer.print(prefix); writer.print("mPrompt="); writer.println(mPrompt);
579 if (mOptions != null) {
580 writer.print(prefix); writer.println("Options:");
581 for (int i=0; i<mOptions.length; i++) {
582 Option op = mOptions[i];
583 writer.print(prefix); writer.print(" #"); writer.print(i); writer.println(":");
584 writer.print(prefix); writer.print(" mLabel="); writer.println(op.mLabel);
585 writer.print(prefix); writer.print(" mIndex="); writer.println(op.mIndex);
586 if (op.mSynonyms != null && op.mSynonyms.size() > 0) {
587 writer.print(prefix); writer.println(" Synonyms:");
588 for (int j=0; j<op.mSynonyms.size(); j++) {
589 writer.print(prefix); writer.print(" #"); writer.print(j);
590 writer.print(": "); writer.println(op.mSynonyms.get(j));
591 }
592 }
593 if (op.mExtras != null) {
594 writer.print(prefix); writer.print(" mExtras=");
595 writer.println(op.mExtras);
596 }
597 }
598 }
599 if (mExtras != null) {
600 writer.print(prefix); writer.print("mExtras="); writer.println(mExtras);
601 }
602 }
603
604 String getRequestTypeName() {
605 return "PickOption";
606 }
607
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700608 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
609 IVoiceInteractorCallback callback) throws RemoteException {
610 return interactor.startPickOption(packageName, callback, mPrompt, mOptions, mExtras);
611 }
612 }
613
614 /**
615 * Reports that the current interaction was successfully completed with voice, so the
616 * application can report the final status to the user. When the response comes back, the
617 * voice system has handled the request and is ready to switch; at that point the
618 * application can start a new non-voice activity or finish. Be sure when starting the new
619 * activity to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
620 * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
621 * interaction task.
622 */
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700623 public static class CompleteVoiceRequest extends Request {
James Cook6cf39752015-06-04 14:18:43 -0700624 final Prompt mPrompt;
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700625 final Bundle mExtras;
626
627 /**
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700628 * Create a new completed voice interaction request.
James Cook6cf39752015-06-04 14:18:43 -0700629 * @param prompt Optional message to speak to the user about the completion status of
630 * the task or null if nothing should be spoken.
631 * @param extras Additional optional information or null.
632 */
633 public CompleteVoiceRequest(@Nullable Prompt prompt, @Nullable Bundle extras) {
634 mPrompt = prompt;
635 mExtras = extras;
636 }
637
638 /**
639 * Create a new completed voice interaction request.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700640 * @param message Optional message to speak to the user about the completion status of
641 * the task or null if nothing should be spoken.
642 * @param extras Additional optional information or null.
Barnaby James371a2382015-06-26 22:19:46 -0700643 * @hide
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700644 */
645 public CompleteVoiceRequest(CharSequence message, Bundle extras) {
James Cook6cf39752015-06-04 14:18:43 -0700646 mPrompt = (message != null ? new Prompt(message) : null);
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700647 mExtras = extras;
648 }
649
650 public void onCompleteResult(Bundle result) {
651 }
652
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700653 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
654 super.dump(prefix, fd, writer, args);
655 writer.print(prefix); writer.print("mPrompt="); writer.println(mPrompt);
656 if (mExtras != null) {
657 writer.print(prefix); writer.print("mExtras="); writer.println(mExtras);
658 }
659 }
660
661 String getRequestTypeName() {
662 return "CompleteVoice";
663 }
664
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700665 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
666 IVoiceInteractorCallback callback) throws RemoteException {
James Cook6cf39752015-06-04 14:18:43 -0700667 return interactor.startCompleteVoice(packageName, callback, mPrompt, mExtras);
Barnaby Jamesd3fdb8b2014-07-07 10:51:06 -0700668 }
669 }
670
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700671 /**
672 * Reports that the current interaction can not be complete with voice, so the
673 * application will need to switch to a traditional input UI. Applications should
674 * only use this when they need to completely bail out of the voice interaction
675 * and switch to a traditional UI. When the response comes back, the voice
676 * system has handled the request and is ready to switch; at that point the application
677 * can start a new non-voice activity. Be sure when starting the new activity
678 * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
679 * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
680 * interaction task.
681 */
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700682 public static class AbortVoiceRequest extends Request {
James Cook6cf39752015-06-04 14:18:43 -0700683 final Prompt mPrompt;
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700684 final Bundle mExtras;
685
686 /**
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700687 * Create a new voice abort request.
James Cook6cf39752015-06-04 14:18:43 -0700688 * @param prompt Optional message to speak to the user indicating why the task could
689 * not be completed by voice or null if nothing should be spoken.
690 * @param extras Additional optional information or null.
691 */
692 public AbortVoiceRequest(@Nullable Prompt prompt, @Nullable Bundle extras) {
693 mPrompt = prompt;
694 mExtras = extras;
695 }
696
697 /**
698 * Create a new voice abort request.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -0700699 * @param message Optional message to speak to the user indicating why the task could
700 * not be completed by voice or null if nothing should be spoken.
701 * @param extras Additional optional information or null.
Barnaby James371a2382015-06-26 22:19:46 -0700702 * @hide
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700703 */
704 public AbortVoiceRequest(CharSequence message, Bundle extras) {
James Cook6cf39752015-06-04 14:18:43 -0700705 mPrompt = (message != null ? new Prompt(message) : null);
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700706 mExtras = extras;
707 }
708
709 public void onAbortResult(Bundle result) {
710 }
711
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700712 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
713 super.dump(prefix, fd, writer, args);
714 writer.print(prefix); writer.print("mPrompt="); writer.println(mPrompt);
715 if (mExtras != null) {
716 writer.print(prefix); writer.print("mExtras="); writer.println(mExtras);
717 }
718 }
719
720 String getRequestTypeName() {
721 return "AbortVoice";
722 }
723
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700724 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
725 IVoiceInteractorCallback callback) throws RemoteException {
James Cook6cf39752015-06-04 14:18:43 -0700726 return interactor.startAbortVoice(packageName, callback, mPrompt, mExtras);
Dianne Hackborna2c076d2014-05-30 16:42:57 -0700727 }
728 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700729
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700730 /**
Dianne Hackborn2ee5c362015-05-29 17:58:53 -0700731 * Execute a vendor-specific command using the trusted system VoiceInteractionService.
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700732 * This allows an Activity to request additional information from the user needed to
733 * complete an action (e.g. booking a table might have several possible times that the
734 * user could select from or an app might need the user to agree to a terms of service).
735 * The result of the confirmation will be returned through an asynchronous call to
736 * either {@link #onCommandResult(boolean, android.os.Bundle)} or
737 * {@link #onCancel()}.
738 *
739 * <p>The command is a string that describes the generic operation to be performed.
740 * The command will determine how the properties in extras are interpreted and the set of
741 * available commands is expected to grow over time. An example might be
742 * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of
743 * airline check-in. (This is not an actual working example.)
744 */
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700745 public static class CommandRequest extends Request {
746 final String mCommand;
747 final Bundle mArgs;
748
749 /**
Dianne Hackborn3d07c942015-03-13 18:02:54 -0700750 * Create a new generic command request.
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700751 * @param command The desired command to perform.
752 * @param args Additional arguments to control execution of the command.
753 */
754 public CommandRequest(String command, Bundle args) {
755 mCommand = command;
756 mArgs = args;
757 }
758
Johnson Hsiehdd329462015-01-20 17:11:40 -0800759 /**
760 * Results for CommandRequest can be returned in partial chunks.
761 * The isCompleted is set to true iff all results have been returned, indicating the
762 * CommandRequest has completed.
763 */
764 public void onCommandResult(boolean isCompleted, Bundle result) {
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700765 }
766
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700767 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
768 super.dump(prefix, fd, writer, args);
769 writer.print(prefix); writer.print("mCommand="); writer.println(mCommand);
770 if (mArgs != null) {
771 writer.print(prefix); writer.print("mArgs="); writer.println(mArgs);
772 }
773 }
774
775 String getRequestTypeName() {
776 return "Command";
777 }
778
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700779 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
780 IVoiceInteractorCallback callback) throws RemoteException {
Dianne Hackborn94d93702014-06-11 13:23:18 -0700781 return interactor.startCommand(packageName, callback, mCommand, mArgs);
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700782 }
James Cook6cf39752015-06-04 14:18:43 -0700783 }
784
785 /**
786 * A set of voice prompts to use with the voice interaction system to confirm an action, select
787 * an option, or do similar operations. Multiple voice prompts may be provided for variety. A
788 * visual prompt must be provided, which might not match the spoken version. For example, the
789 * confirmation "Are you sure you want to purchase this item?" might use a visual label like
790 * "Purchase item".
791 */
792 public static class Prompt implements Parcelable {
793 // Mandatory voice prompt. Must contain at least one item, which must not be null.
794 private final CharSequence[] mVoicePrompts;
795
796 // Mandatory visual prompt.
797 private final CharSequence mVisualPrompt;
798
799 /**
800 * Constructs a prompt set.
801 * @param voicePrompts An array of one or more voice prompts. Must not be empty or null.
802 * @param visualPrompt A prompt to display on the screen. Must not be null.
803 */
804 public Prompt(@NonNull CharSequence[] voicePrompts, @NonNull CharSequence visualPrompt) {
805 if (voicePrompts == null) {
806 throw new NullPointerException("voicePrompts must not be null");
807 }
808 if (voicePrompts.length == 0) {
809 throw new IllegalArgumentException("voicePrompts must not be empty");
810 }
811 if (visualPrompt == null) {
812 throw new NullPointerException("visualPrompt must not be null");
813 }
814 this.mVoicePrompts = voicePrompts;
815 this.mVisualPrompt = visualPrompt;
816 }
817
818 /**
819 * Constructs a prompt set with single prompt used for all interactions. This is most useful
820 * in test apps. Non-trivial apps should prefer the detailed constructor.
821 */
822 public Prompt(@NonNull CharSequence prompt) {
823 this.mVoicePrompts = new CharSequence[] { prompt };
824 this.mVisualPrompt = prompt;
825 }
826
827 /**
828 * Returns a prompt to use for voice interactions.
829 */
830 @NonNull
831 public CharSequence getVoicePromptAt(int index) {
832 return mVoicePrompts[index];
833 }
834
835 /**
836 * Returns the number of different voice prompts.
837 */
838 public int countVoicePrompts() {
839 return mVoicePrompts.length;
840 }
841
842 /**
843 * Returns the prompt to use for visual display.
844 */
845 @NonNull
846 public CharSequence getVisualPrompt() {
847 return mVisualPrompt;
848 }
849
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700850 @Override
851 public String toString() {
852 StringBuilder sb = new StringBuilder(128);
853 DebugUtils.buildShortClassTag(this, sb);
854 if (mVisualPrompt != null && mVoicePrompts != null && mVoicePrompts.length == 1
855 && mVisualPrompt.equals(mVoicePrompts[0])) {
856 sb.append(" ");
857 sb.append(mVisualPrompt);
858 } else {
859 if (mVisualPrompt != null) {
860 sb.append(" visual="); sb.append(mVisualPrompt);
861 }
862 if (mVoicePrompts != null) {
863 sb.append(", voice=");
864 for (int i=0; i<mVoicePrompts.length; i++) {
865 if (i > 0) sb.append(" | ");
866 sb.append(mVoicePrompts[i]);
867 }
868 }
869 }
870 sb.append('}');
871 return sb.toString();
872 }
873
James Cook6cf39752015-06-04 14:18:43 -0700874 /** Constructor to support Parcelable behavior. */
875 Prompt(Parcel in) {
876 mVoicePrompts = in.readCharSequenceArray();
877 mVisualPrompt = in.readCharSequence();
878 }
879
880 @Override
881 public int describeContents() {
882 return 0;
883 }
884
885 @Override
886 public void writeToParcel(Parcel dest, int flags) {
887 dest.writeCharSequenceArray(mVoicePrompts);
888 dest.writeCharSequence(mVisualPrompt);
889 }
890
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700891 public static final @android.annotation.NonNull Creator<Prompt> CREATOR
James Cook6cf39752015-06-04 14:18:43 -0700892 = new Creator<Prompt>() {
893 public Prompt createFromParcel(Parcel in) {
894 return new Prompt(in);
895 }
896
897 public Prompt[] newArray(int size) {
898 return new Prompt[size];
899 }
900 };
901 }
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700902
Dianne Hackborn20d94742014-05-29 18:35:45 -0700903 VoiceInteractor(IVoiceInteractor interactor, Context context, Activity activity,
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700904 Looper looper) {
Dianne Hackborn20d94742014-05-29 18:35:45 -0700905 mInteractor = interactor;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700906 mContext = context;
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700907 mActivity = activity;
Dianne Hackborn91097de2014-04-04 18:02:06 -0700908 mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
Sunny Goyald40c3452019-03-20 12:46:55 -0700909 try {
910 mInteractor.setKillCallback(new KillCallback(this));
911 } catch (RemoteException e) {
912 /* ignore */
913 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700914 }
915
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700916 Request pullRequest(IVoiceInteractorRequest request, boolean complete) {
Dianne Hackborn91097de2014-04-04 18:02:06 -0700917 synchronized (mActiveRequests) {
918 Request req = mActiveRequests.get(request.asBinder());
Dianne Hackborn18f0d352014-04-25 17:06:18 -0700919 if (req != null && complete) {
920 mActiveRequests.remove(request.asBinder());
Dianne Hackborn91097de2014-04-04 18:02:06 -0700921 }
922 return req;
923 }
924 }
925
Dianne Hackborn20d94742014-05-29 18:35:45 -0700926 private ArrayList<Request> makeRequestList() {
927 final int N = mActiveRequests.size();
928 if (N < 1) {
929 return null;
930 }
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700931 ArrayList<Request> list = new ArrayList<>(N);
Dianne Hackborn20d94742014-05-29 18:35:45 -0700932 for (int i=0; i<N; i++) {
933 list.add(mActiveRequests.valueAt(i));
934 }
935 return list;
936 }
937
938 void attachActivity(Activity activity) {
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700939 mRetaining = false;
Dianne Hackborn20d94742014-05-29 18:35:45 -0700940 if (mActivity == activity) {
941 return;
942 }
943 mContext = activity;
944 mActivity = activity;
945 ArrayList<Request> reqs = makeRequestList();
946 if (reqs != null) {
947 for (int i=0; i<reqs.size(); i++) {
948 Request req = reqs.get(i);
949 req.mContext = activity;
950 req.mActivity = activity;
951 req.onAttached(activity);
952 }
953 }
954 }
955
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700956 void retainInstance() {
957 mRetaining = true;
958 }
959
Dianne Hackborn20d94742014-05-29 18:35:45 -0700960 void detachActivity() {
961 ArrayList<Request> reqs = makeRequestList();
962 if (reqs != null) {
963 for (int i=0; i<reqs.size(); i++) {
964 Request req = reqs.get(i);
965 req.onDetached();
966 req.mActivity = null;
967 req.mContext = null;
968 }
969 }
Dianne Hackborn57dd7372015-07-27 18:11:14 -0700970 if (!mRetaining) {
971 reqs = makeRequestList();
972 if (reqs != null) {
973 for (int i=0; i<reqs.size(); i++) {
974 Request req = reqs.get(i);
975 req.cancel();
976 }
977 }
978 mActiveRequests.clear();
979 }
Dianne Hackborn20d94742014-05-29 18:35:45 -0700980 mContext = null;
981 mActivity = null;
982 }
983
Sunny Goyald40c3452019-03-20 12:46:55 -0700984 void destroy() {
985 final int requestCount = mActiveRequests.size();
986 for (int i = requestCount - 1; i >= 0; i--) {
987 final Request request = mActiveRequests.valueAt(i);
988 mActiveRequests.removeAt(i);
989 request.cancel();
990 }
991
992 final int callbackCount = mOnDestroyCallbacks.size();
993 for (int i = callbackCount - 1; i >= 0; i--) {
994 final Runnable callback = mOnDestroyCallbacks.keyAt(i);
995 final Executor executor = mOnDestroyCallbacks.valueAt(i);
996 executor.execute(callback);
997 mOnDestroyCallbacks.removeAt(i);
998 }
999
1000 // destroyed now
1001 mInteractor = null;
Felipe Leme8092cda2019-05-14 10:14:02 -07001002 if (mActivity != null) {
1003 mActivity.setVoiceInteractor(null);
1004 }
Sunny Goyald40c3452019-03-20 12:46:55 -07001005 }
1006
Dianne Hackborn18f0d352014-04-25 17:06:18 -07001007 public boolean submitRequest(Request request) {
Dianne Hackborna3acdb32015-06-08 17:07:40 -07001008 return submitRequest(request, null);
1009 }
1010
1011 /**
1012 * Submit a new {@link Request} to the voice interaction service. The request must be
1013 * one of the available subclasses -- {@link ConfirmationRequest}, {@link PickOptionRequest},
1014 * {@link CompleteVoiceRequest}, {@link AbortVoiceRequest}, or {@link CommandRequest}.
1015 *
1016 * @param request The desired request to submit.
1017 * @param name An optional name for this request, or null. This can be used later with
1018 * {@link #getActiveRequests} and {@link #getActiveRequest} to find the request.
1019 *
1020 * @return Returns true of the request was successfully submitted, else false.
1021 */
1022 public boolean submitRequest(Request request, String name) {
Sunny Goyald40c3452019-03-20 12:46:55 -07001023 if (isDestroyed()) {
1024 Log.w(TAG, "Cannot interact with a destroyed voice interactor");
1025 return false;
1026 }
Dianne Hackborn91097de2014-04-04 18:02:06 -07001027 try {
Dianne Hackborna3acdb32015-06-08 17:07:40 -07001028 if (request.mRequestInterface != null) {
1029 throw new IllegalStateException("Given " + request + " is already active");
1030 }
Dianne Hackborn18f0d352014-04-25 17:06:18 -07001031 IVoiceInteractorRequest ireq = request.submit(mInteractor,
1032 mContext.getOpPackageName(), mCallback);
1033 request.mRequestInterface = ireq;
1034 request.mContext = mContext;
1035 request.mActivity = mActivity;
Dianne Hackborna3acdb32015-06-08 17:07:40 -07001036 request.mName = name;
Dianne Hackborn18f0d352014-04-25 17:06:18 -07001037 synchronized (mActiveRequests) {
1038 mActiveRequests.put(ireq.asBinder(), request);
1039 }
1040 return true;
Dianne Hackborn91097de2014-04-04 18:02:06 -07001041 } catch (RemoteException e) {
Dianne Hackborn18f0d352014-04-25 17:06:18 -07001042 Log.w(TAG, "Remove voice interactor service died", e);
1043 return false;
Dianne Hackborn91097de2014-04-04 18:02:06 -07001044 }
1045 }
1046
1047 /**
Dianne Hackborna3acdb32015-06-08 17:07:40 -07001048 * Return all currently active requests.
1049 */
1050 public Request[] getActiveRequests() {
Sunny Goyald40c3452019-03-20 12:46:55 -07001051 if (isDestroyed()) {
1052 Log.w(TAG, "Cannot interact with a destroyed voice interactor");
1053 return null;
1054 }
Dianne Hackborna3acdb32015-06-08 17:07:40 -07001055 synchronized (mActiveRequests) {
1056 final int N = mActiveRequests.size();
1057 if (N <= 0) {
1058 return NO_REQUESTS;
1059 }
1060 Request[] requests = new Request[N];
1061 for (int i=0; i<N; i++) {
1062 requests[i] = mActiveRequests.valueAt(i);
1063 }
1064 return requests;
1065 }
1066 }
1067
1068 /**
1069 * Return any currently active request that was submitted with the given name.
1070 *
1071 * @param name The name used to submit the request, as per
1072 * {@link #submitRequest(android.app.VoiceInteractor.Request, String)}.
1073 * @return Returns the active request with that name, or null if there was none.
1074 */
1075 public Request getActiveRequest(String name) {
Sunny Goyald40c3452019-03-20 12:46:55 -07001076 if (isDestroyed()) {
1077 Log.w(TAG, "Cannot interact with a destroyed voice interactor");
1078 return null;
1079 }
Dianne Hackborna3acdb32015-06-08 17:07:40 -07001080 synchronized (mActiveRequests) {
1081 final int N = mActiveRequests.size();
1082 for (int i=0; i<N; i++) {
1083 Request req = mActiveRequests.valueAt(i);
1084 if (name == req.getName() || (name != null && name.equals(req.getName()))) {
1085 return req;
1086 }
1087 }
1088 }
1089 return null;
1090 }
1091
1092 /**
James Cook6cf39752015-06-04 14:18:43 -07001093 * Queries the supported commands available from the VoiceInteractionService.
Dianne Hackborn91097de2014-04-04 18:02:06 -07001094 * The command is a string that describes the generic operation to be performed.
Barnaby Jamesb47c9e12015-05-24 10:22:47 -07001095 * An example might be "org.example.commands.PICK_DATE" to ask the user to pick
1096 * a date. (Note: This is not an actual working example.)
Dianne Hackborn91097de2014-04-04 18:02:06 -07001097 *
Barnaby Jamesb47c9e12015-05-24 10:22:47 -07001098 * @param commands The array of commands to query for support.
1099 * @return Array of booleans indicating whether each command is supported or not.
Dianne Hackborn91097de2014-04-04 18:02:06 -07001100 */
1101 public boolean[] supportsCommands(String[] commands) {
Sunny Goyald40c3452019-03-20 12:46:55 -07001102 if (isDestroyed()) {
1103 Log.w(TAG, "Cannot interact with a destroyed voice interactor");
1104 return new boolean[commands.length];
1105 }
Dianne Hackborn91097de2014-04-04 18:02:06 -07001106 try {
1107 boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands);
1108 if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res);
1109 return res;
1110 } catch (RemoteException e) {
1111 throw new RuntimeException("Voice interactor has died", e);
1112 }
1113 }
Dianne Hackborn57dd7372015-07-27 18:11:14 -07001114
Sunny Goyald40c3452019-03-20 12:46:55 -07001115 /**
1116 * @return whether the voice interactor is destroyed. You should not interact
1117 * with a destroyed voice interactor.
1118 */
1119 public boolean isDestroyed() {
1120 return mInteractor == null;
1121 }
1122
1123 /**
1124 * Registers a callback to be called when the VoiceInteractor is destroyed.
1125 *
1126 * @param executor Executor on which to run the callback.
1127 * @param callback The callback to run.
1128 * @return whether the callback was registered.
1129 */
1130 public boolean registerOnDestroyedCallback(@NonNull @CallbackExecutor Executor executor,
1131 @NonNull Runnable callback) {
1132 Preconditions.checkNotNull(executor);
1133 Preconditions.checkNotNull(callback);
1134 if (isDestroyed()) {
1135 Log.w(TAG, "Cannot interact with a destroyed voice interactor");
1136 return false;
1137 }
1138 mOnDestroyCallbacks.put(callback, executor);
1139 return true;
1140 }
1141
1142 /**
1143 * Unregisters a previously registered onDestroy callback
1144 *
1145 * @param callback The callback to remove.
1146 * @return whether the callback was unregistered.
1147 */
1148 public boolean unregisterOnDestroyedCallback(@NonNull Runnable callback) {
1149 Preconditions.checkNotNull(callback);
1150 if (isDestroyed()) {
1151 Log.w(TAG, "Cannot interact with a destroyed voice interactor");
1152 return false;
1153 }
1154 return mOnDestroyCallbacks.remove(callback) != null;
1155 }
1156
1157 /**
1158 * Notifies the assist framework that the direct actions supported by the app changed.
1159 */
1160 public void notifyDirectActionsChanged() {
1161 if (isDestroyed()) {
1162 Log.w(TAG, "Cannot interact with a destroyed voice interactor");
1163 return;
1164 }
1165 try {
1166 mInteractor.notifyDirectActionsChanged(mActivity.getTaskId(),
1167 mActivity.getAssistToken());
1168 } catch (RemoteException e) {
1169 Log.w(TAG, "Voice interactor has died", e);
1170 }
1171 }
1172
Dianne Hackborn57dd7372015-07-27 18:11:14 -07001173 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
1174 String innerPrefix = prefix + " ";
1175 if (mActiveRequests.size() > 0) {
1176 writer.print(prefix); writer.println("Active voice requests:");
1177 for (int i=0; i<mActiveRequests.size(); i++) {
1178 Request req = mActiveRequests.valueAt(i);
1179 writer.print(prefix); writer.print(" #"); writer.print(i);
1180 writer.print(": ");
1181 writer.println(req);
1182 req.dump(innerPrefix, fd, writer, args);
1183 }
1184 }
1185 writer.print(prefix); writer.println("VoiceInteractor misc state:");
1186 writer.print(prefix); writer.print(" mInteractor=");
1187 writer.println(mInteractor.asBinder());
1188 writer.print(prefix); writer.print(" mActivity="); writer.println(mActivity);
1189 }
Sunny Goyald40c3452019-03-20 12:46:55 -07001190
1191 private static final class KillCallback extends ICancellationSignal.Stub {
1192 private final WeakReference<VoiceInteractor> mInteractor;
1193
1194 KillCallback(VoiceInteractor interactor) {
1195 mInteractor= new WeakReference<>(interactor);
1196 }
1197
1198 @Override
1199 public void cancel() {
1200 final VoiceInteractor voiceInteractor = mInteractor.get();
1201 if (voiceInteractor != null) {
1202 voiceInteractor.mHandlerCaller.getHandler().sendMessage(PooledLambda
1203 .obtainMessage(VoiceInteractor::destroy, voiceInteractor));
1204 }
1205 }
1206 }
Dianne Hackborn91097de2014-04-04 18:02:06 -07001207}