1 /** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.service.voice; 18 19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.IntDef; 23 import android.annotation.IntRange; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.Activity; 27 import android.app.Dialog; 28 import android.app.DirectAction; 29 import android.app.Instrumentation; 30 import android.app.VoiceInteractor; 31 import android.app.assist.AssistContent; 32 import android.app.assist.AssistStructure; 33 import android.content.ComponentCallbacks2; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.pm.ParceledListSlice; 37 import android.content.res.Configuration; 38 import android.content.res.TypedArray; 39 import android.graphics.Bitmap; 40 import android.graphics.Rect; 41 import android.graphics.Region; 42 import android.os.Binder; 43 import android.os.Bundle; 44 import android.os.CancellationSignal; 45 import android.os.Handler; 46 import android.os.IBinder; 47 import android.os.ICancellationSignal; 48 import android.os.Message; 49 import android.os.RemoteCallback; 50 import android.os.RemoteException; 51 import android.os.UserHandle; 52 import android.util.ArrayMap; 53 import android.util.DebugUtils; 54 import android.util.Log; 55 import android.view.Gravity; 56 import android.view.KeyEvent; 57 import android.view.LayoutInflater; 58 import android.view.View; 59 import android.view.ViewTreeObserver; 60 import android.view.WindowManager; 61 import android.widget.FrameLayout; 62 63 import com.android.internal.annotations.Immutable; 64 import com.android.internal.app.IVoiceInteractionManagerService; 65 import com.android.internal.app.IVoiceInteractionSessionShowCallback; 66 import com.android.internal.app.IVoiceInteractor; 67 import com.android.internal.app.IVoiceInteractorCallback; 68 import com.android.internal.app.IVoiceInteractorRequest; 69 import com.android.internal.os.HandlerCaller; 70 import com.android.internal.os.SomeArgs; 71 import com.android.internal.util.function.pooled.PooledLambda; 72 73 import java.io.FileDescriptor; 74 import java.io.PrintWriter; 75 import java.lang.annotation.Retention; 76 import java.lang.annotation.RetentionPolicy; 77 import java.lang.ref.WeakReference; 78 import java.util.ArrayList; 79 import java.util.Collections; 80 import java.util.List; 81 import java.util.Map; 82 import java.util.Objects; 83 import java.util.concurrent.Executor; 84 import java.util.function.Consumer; 85 86 /** 87 * An active voice interaction session, providing a facility for the implementation 88 * to interact with the user in the voice interaction layer. The user interface is 89 * initially shown by default, and can be created be overriding {@link #onCreateContentView()} 90 * in which the UI can be built. 91 * 92 * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish} 93 * when done. It can also initiate voice interactions with applications by calling 94 * {@link #startVoiceActivity}</p>. 95 */ 96 public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCallbacks2 { 97 static final String TAG = "VoiceInteractionSession"; 98 static final boolean DEBUG = false; 99 100 /** 101 * Flag received in {@link #onShow}: originator requested that the session be started with 102 * assist data from the currently focused activity. 103 */ 104 public static final int SHOW_WITH_ASSIST = 1<<0; 105 106 /** 107 * Flag received in {@link #onShow}: originator requested that the session be started with 108 * a screen shot of the currently focused activity. 109 */ 110 public static final int SHOW_WITH_SCREENSHOT = 1<<1; 111 112 /** 113 * Flag for use with {@link #onShow}: indicates that the session has been started from the 114 * system assist gesture. 115 */ 116 public static final int SHOW_SOURCE_ASSIST_GESTURE = 1<<2; 117 118 /** 119 * Flag for use with {@link #onShow}: indicates that the application itself has invoked 120 * the assistant. 121 */ 122 public static final int SHOW_SOURCE_APPLICATION = 1<<3; 123 124 /** 125 * Flag for use with {@link #onShow}: indicates that an Activity has invoked the voice 126 * interaction service for a local interaction using 127 * {@link Activity#startLocalVoiceInteraction(Bundle)}. 128 */ 129 public static final int SHOW_SOURCE_ACTIVITY = 1<<4; 130 131 /** 132 * Flag for use with {@link #onShow}: indicates that the voice interaction service was invoked 133 * from a physical button. 134 */ 135 public static final int SHOW_SOURCE_PUSH_TO_TALK = 1 << 5; 136 137 /** 138 * Flag for use with {@link #onShow}: indicates that the voice interaction service was invoked 139 * from a notification. 140 */ 141 public static final int SHOW_SOURCE_NOTIFICATION = 1 << 6; 142 143 /** 144 * Flag for use with {@link #onShow}: indicates that the voice interaction service was invoked 145 * from an Android automotive system UI. 146 */ 147 public static final int SHOW_SOURCE_AUTOMOTIVE_SYSTEM_UI = 1 << 7; 148 149 /** @hide */ 150 public static final int VOICE_INTERACTION_ACTIVITY_EVENT_START = 1; 151 /** @hide */ 152 public static final int VOICE_INTERACTION_ACTIVITY_EVENT_RESUME = 2; 153 /** @hide */ 154 public static final int VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE = 3; 155 /** @hide */ 156 public static final int VOICE_INTERACTION_ACTIVITY_EVENT_STOP = 4; 157 158 /** @hide */ 159 @IntDef(prefix = { "VOICE_INTERACTION_ACTIVITY_EVENT_" }, value = { 160 VOICE_INTERACTION_ACTIVITY_EVENT_START, 161 VOICE_INTERACTION_ACTIVITY_EVENT_RESUME, 162 VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE, 163 VOICE_INTERACTION_ACTIVITY_EVENT_STOP 164 }) 165 @Retention(RetentionPolicy.SOURCE) 166 public @interface VoiceInteractionActivityEventType{} 167 168 final Context mContext; 169 final HandlerCaller mHandlerCaller; 170 171 final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); 172 173 IVoiceInteractionManagerService mSystemService; 174 IBinder mToken; 175 176 int mTheme = 0; 177 LayoutInflater mInflater; 178 TypedArray mThemeAttrs; 179 View mRootView; 180 FrameLayout mContentFrame; 181 VoiceInteractionWindow mWindow; 182 183 boolean mUiEnabled = true; 184 boolean mInitialized; 185 boolean mWindowAdded; 186 boolean mWindowVisible; 187 boolean mWindowWasVisible; 188 boolean mInShowWindow; 189 190 final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); 191 192 final Insets mTmpInsets = new Insets(); 193 194 final WeakReference<VoiceInteractionSession> mWeakRef 195 = new WeakReference<VoiceInteractionSession>(this); 196 197 // Registry of remote callbacks pending a reply with reply handles. 198 final Map<SafeResultListener, Consumer<Bundle>> mRemoteCallbacks = new ArrayMap<>(); 199 200 ICancellationSignal mKillCallback; 201 202 private final Map<VisibleActivityCallback, Executor> mVisibleActivityCallbacks = 203 new ArrayMap<>(); 204 private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>(); 205 206 final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { 207 @Override 208 public IVoiceInteractorRequest startConfirmation(String callingPackage, 209 IVoiceInteractorCallback callback, VoiceInteractor.Prompt prompt, Bundle extras) { 210 ConfirmationRequest request = new ConfirmationRequest(callingPackage, 211 Binder.getCallingUid(), callback, VoiceInteractionSession.this, 212 prompt, extras); 213 addRequest(request); 214 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_CONFIRMATION, 215 request)); 216 return request.mInterface; 217 } 218 219 @Override 220 public IVoiceInteractorRequest startPickOption(String callingPackage, 221 IVoiceInteractorCallback callback, VoiceInteractor.Prompt prompt, 222 VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) { 223 PickOptionRequest request = new PickOptionRequest(callingPackage, 224 Binder.getCallingUid(), callback, VoiceInteractionSession.this, 225 prompt, options, extras); 226 addRequest(request); 227 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_PICK_OPTION, 228 request)); 229 return request.mInterface; 230 } 231 232 @Override 233 public IVoiceInteractorRequest startCompleteVoice(String callingPackage, 234 IVoiceInteractorCallback callback, VoiceInteractor.Prompt message, Bundle extras) { 235 CompleteVoiceRequest request = new CompleteVoiceRequest(callingPackage, 236 Binder.getCallingUid(), callback, VoiceInteractionSession.this, 237 message, extras); 238 addRequest(request); 239 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_COMPLETE_VOICE, 240 request)); 241 return request.mInterface; 242 } 243 244 @Override 245 public IVoiceInteractorRequest startAbortVoice(String callingPackage, 246 IVoiceInteractorCallback callback, VoiceInteractor.Prompt message, Bundle extras) { 247 AbortVoiceRequest request = new AbortVoiceRequest(callingPackage, 248 Binder.getCallingUid(), callback, VoiceInteractionSession.this, 249 message, extras); 250 addRequest(request); 251 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_ABORT_VOICE, 252 request)); 253 return request.mInterface; 254 } 255 256 @Override 257 public IVoiceInteractorRequest startCommand(String callingPackage, 258 IVoiceInteractorCallback callback, String command, Bundle extras) { 259 CommandRequest request = new CommandRequest(callingPackage, 260 Binder.getCallingUid(), callback, VoiceInteractionSession.this, 261 command, extras); 262 addRequest(request); 263 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_COMMAND, 264 request)); 265 return request.mInterface; 266 } 267 268 @Override 269 public boolean[] supportsCommands(String callingPackage, String[] commands) { 270 Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS, 271 0, commands, null); 272 SomeArgs args = mHandlerCaller.sendMessageAndWait(msg); 273 if (args != null) { 274 boolean[] res = (boolean[])args.arg1; 275 args.recycle(); 276 return res; 277 } 278 return new boolean[commands.length]; 279 } 280 281 @Override 282 public void notifyDirectActionsChanged(int taskId, IBinder assistToken) { 283 mHandlerCaller.getHandler().sendMessage(PooledLambda.obtainMessage( 284 VoiceInteractionSession::onDirectActionsInvalidated, 285 VoiceInteractionSession.this, new ActivityId(taskId, assistToken)) 286 ); 287 } 288 289 @Override 290 public void setKillCallback(ICancellationSignal callback) { 291 mKillCallback = callback; 292 } 293 }; 294 295 final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { 296 @Override 297 public void show(Bundle sessionArgs, int flags, 298 IVoiceInteractionSessionShowCallback showCallback) { 299 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW, 300 flags, sessionArgs, showCallback)); 301 } 302 303 @Override 304 public void hide() { 305 // Remove any pending messages to show the session 306 mHandlerCaller.removeMessages(MSG_SHOW); 307 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_HIDE)); 308 } 309 310 @Override 311 public void handleAssist(final int taskId, final IBinder assistToken, final Bundle data, 312 final AssistStructure structure, final AssistContent content, final int index, 313 final int count) { 314 // We want to pre-warm the AssistStructure before handing it off to the main 315 // thread. We also want to do this on a separate thread, so that if the app 316 // is for some reason slow (due to slow filling in of async children in the 317 // structure), we don't block other incoming IPCs (such as the screenshot) to 318 // us (since we are a oneway interface, they get serialized). (Okay?) 319 Thread retriever = new Thread("AssistStructure retriever") { 320 @Override 321 public void run() { 322 Throwable failure = null; 323 if (structure != null) { 324 try { 325 structure.ensureData(); 326 } catch (Throwable e) { 327 Log.w(TAG, "Failure retrieving AssistStructure", e); 328 failure = e; 329 } 330 } 331 332 SomeArgs args = SomeArgs.obtain(); 333 args.argi1 = taskId; 334 args.arg1 = data; 335 args.arg2 = (failure == null) ? structure : null; 336 args.arg3 = failure; 337 args.arg4 = content; 338 args.arg5 = assistToken; 339 args.argi5 = index; 340 args.argi6 = count; 341 342 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO( 343 MSG_HANDLE_ASSIST, args)); 344 } 345 }; 346 retriever.start(); 347 } 348 349 @Override 350 public void handleScreenshot(Bitmap screenshot) { 351 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_SCREENSHOT, 352 screenshot)); 353 } 354 355 @Override 356 public void taskStarted(Intent intent, int taskId) { 357 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED, 358 taskId, intent)); 359 } 360 361 @Override 362 public void taskFinished(Intent intent, int taskId) { 363 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_FINISHED, 364 taskId, intent)); 365 } 366 367 @Override 368 public void closeSystemDialogs() { 369 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CLOSE_SYSTEM_DIALOGS)); 370 } 371 372 @Override 373 public void onLockscreenShown() { 374 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_ON_LOCKSCREEN_SHOWN)); 375 } 376 377 @Override 378 public void destroy() { 379 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY)); 380 } 381 382 @Override 383 public void notifyVisibleActivityInfoChanged(VisibleActivityInfo visibleActivityInfo, 384 int type) { 385 mHandlerCaller.sendMessage( 386 mHandlerCaller.obtainMessageIO(MSG_NOTIFY_VISIBLE_ACTIVITY_INFO_CHANGED, type, 387 visibleActivityInfo)); 388 } 389 }; 390 391 /** 392 * Base class representing a request from a voice-driver app to perform a particular 393 * voice operation with the user. See related subclasses for the types of requests 394 * that are possible. 395 */ 396 public static class Request { 397 final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() { 398 @Override 399 public void cancel() throws RemoteException { 400 VoiceInteractionSession session = mSession.get(); 401 if (session != null) { 402 session.mHandlerCaller.sendMessage( 403 session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); 404 } 405 } 406 }; 407 final String mCallingPackage; 408 final int mCallingUid; 409 final IVoiceInteractorCallback mCallback; 410 final WeakReference<VoiceInteractionSession> mSession; 411 final Bundle mExtras; 412 Request(String packageName, int uid, IVoiceInteractorCallback callback, VoiceInteractionSession session, Bundle extras)413 Request(String packageName, int uid, IVoiceInteractorCallback callback, 414 VoiceInteractionSession session, Bundle extras) { 415 mCallingPackage = packageName; 416 mCallingUid = uid; 417 mCallback = callback; 418 mSession = session.mWeakRef; 419 mExtras = extras; 420 } 421 422 /** 423 * Return the uid of the application that initiated the request. 424 */ getCallingUid()425 public int getCallingUid() { 426 return mCallingUid; 427 } 428 429 /** 430 * Return the package name of the application that initiated the request. 431 */ getCallingPackage()432 public String getCallingPackage() { 433 return mCallingPackage; 434 } 435 436 /** 437 * Return any additional extra information that was supplied as part of the request. 438 */ getExtras()439 public Bundle getExtras() { 440 return mExtras; 441 } 442 443 /** 444 * Check whether this request is currently active. A request becomes inactive after 445 * calling {@link #cancel} or a final result method that completes the request. After 446 * this point, further interactions with the request will result in 447 * {@link java.lang.IllegalStateException} errors; you should not catch these errors, 448 * but can use this method if you need to determine the state of the request. Returns 449 * true if the request is still active. 450 */ isActive()451 public boolean isActive() { 452 VoiceInteractionSession session = mSession.get(); 453 if (session == null) { 454 return false; 455 } 456 return session.isRequestActive(mInterface.asBinder()); 457 } 458 finishRequest()459 void finishRequest() { 460 VoiceInteractionSession session = mSession.get(); 461 if (session == null) { 462 throw new IllegalStateException("VoiceInteractionSession has been destroyed"); 463 } 464 Request req = session.removeRequest(mInterface.asBinder()); 465 if (req == null) { 466 throw new IllegalStateException("Request not active: " + this); 467 } else if (req != this) { 468 throw new IllegalStateException("Current active request " + req 469 + " not same as calling request " + this); 470 } 471 } 472 473 /** 474 * Ask the app to cancel this current request. 475 * This also finishes the request (it is no longer active). 476 */ cancel()477 public void cancel() { 478 try { 479 if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface); 480 finishRequest(); 481 mCallback.deliverCancel(mInterface); 482 } catch (RemoteException e) { 483 } 484 } 485 486 @Override toString()487 public String toString() { 488 StringBuilder sb = new StringBuilder(128); 489 DebugUtils.buildShortClassTag(this, sb); 490 sb.append(" "); 491 sb.append(mInterface.asBinder()); 492 sb.append(" pkg="); 493 sb.append(mCallingPackage); 494 sb.append(" uid="); 495 UserHandle.formatUid(sb, mCallingUid); 496 sb.append('}'); 497 return sb.toString(); 498 } 499 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)500 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 501 writer.print(prefix); writer.print("mInterface="); 502 writer.println(mInterface.asBinder()); 503 writer.print(prefix); writer.print("mCallingPackage="); writer.print(mCallingPackage); 504 writer.print(" mCallingUid="); UserHandle.formatUid(writer, mCallingUid); 505 writer.println(); 506 writer.print(prefix); writer.print("mCallback="); 507 writer.println(mCallback.asBinder()); 508 if (mExtras != null) { 509 writer.print(prefix); writer.print("mExtras="); 510 writer.println(mExtras); 511 } 512 } 513 } 514 515 /** 516 * A request for confirmation from the user of an operation, as per 517 * {@link android.app.VoiceInteractor.ConfirmationRequest 518 * VoiceInteractor.ConfirmationRequest}. 519 */ 520 public static final class ConfirmationRequest extends Request { 521 final VoiceInteractor.Prompt mPrompt; 522 ConfirmationRequest(String packageName, int uid, IVoiceInteractorCallback callback, VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras)523 ConfirmationRequest(String packageName, int uid, IVoiceInteractorCallback callback, 524 VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) { 525 super(packageName, uid, callback, session, extras); 526 mPrompt = prompt; 527 } 528 529 /** 530 * Return the prompt informing the user of what will happen, as per 531 * {@link android.app.VoiceInteractor.ConfirmationRequest 532 * VoiceInteractor.ConfirmationRequest}. 533 */ 534 @Nullable getVoicePrompt()535 public VoiceInteractor.Prompt getVoicePrompt() { 536 return mPrompt; 537 } 538 539 /** 540 * Return the prompt informing the user of what will happen, as per 541 * {@link android.app.VoiceInteractor.ConfirmationRequest 542 * VoiceInteractor.ConfirmationRequest}. 543 * @deprecated Prefer {@link #getVoicePrompt()} which allows multiple voice prompts. 544 */ 545 @Deprecated 546 @Nullable getPrompt()547 public CharSequence getPrompt() { 548 return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null); 549 } 550 551 /** 552 * Report that the voice interactor has confirmed the operation with the user, resulting 553 * in a call to 554 * {@link android.app.VoiceInteractor.ConfirmationRequest#onConfirmationResult 555 * VoiceInteractor.ConfirmationRequest.onConfirmationResult}. 556 * This finishes the request (it is no longer active). 557 */ sendConfirmationResult(boolean confirmed, Bundle result)558 public void sendConfirmationResult(boolean confirmed, Bundle result) { 559 try { 560 if (DEBUG) Log.d(TAG, "sendConfirmationResult: req=" + mInterface 561 + " confirmed=" + confirmed + " result=" + result); 562 finishRequest(); 563 mCallback.deliverConfirmationResult(mInterface, confirmed, result); 564 } catch (RemoteException e) { 565 } 566 } 567 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)568 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 569 super.dump(prefix, fd, writer, args); 570 writer.print(prefix); writer.print("mPrompt="); 571 writer.println(mPrompt); 572 } 573 } 574 575 /** 576 * A request for the user to pick from a set of option, as per 577 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 578 */ 579 public static final class PickOptionRequest extends Request { 580 final VoiceInteractor.Prompt mPrompt; 581 final VoiceInteractor.PickOptionRequest.Option[] mOptions; 582 PickOptionRequest(String packageName, int uid, IVoiceInteractorCallback callback, VoiceInteractionSession session, VoiceInteractor.Prompt prompt, VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras)583 PickOptionRequest(String packageName, int uid, IVoiceInteractorCallback callback, 584 VoiceInteractionSession session, VoiceInteractor.Prompt prompt, 585 VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) { 586 super(packageName, uid, callback, session, extras); 587 mPrompt = prompt; 588 mOptions = options; 589 } 590 591 /** 592 * Return the prompt informing the user of what they are picking, as per 593 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 594 */ 595 @Nullable getVoicePrompt()596 public VoiceInteractor.Prompt getVoicePrompt() { 597 return mPrompt; 598 } 599 600 /** 601 * Return the prompt informing the user of what they are picking, as per 602 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 603 * @deprecated Prefer {@link #getVoicePrompt()} which allows multiple voice prompts. 604 */ 605 @Deprecated 606 @Nullable getPrompt()607 public CharSequence getPrompt() { 608 return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null); 609 } 610 611 /** 612 * Return the set of options the user is picking from, as per 613 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 614 */ getOptions()615 public VoiceInteractor.PickOptionRequest.Option[] getOptions() { 616 return mOptions; 617 } 618 sendPickOptionResult(boolean finished, VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result)619 void sendPickOptionResult(boolean finished, 620 VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) { 621 try { 622 if (DEBUG) Log.d(TAG, "sendPickOptionResult: req=" + mInterface 623 + " finished=" + finished + " selections=" + selections 624 + " result=" + result); 625 if (finished) { 626 finishRequest(); 627 } 628 mCallback.deliverPickOptionResult(mInterface, finished, selections, result); 629 } catch (RemoteException e) { 630 } 631 } 632 633 /** 634 * Report an intermediate option selection from the request, without completing it (the 635 * request is still active and the app is waiting for the final option selection), 636 * resulting in a call to 637 * {@link android.app.VoiceInteractor.PickOptionRequest#onPickOptionResult 638 * VoiceInteractor.PickOptionRequest.onPickOptionResult} with false for finished. 639 */ sendIntermediatePickOptionResult( VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result)640 public void sendIntermediatePickOptionResult( 641 VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) { 642 sendPickOptionResult(false, selections, result); 643 } 644 645 /** 646 * Report the final option selection for the request, completing the request 647 * and resulting in a call to 648 * {@link android.app.VoiceInteractor.PickOptionRequest#onPickOptionResult 649 * VoiceInteractor.PickOptionRequest.onPickOptionResult} with false for finished. 650 * This finishes the request (it is no longer active). 651 */ sendPickOptionResult( VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result)652 public void sendPickOptionResult( 653 VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) { 654 sendPickOptionResult(true, selections, result); 655 } 656 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)657 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 658 super.dump(prefix, fd, writer, args); 659 writer.print(prefix); writer.print("mPrompt="); 660 writer.println(mPrompt); 661 if (mOptions != null) { 662 writer.print(prefix); writer.println("Options:"); 663 for (int i=0; i<mOptions.length; i++) { 664 VoiceInteractor.PickOptionRequest.Option op = mOptions[i]; 665 writer.print(prefix); writer.print(" #"); writer.print(i); writer.println(":"); 666 writer.print(prefix); writer.print(" mLabel="); 667 writer.println(op.getLabel()); 668 writer.print(prefix); writer.print(" mIndex="); 669 writer.println(op.getIndex()); 670 if (op.countSynonyms() > 0) { 671 writer.print(prefix); writer.println(" Synonyms:"); 672 for (int j=0; j<op.countSynonyms(); j++) { 673 writer.print(prefix); writer.print(" #"); writer.print(j); 674 writer.print(": "); writer.println(op.getSynonymAt(j)); 675 } 676 } 677 if (op.getExtras() != null) { 678 writer.print(prefix); writer.print(" mExtras="); 679 writer.println(op.getExtras()); 680 } 681 } 682 } 683 } 684 } 685 686 /** 687 * A request to simply inform the user that the voice operation has completed, as per 688 * {@link android.app.VoiceInteractor.CompleteVoiceRequest 689 * VoiceInteractor.CompleteVoiceRequest}. 690 */ 691 public static final class CompleteVoiceRequest extends Request { 692 final VoiceInteractor.Prompt mPrompt; 693 CompleteVoiceRequest(String packageName, int uid, IVoiceInteractorCallback callback, VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras)694 CompleteVoiceRequest(String packageName, int uid, IVoiceInteractorCallback callback, 695 VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) { 696 super(packageName, uid, callback, session, extras); 697 mPrompt = prompt; 698 } 699 700 /** 701 * Return the message informing the user of the completion, as per 702 * {@link android.app.VoiceInteractor.CompleteVoiceRequest 703 * VoiceInteractor.CompleteVoiceRequest}. 704 */ 705 @Nullable getVoicePrompt()706 public VoiceInteractor.Prompt getVoicePrompt() { 707 return mPrompt; 708 } 709 710 /** 711 * Return the message informing the user of the completion, as per 712 * {@link android.app.VoiceInteractor.CompleteVoiceRequest 713 * VoiceInteractor.CompleteVoiceRequest}. 714 * @deprecated Prefer {@link #getVoicePrompt()} which allows a separate visual message. 715 */ 716 @Deprecated 717 @Nullable getMessage()718 public CharSequence getMessage() { 719 return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null); 720 } 721 722 /** 723 * Report that the voice interactor has finished completing the voice operation, resulting 724 * in a call to 725 * {@link android.app.VoiceInteractor.CompleteVoiceRequest#onCompleteResult 726 * VoiceInteractor.CompleteVoiceRequest.onCompleteResult}. 727 * This finishes the request (it is no longer active). 728 */ sendCompleteResult(Bundle result)729 public void sendCompleteResult(Bundle result) { 730 try { 731 if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface 732 + " result=" + result); 733 finishRequest(); 734 mCallback.deliverCompleteVoiceResult(mInterface, result); 735 } catch (RemoteException e) { 736 } 737 } 738 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)739 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 740 super.dump(prefix, fd, writer, args); 741 writer.print(prefix); writer.print("mPrompt="); 742 writer.println(mPrompt); 743 } 744 } 745 746 /** 747 * A request to report that the current user interaction can not be completed with voice, as per 748 * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. 749 */ 750 public static final class AbortVoiceRequest extends Request { 751 final VoiceInteractor.Prompt mPrompt; 752 AbortVoiceRequest(String packageName, int uid, IVoiceInteractorCallback callback, VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras)753 AbortVoiceRequest(String packageName, int uid, IVoiceInteractorCallback callback, 754 VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) { 755 super(packageName, uid, callback, session, extras); 756 mPrompt = prompt; 757 } 758 759 /** 760 * Return the message informing the user of the problem, as per 761 * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. 762 */ 763 @Nullable getVoicePrompt()764 public VoiceInteractor.Prompt getVoicePrompt() { 765 return mPrompt; 766 } 767 768 /** 769 * Return the message informing the user of the problem, as per 770 * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. 771 * @deprecated Prefer {@link #getVoicePrompt()} which allows a separate visual message. 772 */ 773 @Deprecated 774 @Nullable getMessage()775 public CharSequence getMessage() { 776 return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null); 777 } 778 779 /** 780 * Report that the voice interactor has finished aborting the voice operation, resulting 781 * in a call to 782 * {@link android.app.VoiceInteractor.AbortVoiceRequest#onAbortResult 783 * VoiceInteractor.AbortVoiceRequest.onAbortResult}. This finishes the request (it 784 * is no longer active). 785 */ sendAbortResult(Bundle result)786 public void sendAbortResult(Bundle result) { 787 try { 788 if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface 789 + " result=" + result); 790 finishRequest(); 791 mCallback.deliverAbortVoiceResult(mInterface, result); 792 } catch (RemoteException e) { 793 } 794 } 795 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)796 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 797 super.dump(prefix, fd, writer, args); 798 writer.print(prefix); writer.print("mPrompt="); 799 writer.println(mPrompt); 800 } 801 } 802 803 /** 804 * A generic vendor-specific request, as per 805 * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. 806 */ 807 public static final class CommandRequest extends Request { 808 final String mCommand; 809 CommandRequest(String packageName, int uid, IVoiceInteractorCallback callback, VoiceInteractionSession session, String command, Bundle extras)810 CommandRequest(String packageName, int uid, IVoiceInteractorCallback callback, 811 VoiceInteractionSession session, String command, Bundle extras) { 812 super(packageName, uid, callback, session, extras); 813 mCommand = command; 814 } 815 816 /** 817 * Return the command that is being executed, as per 818 * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. 819 */ getCommand()820 public String getCommand() { 821 return mCommand; 822 } 823 sendCommandResult(boolean finished, Bundle result)824 void sendCommandResult(boolean finished, Bundle result) { 825 try { 826 if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface 827 + " result=" + result); 828 if (finished) { 829 finishRequest(); 830 } 831 mCallback.deliverCommandResult(mInterface, finished, result); 832 } catch (RemoteException e) { 833 } 834 } 835 836 /** 837 * Report an intermediate result of the request, without completing it (the request 838 * is still active and the app is waiting for the final result), resulting in a call to 839 * {@link android.app.VoiceInteractor.CommandRequest#onCommandResult 840 * VoiceInteractor.CommandRequest.onCommandResult} with false for isCompleted. 841 */ sendIntermediateResult(Bundle result)842 public void sendIntermediateResult(Bundle result) { 843 sendCommandResult(false, result); 844 } 845 846 /** 847 * Report the final result of the request, completing the request and resulting in a call to 848 * {@link android.app.VoiceInteractor.CommandRequest#onCommandResult 849 * VoiceInteractor.CommandRequest.onCommandResult} with true for isCompleted. 850 * This finishes the request (it is no longer active). 851 */ sendResult(Bundle result)852 public void sendResult(Bundle result) { 853 sendCommandResult(true, result); 854 } 855 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)856 void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 857 super.dump(prefix, fd, writer, args); 858 writer.print(prefix); writer.print("mCommand="); 859 writer.println(mCommand); 860 } 861 } 862 863 static final int MSG_START_CONFIRMATION = 1; 864 static final int MSG_START_PICK_OPTION = 2; 865 static final int MSG_START_COMPLETE_VOICE = 3; 866 static final int MSG_START_ABORT_VOICE = 4; 867 static final int MSG_START_COMMAND = 5; 868 static final int MSG_SUPPORTS_COMMANDS = 6; 869 static final int MSG_CANCEL = 7; 870 871 static final int MSG_TASK_STARTED = 100; 872 static final int MSG_TASK_FINISHED = 101; 873 static final int MSG_CLOSE_SYSTEM_DIALOGS = 102; 874 static final int MSG_DESTROY = 103; 875 static final int MSG_HANDLE_ASSIST = 104; 876 static final int MSG_HANDLE_SCREENSHOT = 105; 877 static final int MSG_SHOW = 106; 878 static final int MSG_HIDE = 107; 879 static final int MSG_ON_LOCKSCREEN_SHOWN = 108; 880 static final int MSG_NOTIFY_VISIBLE_ACTIVITY_INFO_CHANGED = 109; 881 static final int MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK = 110; 882 static final int MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK = 111; 883 884 class MyCallbacks implements HandlerCaller.Callback, VoiceInteractionWindow.Callback { 885 @Override executeMessage(Message msg)886 public void executeMessage(Message msg) { 887 SomeArgs args = null; 888 switch (msg.what) { 889 case MSG_START_CONFIRMATION: 890 if (DEBUG) Log.d(TAG, "onConfirm: req=" + msg.obj); 891 onRequestConfirmation((ConfirmationRequest) msg.obj); 892 break; 893 case MSG_START_PICK_OPTION: 894 if (DEBUG) Log.d(TAG, "onPickOption: req=" + msg.obj); 895 onRequestPickOption((PickOptionRequest) msg.obj); 896 break; 897 case MSG_START_COMPLETE_VOICE: 898 if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + msg.obj); 899 onRequestCompleteVoice((CompleteVoiceRequest) msg.obj); 900 break; 901 case MSG_START_ABORT_VOICE: 902 if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + msg.obj); 903 onRequestAbortVoice((AbortVoiceRequest) msg.obj); 904 break; 905 case MSG_START_COMMAND: 906 if (DEBUG) Log.d(TAG, "onCommand: req=" + msg.obj); 907 onRequestCommand((CommandRequest) msg.obj); 908 break; 909 case MSG_SUPPORTS_COMMANDS: 910 args = (SomeArgs)msg.obj; 911 if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg1); 912 args.arg1 = onGetSupportedCommands((String[]) args.arg1); 913 args.complete(); 914 args = null; 915 break; 916 case MSG_CANCEL: 917 if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request)msg.obj)); 918 onCancelRequest((Request) msg.obj); 919 break; 920 case MSG_TASK_STARTED: 921 if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj 922 + " taskId=" + msg.arg1); 923 onTaskStarted((Intent) msg.obj, msg.arg1); 924 break; 925 case MSG_TASK_FINISHED: 926 if (DEBUG) Log.d(TAG, "onTaskFinished: intent=" + msg.obj 927 + " taskId=" + msg.arg1); 928 onTaskFinished((Intent) msg.obj, msg.arg1); 929 break; 930 case MSG_CLOSE_SYSTEM_DIALOGS: 931 if (DEBUG) Log.d(TAG, "onCloseSystemDialogs"); 932 onCloseSystemDialogs(); 933 break; 934 case MSG_DESTROY: 935 if (DEBUG) Log.d(TAG, "doDestroy"); 936 doDestroy(); 937 break; 938 case MSG_HANDLE_ASSIST: 939 args = (SomeArgs)msg.obj; 940 if (DEBUG) Log.d(TAG, "onHandleAssist: taskId=" + args.argi1 941 + "assistToken=" + args.arg5 + " data=" + args.arg1 942 + " structure=" + args.arg2 + " content=" + args.arg3 943 + " activityIndex=" + args.argi5 + " activityCount=" + args.argi6); 944 doOnHandleAssist(args.argi1, (IBinder) args.arg5, (Bundle) args.arg1, 945 (AssistStructure) args.arg2, (Throwable) args.arg3, 946 (AssistContent) args.arg4, args.argi5, args.argi6); 947 break; 948 case MSG_HANDLE_SCREENSHOT: 949 if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj); 950 onHandleScreenshot((Bitmap) msg.obj); 951 break; 952 case MSG_SHOW: 953 args = (SomeArgs)msg.obj; 954 if (DEBUG) Log.d(TAG, "doShow: args=" + args.arg1 955 + " flags=" + msg.arg1 956 + " showCallback=" + args.arg2); 957 doShow((Bundle) args.arg1, msg.arg1, 958 (IVoiceInteractionSessionShowCallback) args.arg2); 959 break; 960 case MSG_HIDE: 961 if (DEBUG) Log.d(TAG, "doHide"); 962 doHide(); 963 break; 964 case MSG_ON_LOCKSCREEN_SHOWN: 965 if (DEBUG) Log.d(TAG, "onLockscreenShown"); 966 onLockscreenShown(); 967 break; 968 case MSG_NOTIFY_VISIBLE_ACTIVITY_INFO_CHANGED: 969 if (DEBUG) { 970 Log.d(TAG, 971 "doNotifyVisibleActivityInfoChanged: visibleActivityInfo=" + msg.obj 972 + " type=" + msg.arg1); 973 } 974 doNotifyVisibleActivityInfoChanged((VisibleActivityInfo) msg.obj, msg.arg1); 975 break; 976 case MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK: 977 if (DEBUG) { 978 Log.d(TAG, "doRegisterVisibleActivityCallback"); 979 } 980 args = (SomeArgs) msg.obj; 981 doRegisterVisibleActivityCallback((Executor) args.arg1, 982 (VisibleActivityCallback) args.arg2); 983 break; 984 case MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK: 985 if (DEBUG) { 986 Log.d(TAG, "doUnregisterVisibleActivityCallback"); 987 } 988 doUnregisterVisibleActivityCallback((VisibleActivityCallback) msg.obj); 989 break; 990 } 991 if (args != null) { 992 args.recycle(); 993 } 994 } 995 996 @Override onBackPressed()997 public void onBackPressed() { 998 VoiceInteractionSession.this.onBackPressed(); 999 } 1000 } 1001 1002 final MyCallbacks mCallbacks = new MyCallbacks(); 1003 1004 /** 1005 * Information about where interesting parts of the input method UI appear. 1006 */ 1007 public static final class Insets { 1008 /** 1009 * This is the part of the UI that is the main content. It is 1010 * used to determine the basic space needed, to resize/pan the 1011 * application behind. It is assumed that this inset does not 1012 * change very much, since any change will cause a full resize/pan 1013 * of the application behind. This value is relative to the top edge 1014 * of the input method window. 1015 */ 1016 public final Rect contentInsets = new Rect(); 1017 1018 /** 1019 * This is the region of the UI that is touchable. It is used when 1020 * {@link #touchableInsets} is set to {@link #TOUCHABLE_INSETS_REGION}. 1021 * The region should be specified relative to the origin of the window frame. 1022 */ 1023 public final Region touchableRegion = new Region(); 1024 1025 /** 1026 * Option for {@link #touchableInsets}: the entire window frame 1027 * can be touched. 1028 */ 1029 public static final int TOUCHABLE_INSETS_FRAME 1030 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; 1031 1032 /** 1033 * Option for {@link #touchableInsets}: the area inside of 1034 * the content insets can be touched. 1035 */ 1036 public static final int TOUCHABLE_INSETS_CONTENT 1037 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; 1038 1039 /** 1040 * Option for {@link #touchableInsets}: the region specified by 1041 * {@link #touchableRegion} can be touched. 1042 */ 1043 public static final int TOUCHABLE_INSETS_REGION 1044 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; 1045 1046 /** 1047 * Determine which area of the window is touchable by the user. May 1048 * be one of: {@link #TOUCHABLE_INSETS_FRAME}, 1049 * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_REGION}. 1050 */ 1051 public int touchableInsets; 1052 } 1053 1054 final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = 1055 new ViewTreeObserver.OnComputeInternalInsetsListener() { 1056 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { 1057 onComputeInsets(mTmpInsets); 1058 info.contentInsets.set(mTmpInsets.contentInsets); 1059 info.visibleInsets.set(mTmpInsets.contentInsets); 1060 info.touchableRegion.set(mTmpInsets.touchableRegion); 1061 info.setTouchableInsets(mTmpInsets.touchableInsets); 1062 } 1063 }; 1064 VoiceInteractionSession(Context context)1065 public VoiceInteractionSession(Context context) { 1066 this(context, new Handler()); 1067 } 1068 VoiceInteractionSession(Context context, Handler handler)1069 public VoiceInteractionSession(Context context, Handler handler) { 1070 mContext = context; 1071 mHandlerCaller = new HandlerCaller(context, handler.getLooper(), 1072 mCallbacks, true); 1073 } 1074 getContext()1075 public Context getContext() { 1076 return mContext; 1077 } 1078 addRequest(Request req)1079 void addRequest(Request req) { 1080 synchronized (this) { 1081 mActiveRequests.put(req.mInterface.asBinder(), req); 1082 } 1083 } 1084 isRequestActive(IBinder reqInterface)1085 boolean isRequestActive(IBinder reqInterface) { 1086 synchronized (this) { 1087 return mActiveRequests.containsKey(reqInterface); 1088 } 1089 } 1090 removeRequest(IBinder reqInterface)1091 Request removeRequest(IBinder reqInterface) { 1092 synchronized (this) { 1093 return mActiveRequests.remove(reqInterface); 1094 } 1095 } 1096 doCreate(IVoiceInteractionManagerService service, IBinder token)1097 void doCreate(IVoiceInteractionManagerService service, IBinder token) { 1098 mSystemService = service; 1099 mToken = token; 1100 onCreate(); 1101 } 1102 doShow(Bundle args, int flags, final IVoiceInteractionSessionShowCallback showCallback)1103 void doShow(Bundle args, int flags, final IVoiceInteractionSessionShowCallback showCallback) { 1104 if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded 1105 + " mWindowVisible=" + mWindowVisible); 1106 1107 if (mInShowWindow) { 1108 Log.w(TAG, "Re-entrance in to showWindow"); 1109 return; 1110 } 1111 1112 try { 1113 mInShowWindow = true; 1114 onPrepareShow(args, flags); 1115 if (!mWindowVisible) { 1116 ensureWindowAdded(); 1117 } 1118 onShow(args, flags); 1119 if (!mWindowVisible) { 1120 mWindowVisible = true; 1121 if (mUiEnabled) { 1122 showWindow(); 1123 } 1124 } 1125 if (showCallback != null) { 1126 if (mUiEnabled) { 1127 mRootView.invalidate(); 1128 mRootView.getViewTreeObserver().addOnPreDrawListener( 1129 new ViewTreeObserver.OnPreDrawListener() { 1130 @Override 1131 public boolean onPreDraw() { 1132 mRootView.getViewTreeObserver().removeOnPreDrawListener(this); 1133 try { 1134 showCallback.onShown(); 1135 } catch (RemoteException e) { 1136 Log.w(TAG, "Error calling onShown", e); 1137 } 1138 return true; 1139 } 1140 }); 1141 } else { 1142 try { 1143 showCallback.onShown(); 1144 } catch (RemoteException e) { 1145 Log.w(TAG, "Error calling onShown", e); 1146 } 1147 } 1148 } 1149 } finally { 1150 mWindowWasVisible = true; 1151 mInShowWindow = false; 1152 } 1153 } 1154 doHide()1155 void doHide() { 1156 if (mWindowVisible) { 1157 ensureWindowHidden(); 1158 mWindowVisible = false; 1159 onHide(); 1160 } 1161 } 1162 doDestroy()1163 void doDestroy() { 1164 onDestroy(); 1165 if (mKillCallback != null) { 1166 try { 1167 mKillCallback.cancel(); 1168 } catch (RemoteException e) { 1169 /* ignore */ 1170 } 1171 mKillCallback = null; 1172 } 1173 if (mInitialized) { 1174 mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( 1175 mInsetsComputer); 1176 if (mWindowAdded) { 1177 mWindow.dismiss(); 1178 mWindowAdded = false; 1179 } 1180 mInitialized = false; 1181 } 1182 } 1183 doNotifyVisibleActivityInfoChanged(VisibleActivityInfo visibleActivityInfo, int type)1184 private void doNotifyVisibleActivityInfoChanged(VisibleActivityInfo visibleActivityInfo, 1185 int type) { 1186 1187 if (mVisibleActivityCallbacks.isEmpty()) { 1188 return; 1189 } 1190 1191 switch (type) { 1192 case VisibleActivityInfo.TYPE_ACTIVITY_ADDED: 1193 notifyVisibleActivityChanged(visibleActivityInfo, type); 1194 mVisibleActivityInfos.add(visibleActivityInfo); 1195 break; 1196 case VisibleActivityInfo.TYPE_ACTIVITY_REMOVED: 1197 notifyVisibleActivityChanged(visibleActivityInfo, type); 1198 mVisibleActivityInfos.remove(visibleActivityInfo); 1199 break; 1200 } 1201 } 1202 doRegisterVisibleActivityCallback(@onNull @allbackExecutor Executor executor, @NonNull VisibleActivityCallback callback)1203 private void doRegisterVisibleActivityCallback(@NonNull @CallbackExecutor Executor executor, 1204 @NonNull VisibleActivityCallback callback) { 1205 if (mVisibleActivityCallbacks.containsKey(callback)) { 1206 if (DEBUG) { 1207 Log.d(TAG, "doRegisterVisibleActivityCallback: callback has registered"); 1208 } 1209 return; 1210 } 1211 1212 int preCallbackCount = mVisibleActivityCallbacks.size(); 1213 mVisibleActivityCallbacks.put(callback, executor); 1214 1215 if (preCallbackCount == 0) { 1216 try { 1217 mSystemService.startListeningVisibleActivityChanged(mToken); 1218 } catch (RemoteException e) { 1219 e.rethrowFromSystemServer(); 1220 } 1221 } else { 1222 for (int i = 0; i < mVisibleActivityInfos.size(); i++) { 1223 final VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfos.get(i); 1224 executor.execute(() -> callback.onVisible(visibleActivityInfo)); 1225 } 1226 } 1227 } 1228 doUnregisterVisibleActivityCallback(@onNull VisibleActivityCallback callback)1229 private void doUnregisterVisibleActivityCallback(@NonNull VisibleActivityCallback callback) { 1230 mVisibleActivityCallbacks.remove(callback); 1231 1232 if (mVisibleActivityCallbacks.size() == 0) { 1233 mVisibleActivityInfos.clear(); 1234 try { 1235 mSystemService.stopListeningVisibleActivityChanged(mToken); 1236 } catch (RemoteException e) { 1237 e.rethrowFromSystemServer(); 1238 } 1239 } 1240 } 1241 notifyVisibleActivityChanged(VisibleActivityInfo visibleActivityInfo, int type)1242 private void notifyVisibleActivityChanged(VisibleActivityInfo visibleActivityInfo, int type) { 1243 for (Map.Entry<VisibleActivityCallback, Executor> e : 1244 mVisibleActivityCallbacks.entrySet()) { 1245 final Executor executor = e.getValue(); 1246 final VisibleActivityCallback visibleActivityCallback = e.getKey(); 1247 1248 switch (type) { 1249 case VisibleActivityInfo.TYPE_ACTIVITY_ADDED: 1250 Binder.withCleanCallingIdentity(() -> { 1251 executor.execute( 1252 () -> visibleActivityCallback.onVisible(visibleActivityInfo)); 1253 }); 1254 break; 1255 case VisibleActivityInfo.TYPE_ACTIVITY_REMOVED: 1256 Binder.withCleanCallingIdentity(() -> { 1257 executor.execute(() -> visibleActivityCallback.onInvisible( 1258 visibleActivityInfo.getActivityId())); 1259 }); 1260 break; 1261 } 1262 } 1263 } 1264 ensureWindowCreated()1265 void ensureWindowCreated() { 1266 if (mInitialized) { 1267 return; 1268 } 1269 1270 if (!mUiEnabled) { 1271 throw new IllegalStateException("setUiEnabled is false"); 1272 } 1273 1274 mInitialized = true; 1275 mInflater = (LayoutInflater)mContext.getSystemService( 1276 Context.LAYOUT_INFLATER_SERVICE); 1277 mWindow = new VoiceInteractionWindow(mContext, "VoiceInteractionSession", mTheme, 1278 mCallbacks, this, mDispatcherState, 1279 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true); 1280 mWindow.getWindow().getAttributes().setFitInsetsTypes(0 /* types */); 1281 mWindow.getWindow().addFlags( 1282 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | 1283 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | 1284 WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); 1285 1286 mThemeAttrs = mContext.obtainStyledAttributes(android.R.styleable.VoiceInteractionSession); 1287 mRootView = mInflater.inflate( 1288 com.android.internal.R.layout.voice_interaction_session, null); 1289 mRootView.setSystemUiVisibility( 1290 View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 1291 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 1292 mWindow.setContentView(mRootView); 1293 mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); 1294 1295 mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content); 1296 1297 mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT); 1298 mWindow.setToken(mToken); 1299 } 1300 ensureWindowAdded()1301 void ensureWindowAdded() { 1302 if (mUiEnabled && !mWindowAdded) { 1303 mWindowAdded = true; 1304 ensureWindowCreated(); 1305 View v = onCreateContentView(); 1306 if (v != null) { 1307 setContentView(v); 1308 } 1309 } 1310 } 1311 showWindow()1312 void showWindow() { 1313 if (mWindow != null) { 1314 mWindow.show(); 1315 try { 1316 mSystemService.setSessionWindowVisible(mToken, true); 1317 } catch (RemoteException e) { 1318 Log.w(TAG, "Failed to notify session window shown", e); 1319 } 1320 } 1321 } 1322 ensureWindowHidden()1323 void ensureWindowHidden() { 1324 if (mWindow != null) { 1325 mWindow.hide(); 1326 try { 1327 mSystemService.setSessionWindowVisible(mToken, false); 1328 } catch (RemoteException e) { 1329 Log.w(TAG, "Failed to notify session window hidden", e); 1330 } 1331 } 1332 } 1333 1334 /** 1335 * Equivalent to {@link VoiceInteractionService#setDisabledShowContext 1336 * VoiceInteractionService.setDisabledShowContext(int)}. 1337 */ setDisabledShowContext(int flags)1338 public void setDisabledShowContext(int flags) { 1339 try { 1340 mSystemService.setDisabledShowContext(flags); 1341 } catch (RemoteException e) { 1342 } 1343 } 1344 1345 /** 1346 * Equivalent to {@link VoiceInteractionService#getDisabledShowContext 1347 * VoiceInteractionService.getDisabledShowContext}. 1348 */ getDisabledShowContext()1349 public int getDisabledShowContext() { 1350 try { 1351 return mSystemService.getDisabledShowContext(); 1352 } catch (RemoteException e) { 1353 return 0; 1354 } 1355 } 1356 1357 /** 1358 * Return which show context flags have been disabled by the user through the system 1359 * settings UI, so the session will never get this data. Returned flags are any combination of 1360 * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and 1361 * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT 1362 * VoiceInteractionSession.SHOW_WITH_SCREENSHOT}. Note that this only tells you about 1363 * global user settings, not about restrictions that may be applied contextual based on 1364 * the current application the user is in or other transient states. 1365 */ getUserDisabledShowContext()1366 public int getUserDisabledShowContext() { 1367 try { 1368 return mSystemService.getUserDisabledShowContext(); 1369 } catch (RemoteException e) { 1370 return 0; 1371 } 1372 } 1373 1374 /** 1375 * Show the UI for this session. This asks the system to go through the process of showing 1376 * your UI, which will eventually culminate in {@link #onShow}. This is similar to calling 1377 * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. 1378 * @param args Arbitrary arguments that will be propagated {@link #onShow}. 1379 * @param flags Indicates additional optional behavior that should be performed. May 1380 * be any combination of 1381 * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and 1382 * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT 1383 * VoiceInteractionSession.SHOW_WITH_SCREENSHOT} 1384 * to request that the system generate and deliver assist data on the current foreground 1385 * app as part of showing the session UI. 1386 */ show(Bundle args, int flags)1387 public void show(Bundle args, int flags) { 1388 if (mToken == null) { 1389 throw new IllegalStateException("Can't call before onCreate()"); 1390 } 1391 try { 1392 mSystemService.showSessionFromSession(mToken, args, flags); 1393 } catch (RemoteException e) { 1394 } 1395 } 1396 1397 /** 1398 * Hide the session's UI, if currently shown. Call this when you are done with your 1399 * user interaction. 1400 */ hide()1401 public void hide() { 1402 if (mToken == null) { 1403 throw new IllegalStateException("Can't call before onCreate()"); 1404 } 1405 try { 1406 mSystemService.hideSessionFromSession(mToken); 1407 } catch (RemoteException e) { 1408 } 1409 } 1410 1411 /** 1412 * Control whether the UI layer for this session is enabled. It is enabled by default. 1413 * If set to false, you will not be able to provide a UI through {@link #onCreateContentView()}. 1414 */ setUiEnabled(boolean enabled)1415 public void setUiEnabled(boolean enabled) { 1416 if (mUiEnabled != enabled) { 1417 mUiEnabled = enabled; 1418 if (mWindowVisible) { 1419 if (enabled) { 1420 ensureWindowAdded(); 1421 showWindow(); 1422 } else { 1423 ensureWindowHidden(); 1424 } 1425 } 1426 } 1427 } 1428 1429 /** 1430 * You can call this to customize the theme used by your IME's window. 1431 * This must be set before {@link #onCreate}, so you 1432 * will typically call it in your constructor with the resource ID 1433 * of your custom theme. 1434 */ setTheme(int theme)1435 public void setTheme(int theme) { 1436 if (mWindow != null) { 1437 throw new IllegalStateException("Must be called before onCreate()"); 1438 } 1439 mTheme = theme; 1440 } 1441 1442 /** 1443 * Ask that a new activity be started for voice interaction. This will create a 1444 * new dedicated task in the activity manager for this voice interaction session; 1445 * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} 1446 * will be set for you to make it a new task. 1447 * 1448 * <p>The newly started activity will be displayed to the user in a special way, as 1449 * a layer under the voice interaction UI.</p> 1450 * 1451 * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor} 1452 * through which it can perform voice interactions through your session. These requests 1453 * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands}, 1454 * {@link #onRequestConfirmation}, {@link #onRequestPickOption}, 1455 * {@link #onRequestCompleteVoice}, {@link #onRequestAbortVoice}, 1456 * or {@link #onRequestCommand} 1457 * 1458 * <p>You will receive a call to {@link #onTaskStarted} when the task starts up 1459 * and {@link #onTaskFinished} when the last activity has finished. 1460 * 1461 * @param intent The Intent to start this voice interaction. The given Intent will 1462 * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since 1463 * this is part of a voice interaction. 1464 */ startVoiceActivity(Intent intent)1465 public void startVoiceActivity(Intent intent) { 1466 if (mToken == null) { 1467 throw new IllegalStateException("Can't call before onCreate()"); 1468 } 1469 try { 1470 intent.migrateExtraStreamToClipData(mContext); 1471 intent.prepareToLeaveProcess(mContext); 1472 int res = mSystemService.startVoiceActivity(mToken, intent, 1473 intent.resolveType(mContext.getContentResolver()), 1474 mContext.getAttributionTag()); 1475 Instrumentation.checkStartActivityResult(res, intent); 1476 } catch (RemoteException e) { 1477 } 1478 } 1479 1480 /** 1481 * <p>Ask that a new assistant activity be started. This will create a new task in the 1482 * in activity manager: this means that 1483 * {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} 1484 * will be set for you to make it a new task.</p> 1485 * 1486 * <p>The newly started activity will be displayed on top of other activities in the system 1487 * in a new layer that is not affected by multi-window mode. Tasks started from this activity 1488 * will go into the normal activity layer and not this new layer.</p> 1489 * 1490 * <p>By default, the system will create a window for the UI for this session. If you are using 1491 * an assistant activity instead, then you can disable the window creation by calling 1492 * {@link #setUiEnabled} in {@link #onPrepareShow(Bundle, int)}.</p> 1493 */ startAssistantActivity(Intent intent)1494 public void startAssistantActivity(Intent intent) { 1495 if (mToken == null) { 1496 throw new IllegalStateException("Can't call before onCreate()"); 1497 } 1498 try { 1499 intent.migrateExtraStreamToClipData(mContext); 1500 intent.prepareToLeaveProcess(mContext); 1501 int res = mSystemService.startAssistantActivity(mToken, intent, 1502 intent.resolveType(mContext.getContentResolver()), 1503 mContext.getAttributionTag()); 1504 Instrumentation.checkStartActivityResult(res, intent); 1505 } catch (RemoteException e) { 1506 } 1507 } 1508 1509 /** 1510 * Requests a list of supported actions from an app. 1511 * 1512 * @param activityId Ths activity id of the app to get the actions from. 1513 * @param cancellationSignal A signal to cancel the operation in progress, 1514 * or {@code null} if none. 1515 * @param resultExecutor The handler to receive the callback. 1516 * @param callback The callback to receive the response. 1517 */ requestDirectActions(@onNull ActivityId activityId, @Nullable CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor resultExecutor, @NonNull Consumer<List<DirectAction>> callback)1518 public final void requestDirectActions(@NonNull ActivityId activityId, 1519 @Nullable CancellationSignal cancellationSignal, 1520 @NonNull @CallbackExecutor Executor resultExecutor, 1521 @NonNull Consumer<List<DirectAction>> callback) { 1522 Objects.requireNonNull(activityId); 1523 Objects.requireNonNull(resultExecutor); 1524 Objects.requireNonNull(callback); 1525 if (mToken == null) { 1526 throw new IllegalStateException("Can't call before onCreate()"); 1527 } 1528 1529 if (cancellationSignal != null) { 1530 cancellationSignal.throwIfCanceled(); 1531 } 1532 1533 final RemoteCallback cancellationCallback = (cancellationSignal != null) 1534 ? new RemoteCallback(b -> { 1535 if (b != null) { 1536 final IBinder cancellation = b.getBinder( 1537 VoiceInteractor.KEY_CANCELLATION_SIGNAL); 1538 if (cancellation != null) { 1539 cancellationSignal.setRemote(ICancellationSignal.Stub.asInterface( 1540 cancellation)); 1541 } 1542 } 1543 }) 1544 : null; 1545 1546 try { 1547 mSystemService.requestDirectActions(mToken, activityId.getTaskId(), 1548 activityId.getAssistToken(), cancellationCallback, 1549 new RemoteCallback(createSafeResultListener((result) -> { 1550 List<DirectAction> list; 1551 if (result == null) { 1552 list = Collections.emptyList(); 1553 } else { 1554 final ParceledListSlice<DirectAction> pls = result.getParcelable( 1555 DirectAction.KEY_ACTIONS_LIST); 1556 if (pls != null) { 1557 final List<DirectAction> receivedList = pls.getList(); 1558 list = (receivedList != null) ? receivedList : Collections.emptyList(); 1559 } else { 1560 list = Collections.emptyList(); 1561 } 1562 } 1563 resultExecutor.execute(() -> callback.accept(list)); 1564 }))); 1565 } catch (RemoteException e) { 1566 e.rethrowFromSystemServer(); 1567 } 1568 } 1569 1570 /** 1571 * Called when the direct actions are invalidated. 1572 */ onDirectActionsInvalidated(@onNull ActivityId activityId)1573 public void onDirectActionsInvalidated(@NonNull ActivityId activityId) { 1574 1575 } 1576 1577 /** 1578 * Asks that an action be performed by the app. This will send a request to the app which 1579 * provided this action. 1580 * 1581 * <p> An action could take time to execute and the result is provided asynchronously 1582 * via a callback. If the action is taking longer and you want to cancel its execution 1583 * you can pass in a cancellation signal through which to notify the app to abort the 1584 * action. 1585 * 1586 * @param action The action to be performed. 1587 * @param extras Any optional extras sent to the app as part of the request 1588 * @param cancellationSignal A signal to cancel the operation in progress, 1589 * or {@code null} if none. 1590 * @param resultExecutor The handler to receive the callback. 1591 * @param resultListener The callback to receive the response. 1592 * 1593 * @see #requestDirectActions(ActivityId, CancellationSignal, Executor, Consumer) 1594 * @see Activity#onGetDirectActions(CancellationSignal, Consumer) 1595 */ performDirectAction(@onNull DirectAction action, @Nullable Bundle extras, @Nullable CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor resultExecutor, @NonNull Consumer<Bundle> resultListener)1596 public final void performDirectAction(@NonNull DirectAction action, @Nullable Bundle extras, 1597 @Nullable CancellationSignal cancellationSignal, 1598 @NonNull @CallbackExecutor Executor resultExecutor, 1599 @NonNull Consumer<Bundle> resultListener) { 1600 if (mToken == null) { 1601 throw new IllegalStateException("Can't call before onCreate()"); 1602 } 1603 Objects.requireNonNull(resultExecutor); 1604 Objects.requireNonNull(resultListener); 1605 1606 if (cancellationSignal != null) { 1607 cancellationSignal.throwIfCanceled(); 1608 } 1609 1610 final RemoteCallback cancellationCallback = (cancellationSignal != null) 1611 ? new RemoteCallback(createSafeResultListener(b -> { 1612 if (b != null) { 1613 final IBinder cancellation = b.getBinder( 1614 VoiceInteractor.KEY_CANCELLATION_SIGNAL); 1615 if (cancellation != null) { 1616 cancellationSignal.setRemote(ICancellationSignal.Stub.asInterface( 1617 cancellation)); 1618 } 1619 } 1620 })) 1621 : null; 1622 1623 final RemoteCallback resultCallback = new RemoteCallback(createSafeResultListener(b -> { 1624 if (b != null) { 1625 resultExecutor.execute(() -> resultListener.accept(b)); 1626 } else { 1627 resultExecutor.execute(() -> resultListener.accept(Bundle.EMPTY)); 1628 } 1629 })); 1630 1631 try { 1632 mSystemService.performDirectAction(mToken, action.getId(), extras, 1633 action.getTaskId(), action.getActivityId(), cancellationCallback, 1634 resultCallback); 1635 } catch (RemoteException e) { 1636 e.rethrowFromSystemServer(); 1637 } 1638 } 1639 1640 /** 1641 * Set whether this session will keep the device awake while it is running a voice 1642 * activity. By default, the system holds a wake lock for it while in this state, 1643 * so that it can work even if the screen is off. Setting this to false removes that 1644 * wake lock, allowing the CPU to go to sleep. This is typically used if the 1645 * session decides it has been waiting too long for a response from the user and 1646 * doesn't want to let this continue to drain the battery. 1647 * 1648 * <p>Passing false here will release the wake lock, and you can call later with 1649 * true to re-acquire it. It will also be automatically re-acquired for you each 1650 * time you start a new voice activity task -- that is when you call 1651 * {@link #startVoiceActivity}.</p> 1652 */ setKeepAwake(boolean keepAwake)1653 public void setKeepAwake(boolean keepAwake) { 1654 if (mToken == null) { 1655 throw new IllegalStateException("Can't call before onCreate()"); 1656 } 1657 try { 1658 mSystemService.setKeepAwake(mToken, keepAwake); 1659 } catch (RemoteException e) { 1660 } 1661 } 1662 1663 /** 1664 * Request that all system dialogs (and status bar shade etc) be closed, allowing 1665 * access to the session's UI. This will <em>not</em> cause the lock screen to be 1666 * dismissed. 1667 */ closeSystemDialogs()1668 public void closeSystemDialogs() { 1669 if (mToken == null) { 1670 throw new IllegalStateException("Can't call before onCreate()"); 1671 } 1672 try { 1673 mSystemService.closeSystemDialogs(mToken); 1674 } catch (RemoteException e) { 1675 } 1676 } 1677 1678 /** 1679 * Convenience for inflating views. 1680 */ getLayoutInflater()1681 public LayoutInflater getLayoutInflater() { 1682 ensureWindowCreated(); 1683 return mInflater; 1684 } 1685 1686 /** 1687 * Retrieve the window being used to show the session's UI. 1688 */ getWindow()1689 public Dialog getWindow() { 1690 ensureWindowCreated(); 1691 return mWindow; 1692 } 1693 1694 /** 1695 * Finish the session. This completely destroys the session -- the next time it is shown, 1696 * an entirely new one will be created. You do not normally call this function; instead, 1697 * use {@link #hide} and allow the system to destroy your session if it needs its RAM. 1698 */ finish()1699 public void finish() { 1700 if (mToken == null) { 1701 throw new IllegalStateException("Can't call before onCreate()"); 1702 } 1703 try { 1704 mSystemService.finish(mToken); 1705 } catch (RemoteException e) { 1706 } 1707 } 1708 1709 /** 1710 * Initiatize a new session. At this point you don't know exactly what this 1711 * session will be used for; you will find that out in {@link #onShow}. 1712 */ onCreate()1713 public void onCreate() { 1714 doOnCreate(); 1715 } 1716 doOnCreate()1717 private void doOnCreate() { 1718 mTheme = mTheme != 0 ? mTheme 1719 : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession; 1720 } 1721 1722 /** 1723 * Called prior to {@link #onShow} before any UI setup has occurred. Not generally useful. 1724 * 1725 * @param args The arguments that were supplied to 1726 * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. 1727 * @param showFlags The show flags originally provided to 1728 * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. 1729 */ onPrepareShow(Bundle args, int showFlags)1730 public void onPrepareShow(Bundle args, int showFlags) { 1731 } 1732 1733 /** 1734 * Called when the session UI is going to be shown. This is called after 1735 * {@link #onCreateContentView} (if the session's content UI needed to be created) and 1736 * immediately prior to the window being shown. This may be called while the window 1737 * is already shown, if a show request has come in while it is shown, to allow you to 1738 * update the UI to match the new show arguments. 1739 * 1740 * @param args The arguments that were supplied to 1741 * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. 1742 * Some example keys include : "invocation_type", "invocation_phone_state", 1743 * "invocation_time_ms", Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing 1744 * in milliseconds of the KeyEvent that triggered Assistant and 1745 * Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID) 1746 * referring to the device that sent the request. 1747 * @param showFlags The show flags originally provided to 1748 * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. 1749 */ onShow(Bundle args, int showFlags)1750 public void onShow(Bundle args, int showFlags) { 1751 } 1752 1753 /** 1754 * Called immediately after stopping to show the session UI. 1755 */ onHide()1756 public void onHide() { 1757 } 1758 1759 /** 1760 * Last callback to the session as it is being finished. 1761 */ onDestroy()1762 public void onDestroy() { 1763 } 1764 1765 /** 1766 * Hook in which to create the session's UI. 1767 */ onCreateContentView()1768 public View onCreateContentView() { 1769 return null; 1770 } 1771 setContentView(View view)1772 public void setContentView(View view) { 1773 ensureWindowCreated(); 1774 mContentFrame.removeAllViews(); 1775 mContentFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 1776 mContentFrame.requestApplyInsets(); 1777 } 1778 doOnHandleAssist(int taskId, IBinder assistToken, Bundle data, AssistStructure structure, Throwable failure, AssistContent content, int index, int count)1779 void doOnHandleAssist(int taskId, IBinder assistToken, Bundle data, AssistStructure structure, 1780 Throwable failure, AssistContent content, int index, int count) { 1781 if (failure != null) { 1782 onAssistStructureFailure(failure); 1783 } 1784 AssistState assistState = new AssistState(new ActivityId(taskId, assistToken), 1785 data, structure, content, index, count); 1786 onHandleAssist(assistState); 1787 } 1788 1789 /** 1790 * Called when there has been a failure transferring the {@link AssistStructure} to 1791 * the assistant. This may happen, for example, if the data is too large and results 1792 * in an out of memory exception, the data has been cleared during transferring due to 1793 * the new incoming assist data, or the client has provided corrupt data. This will be 1794 * called immediately before {@link #onHandleAssist} and the AssistStructure supplied 1795 * there afterwards will be null. 1796 * 1797 * @param failure The failure exception that was thrown when building the 1798 * {@link AssistStructure}. 1799 */ onAssistStructureFailure(Throwable failure)1800 public void onAssistStructureFailure(Throwable failure) { 1801 } 1802 1803 /** 1804 * Called to receive data from the application that the user was currently viewing when 1805 - * an assist session is started. If the original show request did not specify 1806 * {@link #SHOW_WITH_ASSIST}, this method will not be called. 1807 * 1808 * @param data Arbitrary data supplied by the app through 1809 * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}. 1810 * May be null if assist data has been disabled by the user or device policy. 1811 * @param structure If available, the structure definition of all windows currently 1812 * displayed by the app. May be null if assist data has been disabled by the user 1813 * or device policy; will be an empty stub if the application has disabled assist 1814 * by marking its window as secure. 1815 * @param content Additional content data supplied by the app through 1816 * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. 1817 * May be null if assist data has been disabled by the user or device policy; will 1818 * not be automatically filled in with data from the app if the app has marked its 1819 * window as secure. 1820 * 1821 * @deprecated use {@link #onHandleAssist(AssistState)} 1822 */ 1823 @Deprecated onHandleAssist(@ullable Bundle data, @Nullable AssistStructure structure, @Nullable AssistContent content)1824 public void onHandleAssist(@Nullable Bundle data, @Nullable AssistStructure structure, 1825 @Nullable AssistContent content) { 1826 } 1827 1828 /** 1829 * Called to receive data from the application that the user was currently viewing when 1830 * an assist session is started. If the original show request did not specify 1831 * {@link #SHOW_WITH_ASSIST}, {@link AssistState} parameter will only provide 1832 * {@link ActivityId}. If there was a failure to write the assist data to 1833 * {@link AssistStructure}, the {@link AssistState#getAssistStructure()} will return null. 1834 * 1835 * <p>This method is called for all activities along with an index and count that indicates 1836 * which activity the data is for. {@code index} will be between 0 and {@code count}-1 and 1837 * this method is called once for each activity in no particular order. The {@code count} 1838 * indicates how many activities to expect assist data for, including the top focused one. 1839 * The focused activity can be determined by calling {@link AssistState#isFocused()}. 1840 * 1841 * <p>To be responsive to assist requests, process assist data as soon as it is received, 1842 * without waiting for all queued activities to return assist data. 1843 * 1844 * @param state The state object capturing the state of an activity. 1845 */ onHandleAssist(@onNull AssistState state)1846 public void onHandleAssist(@NonNull AssistState state) { 1847 if (state.getAssistData() == null && state.getAssistStructure() == null 1848 && state.getAssistContent() == null) { 1849 return; 1850 } else if (state.getIndex() == 0) { 1851 onHandleAssist(state.getAssistData(), state.getAssistStructure(), 1852 state.getAssistContent()); 1853 } else { 1854 onHandleAssistSecondary(state.getAssistData(), state.getAssistStructure(), 1855 state.getAssistContent(), state.getIndex(), state.getCount()); 1856 } 1857 } 1858 1859 /** 1860 * Called to receive data from other applications that the user was or is interacting with, 1861 * that are currently on the screen in a multi-window display environment, not including the 1862 * currently focused activity. This could be 1863 * a free-form window, a picture-in-picture window, or another window in a split-screen display. 1864 * <p> 1865 * This method is very similar to 1866 * {@link #onHandleAssist} except that it is called 1867 * for additional non-focused activities along with an index and count that indicates 1868 * which additional activity the data is for. {@code index} will be between 1 and 1869 * {@code count}-1 and this method is called once for each additional window, in no particular 1870 * order. The {@code count} indicates how many windows to expect assist data for, including the 1871 * top focused activity, which continues to be returned via {@link #onHandleAssist}. 1872 * <p> 1873 * To be responsive to assist requests, process assist data as soon as it is received, 1874 * without waiting for all queued activities to return assist data. 1875 * 1876 * @param data Arbitrary data supplied by the app through 1877 * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}. 1878 * May be null if assist data has been disabled by the user or device policy. 1879 * @param structure If available, the structure definition of all windows currently 1880 * displayed by the app. May be null if assist data has been disabled by the user 1881 * or device policy; will be an empty stub if the application has disabled assist 1882 * by marking its window as secure. 1883 * @param content Additional content data supplied by the app through 1884 * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. 1885 * May be null if assist data has been disabled by the user or device policy; will 1886 * not be automatically filled in with data from the app if the app has marked its 1887 * window as secure. 1888 * @param index the index of the additional activity that this data 1889 * is for. 1890 * @param count the total number of additional activities for which the assist data is being 1891 * returned, including the focused activity that is returned via 1892 * {@link #onHandleAssist}. 1893 * 1894 * @deprecated use {@link #onHandleAssist(AssistState)} 1895 */ 1896 @Deprecated onHandleAssistSecondary(@ullable Bundle data, @Nullable AssistStructure structure, @Nullable AssistContent content, int index, int count)1897 public void onHandleAssistSecondary(@Nullable Bundle data, @Nullable AssistStructure structure, 1898 @Nullable AssistContent content, int index, int count) { 1899 } 1900 1901 /** 1902 * Called to receive a screenshot of what the user was currently viewing when an assist 1903 * session is started. May be null if screenshots are disabled by the user, policy, 1904 * or application. If the original show request did not specify 1905 * {@link #SHOW_WITH_SCREENSHOT}, this method will not be called. 1906 */ onHandleScreenshot(@ullable Bitmap screenshot)1907 public void onHandleScreenshot(@Nullable Bitmap screenshot) { 1908 } 1909 onKeyDown(int keyCode, KeyEvent event)1910 public boolean onKeyDown(int keyCode, KeyEvent event) { 1911 return false; 1912 } 1913 onKeyLongPress(int keyCode, KeyEvent event)1914 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 1915 return false; 1916 } 1917 onKeyUp(int keyCode, KeyEvent event)1918 public boolean onKeyUp(int keyCode, KeyEvent event) { 1919 return false; 1920 } 1921 onKeyMultiple(int keyCode, int count, KeyEvent event)1922 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { 1923 return false; 1924 } 1925 1926 /** 1927 * Called when the user presses the back button while focus is in the session UI. Note 1928 * that this will only happen if the session UI has requested input focus in its window; 1929 * otherwise, the back key will go to whatever window has focus and do whatever behavior 1930 * it normally has there. The default implementation simply calls {@link #hide}. 1931 */ onBackPressed()1932 public void onBackPressed() { 1933 hide(); 1934 } 1935 1936 /** 1937 * Sessions automatically watch for requests that all system UI be closed (such as when 1938 * the user presses HOME), which will appear here. The default implementation always 1939 * calls {@link #hide}. 1940 */ onCloseSystemDialogs()1941 public void onCloseSystemDialogs() { 1942 hide(); 1943 } 1944 1945 /** 1946 * Called when the lockscreen was shown. 1947 */ onLockscreenShown()1948 public void onLockscreenShown() { 1949 hide(); 1950 } 1951 1952 @Override onConfigurationChanged(Configuration newConfig)1953 public void onConfigurationChanged(Configuration newConfig) { 1954 } 1955 1956 @Override onLowMemory()1957 public void onLowMemory() { 1958 } 1959 1960 @Override onTrimMemory(int level)1961 public void onTrimMemory(int level) { 1962 } 1963 1964 /** 1965 * Compute the interesting insets into your UI. The default implementation 1966 * sets {@link Insets#contentInsets outInsets.contentInsets.top} to the height 1967 * of the window, meaning it should not adjust content underneath. The default touchable 1968 * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}, meaning it consumes all touch 1969 * events within its window frame. 1970 * 1971 * @param outInsets Fill in with the current UI insets. 1972 */ onComputeInsets(Insets outInsets)1973 public void onComputeInsets(Insets outInsets) { 1974 outInsets.contentInsets.left = 0; 1975 outInsets.contentInsets.bottom = 0; 1976 outInsets.contentInsets.right = 0; 1977 View decor = getWindow().getWindow().getDecorView(); 1978 outInsets.contentInsets.top = decor.getHeight(); 1979 outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME; 1980 outInsets.touchableRegion.setEmpty(); 1981 } 1982 1983 /** 1984 * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)} 1985 * has actually started. 1986 * 1987 * @param intent The original {@link Intent} supplied to 1988 * {@link #startVoiceActivity(android.content.Intent)}. 1989 * @param taskId Unique ID of the now running task. 1990 */ onTaskStarted(Intent intent, int taskId)1991 public void onTaskStarted(Intent intent, int taskId) { 1992 } 1993 1994 /** 1995 * Called when the last activity of a task initiated by 1996 * {@link #startVoiceActivity(android.content.Intent)} has finished. The default 1997 * implementation calls {@link #finish()} on the assumption that this represents 1998 * the completion of a voice action. You can override the implementation if you would 1999 * like a different behavior. 2000 * 2001 * @param intent The original {@link Intent} supplied to 2002 * {@link #startVoiceActivity(android.content.Intent)}. 2003 * @param taskId Unique ID of the finished task. 2004 */ onTaskFinished(Intent intent, int taskId)2005 public void onTaskFinished(Intent intent, int taskId) { 2006 hide(); 2007 } 2008 2009 /** 2010 * Request to query for what extended commands the session supports. 2011 * 2012 * @param commands An array of commands that are being queried. 2013 * @return Return an array of booleans indicating which of each entry in the 2014 * command array is supported. A true entry in the array indicates the command 2015 * is supported; false indicates it is not. The default implementation returns 2016 * an array of all false entries. 2017 */ onGetSupportedCommands(String[] commands)2018 public boolean[] onGetSupportedCommands(String[] commands) { 2019 return new boolean[commands.length]; 2020 } 2021 2022 /** 2023 * Request to confirm with the user before proceeding with an unrecoverable operation, 2024 * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest 2025 * VoiceInteractor.ConfirmationRequest}. 2026 * 2027 * @param request The active request. 2028 */ onRequestConfirmation(ConfirmationRequest request)2029 public void onRequestConfirmation(ConfirmationRequest request) { 2030 } 2031 2032 /** 2033 * Request for the user to pick one of N options, corresponding to a 2034 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 2035 * 2036 * @param request The active request. 2037 */ onRequestPickOption(PickOptionRequest request)2038 public void onRequestPickOption(PickOptionRequest request) { 2039 } 2040 2041 /** 2042 * Request to complete the voice interaction session because the voice activity successfully 2043 * completed its interaction using voice. Corresponds to 2044 * {@link android.app.VoiceInteractor.CompleteVoiceRequest 2045 * VoiceInteractor.CompleteVoiceRequest}. The default implementation just sends an empty 2046 * confirmation back to allow the activity to exit. 2047 * 2048 * @param request The active request. 2049 */ onRequestCompleteVoice(CompleteVoiceRequest request)2050 public void onRequestCompleteVoice(CompleteVoiceRequest request) { 2051 } 2052 2053 /** 2054 * Request to abort the voice interaction session because the voice activity can not 2055 * complete its interaction using voice. Corresponds to 2056 * {@link android.app.VoiceInteractor.AbortVoiceRequest 2057 * VoiceInteractor.AbortVoiceRequest}. The default implementation just sends an empty 2058 * confirmation back to allow the activity to exit. 2059 * 2060 * @param request The active request. 2061 */ onRequestAbortVoice(AbortVoiceRequest request)2062 public void onRequestAbortVoice(AbortVoiceRequest request) { 2063 } 2064 2065 /** 2066 * Process an arbitrary extended command from the caller, 2067 * corresponding to a {@link android.app.VoiceInteractor.CommandRequest 2068 * VoiceInteractor.CommandRequest}. 2069 * 2070 * @param request The active request. 2071 */ onRequestCommand(CommandRequest request)2072 public void onRequestCommand(CommandRequest request) { 2073 } 2074 2075 /** 2076 * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request} 2077 * that was previously delivered to {@link #onRequestConfirmation}, 2078 * {@link #onRequestPickOption}, {@link #onRequestCompleteVoice}, {@link #onRequestAbortVoice}, 2079 * or {@link #onRequestCommand}. 2080 * 2081 * @param request The request that is being canceled. 2082 */ onCancelRequest(Request request)2083 public void onCancelRequest(Request request) { 2084 } 2085 2086 /** 2087 * Registers a callback that will be notified when visible activities have been changed. 2088 * 2089 * Note: The {@link VisibleActivityCallback#onVisible(VisibleActivityInfo)} will be called 2090 * immediately with current visible activities when the callback is registered for the first 2091 * time. If the callback is already registered, this method does nothing. 2092 * 2093 * @param executor The executor which will be used to invoke the callback. 2094 * @param callback The callback to receive the response. 2095 * 2096 * @throws IllegalStateException if calling this method before onCreate(). 2097 */ registerVisibleActivityCallback(@onNull @allbackExecutor Executor executor, @NonNull VisibleActivityCallback callback)2098 public final void registerVisibleActivityCallback(@NonNull @CallbackExecutor Executor executor, 2099 @NonNull VisibleActivityCallback callback) { 2100 if (DEBUG) { 2101 Log.d(TAG, "registerVisibleActivityCallback"); 2102 } 2103 if (mToken == null) { 2104 throw new IllegalStateException("Can't call before onCreate()"); 2105 } 2106 Objects.requireNonNull(executor); 2107 Objects.requireNonNull(callback); 2108 2109 mHandlerCaller.sendMessage( 2110 mHandlerCaller.obtainMessageOO(MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK, executor, 2111 callback)); 2112 } 2113 2114 /** 2115 * Unregisters the callback. 2116 * 2117 * @param callback The callback to receive the response. 2118 */ unregisterVisibleActivityCallback(@onNull VisibleActivityCallback callback)2119 public final void unregisterVisibleActivityCallback(@NonNull VisibleActivityCallback callback) { 2120 if (DEBUG) { 2121 Log.d(TAG, "unregisterVisibleActivityCallback"); 2122 } 2123 Objects.requireNonNull(callback); 2124 2125 mHandlerCaller.sendMessage( 2126 mHandlerCaller.obtainMessageO(MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK, callback)); 2127 } 2128 2129 /** 2130 * Print the Service's state into the given stream. This gets invoked by 2131 * {@link VoiceInteractionSessionService} when its Service 2132 * {@link android.app.Service#dump} method is called. 2133 * 2134 * @param prefix Text to print at the front of each line. 2135 * @param fd The raw file descriptor that the dump is being sent to. 2136 * @param writer The PrintWriter to which you should dump your state. This will be 2137 * closed for you after you return. 2138 * @param args additional arguments to the dump request. 2139 */ dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)2140 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 2141 writer.print(prefix); writer.print("mToken="); writer.println(mToken); 2142 writer.print(prefix); writer.print("mTheme=#"); writer.println(Integer.toHexString(mTheme)); 2143 writer.print(prefix); writer.print("mUiEnabled="); writer.println(mUiEnabled); 2144 writer.print(" mInitialized="); writer.println(mInitialized); 2145 writer.print(prefix); writer.print("mWindowAdded="); writer.print(mWindowAdded); 2146 writer.print(" mWindowVisible="); writer.println(mWindowVisible); 2147 writer.print(prefix); writer.print("mWindowWasVisible="); writer.print(mWindowWasVisible); 2148 writer.print(" mInShowWindow="); writer.println(mInShowWindow); 2149 if (mActiveRequests.size() > 0) { 2150 writer.print(prefix); writer.println("Active requests:"); 2151 String innerPrefix = prefix + " "; 2152 for (int i=0; i<mActiveRequests.size(); i++) { 2153 Request req = mActiveRequests.valueAt(i); 2154 writer.print(prefix); writer.print(" #"); writer.print(i); 2155 writer.print(": "); 2156 writer.println(req); 2157 req.dump(innerPrefix, fd, writer, args); 2158 2159 } 2160 } 2161 } 2162 createSafeResultListener( @onNull Consumer<Bundle> consumer)2163 private SafeResultListener createSafeResultListener( 2164 @NonNull Consumer<Bundle> consumer) { 2165 synchronized (this) { 2166 final SafeResultListener listener = new SafeResultListener(consumer, this); 2167 mRemoteCallbacks.put(listener, consumer); 2168 return listener; 2169 } 2170 } 2171 removeSafeResultListener(@onNull SafeResultListener listener)2172 private Consumer<Bundle> removeSafeResultListener(@NonNull SafeResultListener listener) { 2173 synchronized (this) { 2174 return mRemoteCallbacks.remove(listener); 2175 } 2176 } 2177 2178 /** 2179 * Callback interface for receiving visible activity changes used for assistant usage. 2180 */ 2181 public interface VisibleActivityCallback { 2182 /** Callback to inform that an activity has become visible. */ onVisible(@onNull VisibleActivityInfo activityInfo)2183 default void onVisible(@NonNull VisibleActivityInfo activityInfo) {} 2184 2185 /** Callback to inform that a visible activity has gone. */ onInvisible(@onNull ActivityId activityId)2186 default void onInvisible(@NonNull ActivityId activityId) {} 2187 } 2188 2189 /** 2190 * Represents assist state captured when this session was started. 2191 * It contains the various assist data objects and a reference to 2192 * the source activity. 2193 */ 2194 @Immutable 2195 public static final class AssistState { 2196 private final @NonNull ActivityId mActivityId; 2197 private final int mIndex; 2198 private final int mCount; 2199 private final @Nullable Bundle mData; 2200 private final @Nullable AssistStructure mStructure; 2201 private final @Nullable AssistContent mContent; 2202 AssistState(@onNull ActivityId activityId, @Nullable Bundle data, @Nullable AssistStructure structure, @Nullable AssistContent content, int index, int count)2203 AssistState(@NonNull ActivityId activityId, @Nullable Bundle data, 2204 @Nullable AssistStructure structure, @Nullable AssistContent content, 2205 int index, int count) { 2206 mActivityId = activityId; 2207 mIndex = index; 2208 mCount = count; 2209 mData = data; 2210 mStructure = structure; 2211 mContent = content; 2212 } 2213 2214 /** 2215 * @return whether the source activity is focused. 2216 */ isFocused()2217 public boolean isFocused() { 2218 return mIndex == 0; 2219 } 2220 2221 /** 2222 * @return the index of the activity that this state is for or -1 2223 * if there was no assist data captured. 2224 */ getIndex()2225 public @IntRange(from = -1) int getIndex() { 2226 return mIndex; 2227 } 2228 2229 /** 2230 * @return the total number of activities for which the assist data is 2231 * being returned. 2232 */ getCount()2233 public @IntRange(from = 0) int getCount() { 2234 return mCount; 2235 } 2236 2237 /** 2238 * @return the id of the source activity 2239 */ getActivityId()2240 public @NonNull ActivityId getActivityId() { 2241 return mActivityId; 2242 } 2243 2244 /** 2245 * @return Arbitrary data supplied by the app through 2246 * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}. 2247 * May be null if assist data has been disabled by the user or device policy; will be null 2248 * if the original show request did not specify {@link #SHOW_WITH_ASSIST}. 2249 */ getAssistData()2250 public @Nullable Bundle getAssistData() { 2251 return mData; 2252 } 2253 2254 /** 2255 * @return If available, the structure definition of all windows currently 2256 * displayed by the app. May be null if assist data has been disabled by the user 2257 * or device policy; will be null if the original show request did not specify 2258 * {@link #SHOW_WITH_ASSIST} or the assist data has been corrupt when writing the data to 2259 * {@link AssistStructure}; will be an empty stub if the application has disabled assist 2260 * by marking its window as secure. 2261 */ getAssistStructure()2262 public @Nullable AssistStructure getAssistStructure() { 2263 return mStructure; 2264 } 2265 2266 /** 2267 * @return Additional content data supplied by the app through 2268 * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. 2269 * May be null if assist data has been disabled by the user or device policy; will be null 2270 * if the original show request did not specify {@link #SHOW_WITH_ASSIST}. Will not be 2271 * automatically filled in with data from the app if the app has marked its window as 2272 * secure. 2273 */ getAssistContent()2274 public @Nullable AssistContent getAssistContent() { 2275 return mContent; 2276 } 2277 } 2278 2279 /** 2280 * Represents the id of an assist source activity. You can use 2281 * {@link #equals(Object)} to compare instances of this class. 2282 */ 2283 public static class ActivityId { 2284 private final int mTaskId; 2285 private final IBinder mAssistToken; 2286 ActivityId(int taskId, IBinder assistToken)2287 ActivityId(int taskId, IBinder assistToken) { 2288 mTaskId = taskId; 2289 mAssistToken = assistToken; 2290 } 2291 getTaskId()2292 int getTaskId() { 2293 return mTaskId; 2294 } 2295 getAssistToken()2296 IBinder getAssistToken() { 2297 return mAssistToken; 2298 } 2299 2300 @Override equals(@ullable Object o)2301 public boolean equals(@Nullable Object o) { 2302 if (this == o) { 2303 return true; 2304 } 2305 if (o == null || getClass() != o.getClass()) { 2306 return false; 2307 } 2308 2309 ActivityId that = (ActivityId) o; 2310 2311 if (mTaskId != that.mTaskId) { 2312 return false; 2313 } 2314 return mAssistToken != null 2315 ? mAssistToken.equals(that.mAssistToken) 2316 : that.mAssistToken == null; 2317 } 2318 2319 @Override hashCode()2320 public int hashCode() { 2321 int result = mTaskId; 2322 result = 31 * result + (mAssistToken != null ? mAssistToken.hashCode() : 0); 2323 return result; 2324 } 2325 } 2326 2327 private static class SafeResultListener implements RemoteCallback.OnResultListener { 2328 private final @NonNull WeakReference<VoiceInteractionSession> mWeakSession; 2329 SafeResultListener(@onNull Consumer<Bundle> action, @NonNull VoiceInteractionSession session)2330 SafeResultListener(@NonNull Consumer<Bundle> action, 2331 @NonNull VoiceInteractionSession session) { 2332 mWeakSession = new WeakReference<>(session); 2333 } 2334 2335 @Override onResult(Bundle result)2336 public void onResult(Bundle result) { 2337 final VoiceInteractionSession session = mWeakSession.get(); 2338 if (session != null) { 2339 final Consumer<Bundle> consumer = session.removeSafeResultListener(this); 2340 if (consumer != null) { 2341 consumer.accept(result); 2342 } 2343 } 2344 } 2345 } 2346 } 2347