VoiceInteractionSession.java revision 38a499edf5542fbd0948a02b77ecd84c0d62a846
1/** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.service.voice; 18 19import android.app.AssistStructure; 20import android.app.Dialog; 21import android.app.Instrumentation; 22import android.app.VoiceInteractor; 23import android.content.ComponentCallbacks2; 24import android.content.Context; 25import android.content.Intent; 26import android.content.res.Configuration; 27import android.content.res.TypedArray; 28import android.graphics.Bitmap; 29import android.graphics.Rect; 30import android.graphics.Region; 31import android.inputmethodservice.SoftInputWindow; 32import android.os.Binder; 33import android.os.Bundle; 34import android.os.Handler; 35import android.os.IBinder; 36import android.os.Message; 37import android.os.RemoteException; 38import android.util.ArrayMap; 39import android.util.Log; 40import android.view.Gravity; 41import android.view.KeyEvent; 42import android.view.LayoutInflater; 43import android.view.View; 44import android.view.ViewGroup; 45import android.view.ViewTreeObserver; 46import android.view.WindowManager; 47import android.widget.FrameLayout; 48import com.android.internal.app.IVoiceInteractionManagerService; 49import com.android.internal.app.IVoiceInteractionSessionShowCallback; 50import com.android.internal.app.IVoiceInteractor; 51import com.android.internal.app.IVoiceInteractorCallback; 52import com.android.internal.app.IVoiceInteractorRequest; 53import com.android.internal.os.HandlerCaller; 54import com.android.internal.os.SomeArgs; 55 56import java.lang.ref.WeakReference; 57 58import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 59 60/** 61 * An active voice interaction session, providing a facility for the implementation 62 * to interact with the user in the voice interaction layer. The user interface is 63 * initially shown by default, and can be created be overriding {@link #onCreateContentView()} 64 * in which the UI can be built. 65 * 66 * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish} 67 * when done. It can also initiate voice interactions with applications by calling 68 * {@link #startVoiceActivity}</p>. 69 */ 70public abstract class VoiceInteractionSession implements KeyEvent.Callback, 71 ComponentCallbacks2 { 72 static final String TAG = "VoiceInteractionSession"; 73 static final boolean DEBUG = true; 74 75 final Context mContext; 76 final HandlerCaller mHandlerCaller; 77 78 final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); 79 80 IVoiceInteractionManagerService mSystemService; 81 IBinder mToken; 82 83 int mTheme = 0; 84 LayoutInflater mInflater; 85 TypedArray mThemeAttrs; 86 View mRootView; 87 FrameLayout mContentFrame; 88 SoftInputWindow mWindow; 89 90 boolean mInitialized; 91 boolean mWindowAdded; 92 boolean mWindowVisible; 93 boolean mWindowWasVisible; 94 boolean mInShowWindow; 95 96 final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); 97 98 final Insets mTmpInsets = new Insets(); 99 100 final WeakReference<VoiceInteractionSession> mWeakRef 101 = new WeakReference<VoiceInteractionSession>(this); 102 103 final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { 104 @Override 105 public IVoiceInteractorRequest startConfirmation(String callingPackage, 106 IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) { 107 Request request = newRequest(callback); 108 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION, 109 new Caller(callingPackage, Binder.getCallingUid()), request, 110 prompt, extras)); 111 return request.mInterface; 112 } 113 114 @Override 115 public IVoiceInteractorRequest startPickOption(String callingPackage, 116 IVoiceInteractorCallback callback, CharSequence prompt, 117 VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) { 118 Request request = newRequest(callback); 119 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOOO(MSG_START_PICK_OPTION, 120 new Caller(callingPackage, Binder.getCallingUid()), request, 121 prompt, options, extras)); 122 return request.mInterface; 123 } 124 125 @Override 126 public IVoiceInteractorRequest startCompleteVoice(String callingPackage, 127 IVoiceInteractorCallback callback, CharSequence message, Bundle extras) { 128 Request request = newRequest(callback); 129 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMPLETE_VOICE, 130 new Caller(callingPackage, Binder.getCallingUid()), request, 131 message, extras)); 132 return request.mInterface; 133 } 134 135 @Override 136 public IVoiceInteractorRequest startAbortVoice(String callingPackage, 137 IVoiceInteractorCallback callback, CharSequence message, Bundle extras) { 138 Request request = newRequest(callback); 139 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE, 140 new Caller(callingPackage, Binder.getCallingUid()), request, 141 message, extras)); 142 return request.mInterface; 143 } 144 145 @Override 146 public IVoiceInteractorRequest startCommand(String callingPackage, 147 IVoiceInteractorCallback callback, String command, Bundle extras) { 148 Request request = newRequest(callback); 149 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND, 150 new Caller(callingPackage, Binder.getCallingUid()), request, 151 command, extras)); 152 return request.mInterface; 153 } 154 155 @Override 156 public boolean[] supportsCommands(String callingPackage, String[] commands) { 157 Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS, 158 0, new Caller(callingPackage, Binder.getCallingUid()), commands); 159 SomeArgs args = mHandlerCaller.sendMessageAndWait(msg); 160 if (args != null) { 161 boolean[] res = (boolean[])args.arg1; 162 args.recycle(); 163 return res; 164 } 165 return new boolean[commands.length]; 166 } 167 }; 168 169 final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { 170 @Override 171 public void show(Bundle sessionArgs, int flags, 172 IVoiceInteractionSessionShowCallback showCallback) { 173 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW, 174 flags, sessionArgs, showCallback)); 175 } 176 177 @Override 178 public void hide() { 179 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_HIDE)); 180 } 181 182 @Override 183 public void handleAssist(Bundle assistBundle) { 184 // We want to pre-warm the AssistStructure before handing it off to the main 185 // thread. There is a strong argument to be made that it should be handed 186 // through as a separate param rather than part of the assistBundle. 187 if (assistBundle != null) { 188 Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT); 189 if (assistContext != null) { 190 AssistStructure as = AssistStructure.getAssistStructure(assistContext); 191 if (as != null) { 192 as.ensureData(); 193 } 194 } 195 } 196 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_ASSIST, 197 assistBundle)); 198 } 199 200 @Override 201 public void handleScreenshot(Bitmap screenshot) { 202 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_SCREENSHOT, 203 screenshot)); 204 } 205 206 @Override 207 public void taskStarted(Intent intent, int taskId) { 208 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED, 209 taskId, intent)); 210 } 211 212 @Override 213 public void taskFinished(Intent intent, int taskId) { 214 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_FINISHED, 215 taskId, intent)); 216 } 217 218 @Override 219 public void closeSystemDialogs() { 220 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CLOSE_SYSTEM_DIALOGS)); 221 } 222 223 @Override 224 public void destroy() { 225 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY)); 226 } 227 }; 228 229 public static class Request { 230 final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() { 231 @Override 232 public void cancel() throws RemoteException { 233 VoiceInteractionSession session = mSession.get(); 234 if (session != null) { 235 session.mHandlerCaller.sendMessage( 236 session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); 237 } 238 } 239 }; 240 final IVoiceInteractorCallback mCallback; 241 final WeakReference<VoiceInteractionSession> mSession; 242 243 Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) { 244 mCallback = callback; 245 mSession = session.mWeakRef; 246 } 247 248 void finishRequest() { 249 VoiceInteractionSession session = mSession.get(); 250 if (session == null) { 251 throw new IllegalStateException("VoiceInteractionSession has been destroyed"); 252 } 253 Request req = session.removeRequest(mInterface.asBinder()); 254 if (req == null) { 255 throw new IllegalStateException("Request not active: " + this); 256 } else if (req != this) { 257 throw new IllegalStateException("Current active request " + req 258 + " not same as calling request " + this); 259 } 260 } 261 262 public void sendConfirmResult(boolean confirmed, Bundle result) { 263 try { 264 if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface 265 + " confirmed=" + confirmed + " result=" + result); 266 finishRequest(); 267 mCallback.deliverConfirmationResult(mInterface, confirmed, result); 268 } catch (RemoteException e) { 269 } 270 } 271 272 public void sendPickOptionResult(boolean finished, 273 VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) { 274 try { 275 if (DEBUG) Log.d(TAG, "sendPickOptionResult: req=" + mInterface 276 + " finished=" + finished + " selections=" + selections 277 + " result=" + result); 278 if (finished) { 279 finishRequest(); 280 } 281 mCallback.deliverPickOptionResult(mInterface, finished, selections, result); 282 } catch (RemoteException e) { 283 } 284 } 285 286 public void sendCompleteVoiceResult(Bundle result) { 287 try { 288 if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface 289 + " result=" + result); 290 finishRequest(); 291 mCallback.deliverCompleteVoiceResult(mInterface, result); 292 } catch (RemoteException e) { 293 } 294 } 295 296 public void sendAbortVoiceResult(Bundle result) { 297 try { 298 if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface 299 + " result=" + result); 300 finishRequest(); 301 mCallback.deliverAbortVoiceResult(mInterface, result); 302 } catch (RemoteException e) { 303 } 304 } 305 306 public void sendCommandResult(boolean finished, Bundle result) { 307 try { 308 if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface 309 + " result=" + result); 310 if (finished) { 311 finishRequest(); 312 } 313 mCallback.deliverCommandResult(mInterface, finished, result); 314 } catch (RemoteException e) { 315 } 316 } 317 318 public void sendCancelResult() { 319 try { 320 if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface); 321 finishRequest(); 322 mCallback.deliverCancel(mInterface); 323 } catch (RemoteException e) { 324 } 325 } 326 } 327 328 public static class Caller { 329 final String packageName; 330 final int uid; 331 332 Caller(String _packageName, int _uid) { 333 packageName = _packageName; 334 uid = _uid; 335 } 336 } 337 338 static final int MSG_START_CONFIRMATION = 1; 339 static final int MSG_START_PICK_OPTION = 2; 340 static final int MSG_START_COMPLETE_VOICE = 3; 341 static final int MSG_START_ABORT_VOICE = 4; 342 static final int MSG_START_COMMAND = 5; 343 static final int MSG_SUPPORTS_COMMANDS = 6; 344 static final int MSG_CANCEL = 7; 345 346 static final int MSG_TASK_STARTED = 100; 347 static final int MSG_TASK_FINISHED = 101; 348 static final int MSG_CLOSE_SYSTEM_DIALOGS = 102; 349 static final int MSG_DESTROY = 103; 350 static final int MSG_HANDLE_ASSIST = 104; 351 static final int MSG_HANDLE_SCREENSHOT = 105; 352 static final int MSG_SHOW = 106; 353 static final int MSG_HIDE = 107; 354 355 class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback { 356 @Override 357 public void executeMessage(Message msg) { 358 SomeArgs args; 359 switch (msg.what) { 360 case MSG_START_CONFIRMATION: 361 args = (SomeArgs)msg.obj; 362 if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface 363 + " prompt=" + args.arg3 + " extras=" + args.arg4); 364 onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, 365 (Bundle)args.arg4); 366 break; 367 case MSG_START_PICK_OPTION: 368 args = (SomeArgs)msg.obj; 369 if (DEBUG) Log.d(TAG, "onPickOption: req=" + ((Request) args.arg2).mInterface 370 + " prompt=" + args.arg3 + " options=" + args.arg4 371 + " extras=" + args.arg5); 372 onPickOption((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, 373 (VoiceInteractor.PickOptionRequest.Option[])args.arg4, 374 (Bundle)args.arg5); 375 break; 376 case MSG_START_COMPLETE_VOICE: 377 args = (SomeArgs)msg.obj; 378 if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((Request) args.arg2).mInterface 379 + " message=" + args.arg3 + " extras=" + args.arg4); 380 onCompleteVoice((Caller) args.arg1, (Request) args.arg2, 381 (CharSequence) args.arg3, (Bundle) args.arg4); 382 break; 383 case MSG_START_ABORT_VOICE: 384 args = (SomeArgs)msg.obj; 385 if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface 386 + " message=" + args.arg3 + " extras=" + args.arg4); 387 onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3, 388 (Bundle) args.arg4); 389 break; 390 case MSG_START_COMMAND: 391 args = (SomeArgs)msg.obj; 392 if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface 393 + " command=" + args.arg3 + " extras=" + args.arg4); 394 onCommand((Caller) args.arg1, (Request) args.arg2, (String) args.arg3, 395 (Bundle) args.arg4); 396 break; 397 case MSG_SUPPORTS_COMMANDS: 398 args = (SomeArgs)msg.obj; 399 if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2); 400 args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2); 401 break; 402 case MSG_CANCEL: 403 if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request)msg.obj)); 404 onCancel((Request)msg.obj); 405 break; 406 case MSG_TASK_STARTED: 407 if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj 408 + " taskId=" + msg.arg1); 409 onTaskStarted((Intent) msg.obj, msg.arg1); 410 break; 411 case MSG_TASK_FINISHED: 412 if (DEBUG) Log.d(TAG, "onTaskFinished: intent=" + msg.obj 413 + " taskId=" + msg.arg1); 414 onTaskFinished((Intent) msg.obj, msg.arg1); 415 break; 416 case MSG_CLOSE_SYSTEM_DIALOGS: 417 if (DEBUG) Log.d(TAG, "onCloseSystemDialogs"); 418 onCloseSystemDialogs(); 419 break; 420 case MSG_DESTROY: 421 if (DEBUG) Log.d(TAG, "doDestroy"); 422 doDestroy(); 423 break; 424 case MSG_HANDLE_ASSIST: 425 if (DEBUG) Log.d(TAG, "onHandleAssist: " + msg.obj); 426 onHandleAssist((Bundle) msg.obj); 427 break; 428 case MSG_HANDLE_SCREENSHOT: 429 if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj); 430 onHandleScreenshot((Bitmap) msg.obj); 431 break; 432 case MSG_SHOW: 433 args = (SomeArgs)msg.obj; 434 if (DEBUG) Log.d(TAG, "doShow: args=" + args.arg1 435 + " flags=" + msg.arg1 436 + " showCallback=" + args.arg2); 437 doShow((Bundle) args.arg1, msg.arg1, 438 (IVoiceInteractionSessionShowCallback) args.arg2); 439 break; 440 case MSG_HIDE: 441 if (DEBUG) Log.d(TAG, "doHide"); 442 doHide(); 443 break; 444 } 445 } 446 447 @Override 448 public void onBackPressed() { 449 VoiceInteractionSession.this.onBackPressed(); 450 } 451 } 452 453 final MyCallbacks mCallbacks = new MyCallbacks(); 454 455 /** 456 * Information about where interesting parts of the input method UI appear. 457 */ 458 public static final class Insets { 459 /** 460 * This is the part of the UI that is the main content. It is 461 * used to determine the basic space needed, to resize/pan the 462 * application behind. It is assumed that this inset does not 463 * change very much, since any change will cause a full resize/pan 464 * of the application behind. This value is relative to the top edge 465 * of the input method window. 466 */ 467 public final Rect contentInsets = new Rect(); 468 469 /** 470 * This is the region of the UI that is touchable. It is used when 471 * {@link #touchableInsets} is set to {@link #TOUCHABLE_INSETS_REGION}. 472 * The region should be specified relative to the origin of the window frame. 473 */ 474 public final Region touchableRegion = new Region(); 475 476 /** 477 * Option for {@link #touchableInsets}: the entire window frame 478 * can be touched. 479 */ 480 public static final int TOUCHABLE_INSETS_FRAME 481 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; 482 483 /** 484 * Option for {@link #touchableInsets}: the area inside of 485 * the content insets can be touched. 486 */ 487 public static final int TOUCHABLE_INSETS_CONTENT 488 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; 489 490 /** 491 * Option for {@link #touchableInsets}: the region specified by 492 * {@link #touchableRegion} can be touched. 493 */ 494 public static final int TOUCHABLE_INSETS_REGION 495 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; 496 497 /** 498 * Determine which area of the window is touchable by the user. May 499 * be one of: {@link #TOUCHABLE_INSETS_FRAME}, 500 * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_REGION}. 501 */ 502 public int touchableInsets; 503 } 504 505 final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = 506 new ViewTreeObserver.OnComputeInternalInsetsListener() { 507 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { 508 onComputeInsets(mTmpInsets); 509 info.contentInsets.set(mTmpInsets.contentInsets); 510 info.visibleInsets.set(mTmpInsets.contentInsets); 511 info.touchableRegion.set(mTmpInsets.touchableRegion); 512 info.setTouchableInsets(mTmpInsets.touchableInsets); 513 } 514 }; 515 516 public VoiceInteractionSession(Context context) { 517 this(context, new Handler()); 518 } 519 520 public VoiceInteractionSession(Context context, Handler handler) { 521 mContext = context; 522 mHandlerCaller = new HandlerCaller(context, handler.getLooper(), 523 mCallbacks, true); 524 } 525 526 public Context getContext() { 527 return mContext; 528 } 529 530 Request newRequest(IVoiceInteractorCallback callback) { 531 synchronized (this) { 532 Request req = new Request(callback, this); 533 mActiveRequests.put(req.mInterface.asBinder(), req); 534 return req; 535 } 536 } 537 538 Request removeRequest(IBinder reqInterface) { 539 synchronized (this) { 540 return mActiveRequests.remove(reqInterface); 541 } 542 } 543 544 void doCreate(IVoiceInteractionManagerService service, IBinder token, Bundle args, 545 int startFlags) { 546 mSystemService = service; 547 mToken = token; 548 onCreate(args, startFlags); 549 } 550 551 void doShow(Bundle args, int flags, final IVoiceInteractionSessionShowCallback showCallback) { 552 if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded 553 + " mWindowVisible=" + mWindowVisible); 554 555 if (mInShowWindow) { 556 Log.w(TAG, "Re-entrance in to showWindow"); 557 return; 558 } 559 560 try { 561 mInShowWindow = true; 562 if (!mWindowVisible) { 563 if (!mWindowAdded) { 564 mWindowAdded = true; 565 View v = onCreateContentView(); 566 if (v != null) { 567 setContentView(v); 568 } 569 } 570 } 571 onShow(args, flags); 572 if (!mWindowVisible) { 573 mWindowVisible = true; 574 mWindow.show(); 575 } 576 if (showCallback != null) { 577 mRootView.invalidate(); 578 mRootView.getViewTreeObserver().addOnPreDrawListener( 579 new ViewTreeObserver.OnPreDrawListener() { 580 @Override 581 public boolean onPreDraw() { 582 mRootView.getViewTreeObserver().removeOnPreDrawListener(this); 583 try { 584 showCallback.onShown(); 585 } catch (RemoteException e) { 586 Log.w(TAG, "Error calling onShown", e); 587 } 588 return true; 589 } 590 }); 591 } 592 } finally { 593 mWindowWasVisible = true; 594 mInShowWindow = false; 595 } 596 } 597 598 void doHide() { 599 if (mWindowVisible) { 600 mWindow.hide(); 601 mWindowVisible = false; 602 onHide(); 603 } 604 } 605 606 void doDestroy() { 607 onDestroy(); 608 if (mInitialized) { 609 mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( 610 mInsetsComputer); 611 if (mWindowAdded) { 612 mWindow.dismiss(); 613 mWindowAdded = false; 614 } 615 mInitialized = false; 616 } 617 } 618 619 void initViews() { 620 mInitialized = true; 621 622 mThemeAttrs = mContext.obtainStyledAttributes(android.R.styleable.VoiceInteractionSession); 623 mRootView = mInflater.inflate( 624 com.android.internal.R.layout.voice_interaction_session, null); 625 mRootView.setSystemUiVisibility( 626 View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 627 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 628 mWindow.setContentView(mRootView); 629 mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); 630 631 mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content); 632 } 633 634 public void show() { 635 try { 636 mSystemService.showSessionFromSession(mToken, null, 0); 637 } catch (RemoteException e) { 638 } 639 } 640 641 public void hide() { 642 try { 643 mSystemService.hideSessionFromSession(mToken); 644 } catch (RemoteException e) { 645 } 646 } 647 648 /** TODO: remove */ 649 public void showWindow() { 650 } 651 652 /** TODO: remove */ 653 public void hideWindow() { 654 } 655 656 /** 657 * You can call this to customize the theme used by your IME's window. 658 * This must be set before {@link #onCreate}, so you 659 * will typically call it in your constructor with the resource ID 660 * of your custom theme. 661 */ 662 public void setTheme(int theme) { 663 if (mWindow != null) { 664 throw new IllegalStateException("Must be called before onCreate()"); 665 } 666 mTheme = theme; 667 } 668 669 /** 670 * Ask that a new activity be started for voice interaction. This will create a 671 * new dedicated task in the activity manager for this voice interaction session; 672 * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} 673 * will be set for you to make it a new task. 674 * 675 * <p>The newly started activity will be displayed to the user in a special way, as 676 * a layer under the voice interaction UI.</p> 677 * 678 * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor} 679 * through which it can perform voice interactions through your session. These requests 680 * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands}, 681 * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}. 682 * 683 * <p>You will receive a call to {@link #onTaskStarted} when the task starts up 684 * and {@link #onTaskFinished} when the last activity has finished. 685 * 686 * @param intent The Intent to start this voice interaction. The given Intent will 687 * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since 688 * this is part of a voice interaction. 689 */ 690 public void startVoiceActivity(Intent intent) { 691 if (mToken == null) { 692 throw new IllegalStateException("Can't call before onCreate()"); 693 } 694 try { 695 intent.migrateExtraStreamToClipData(); 696 intent.prepareToLeaveProcess(); 697 int res = mSystemService.startVoiceActivity(mToken, intent, 698 intent.resolveType(mContext.getContentResolver())); 699 Instrumentation.checkStartActivityResult(res, intent); 700 } catch (RemoteException e) { 701 } 702 } 703 704 /** 705 * Set whether this session will keep the device awake while it is running a voice 706 * activity. By default, the system holds a wake lock for it while in this state, 707 * so that it can work even if the screen is off. Setting this to false removes that 708 * wake lock, allowing the CPU to go to sleep. This is typically used if the 709 * session decides it has been waiting too long for a response from the user and 710 * doesn't want to let this continue to drain the battery. 711 * 712 * <p>Passing false here will release the wake lock, and you can call later with 713 * true to re-acquire it. It will also be automatically re-acquired for you each 714 * time you start a new voice activity task -- that is when you call 715 * {@link #startVoiceActivity}.</p> 716 */ 717 public void setKeepAwake(boolean keepAwake) { 718 try { 719 mSystemService.setKeepAwake(mToken, keepAwake); 720 } catch (RemoteException e) { 721 } 722 } 723 724 /** 725 * Convenience for inflating views. 726 */ 727 public LayoutInflater getLayoutInflater() { 728 return mInflater; 729 } 730 731 /** 732 * Retrieve the window being used to show the session's UI. 733 */ 734 public Dialog getWindow() { 735 return mWindow; 736 } 737 738 /** 739 * Finish the session. 740 */ 741 public void finish() { 742 if (mToken == null) { 743 throw new IllegalStateException("Can't call before onCreate()"); 744 } 745 hideWindow(); 746 try { 747 mSystemService.finish(mToken); 748 } catch (RemoteException e) { 749 } 750 } 751 752 /** @hide */ 753 public void onCreate(Bundle args) { 754 mTheme = mTheme != 0 ? mTheme 755 : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession; 756 mInflater = (LayoutInflater)mContext.getSystemService( 757 Context.LAYOUT_INFLATER_SERVICE); 758 mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme, 759 mCallbacks, this, mDispatcherState, 760 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true); 761 mWindow.getWindow().addFlags( 762 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | 763 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); 764 initViews(); 765 mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT); 766 mWindow.setToken(mToken); 767 } 768 769 /** 770 * Initiatize a new session. The given args and showFlags are the initial values 771 * passed to {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}, 772 * if possible. Normally you should handle these in {@link #onShow}. 773 */ 774 public void onCreate(Bundle args, int showFlags) { 775 onCreate(args); 776 } 777 778 /** 779 * Called when the session UI is going to be shown. This is called after 780 * {@link #onCreateContentView} (if the session's content UI needed to be created) and 781 * immediately prior to the window being shown. This may be called while the window 782 * is already shown, if a show request has come in while it is shown, to allow you to 783 * update the UI to match the new show arguments. 784 * 785 * @param args The arguments that were supplied to 786 * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. 787 * @param showFlags The show flags originally provided to 788 * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. 789 */ 790 public void onShow(Bundle args, int showFlags) { 791 } 792 793 /** 794 * Called immediately after stopping to show the session UI. 795 */ 796 public void onHide() { 797 } 798 799 /** 800 * Last callback to the session as it is being finished. 801 */ 802 public void onDestroy() { 803 } 804 805 /** 806 * Hook in which to create the session's UI. 807 */ 808 public View onCreateContentView() { 809 return null; 810 } 811 812 public void setContentView(View view) { 813 mContentFrame.removeAllViews(); 814 mContentFrame.addView(view, new FrameLayout.LayoutParams( 815 ViewGroup.LayoutParams.MATCH_PARENT, 816 ViewGroup.LayoutParams.MATCH_PARENT)); 817 818 } 819 820 public void onHandleAssist(Bundle assistBundle) { 821 } 822 823 /** @hide */ 824 public void onHandleScreenshot(Bitmap screenshot) { 825 } 826 827 public boolean onKeyDown(int keyCode, KeyEvent event) { 828 return false; 829 } 830 831 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 832 return false; 833 } 834 835 public boolean onKeyUp(int keyCode, KeyEvent event) { 836 return false; 837 } 838 839 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { 840 return false; 841 } 842 843 /** 844 * Called when the user presses the back button while focus is in the session UI. Note 845 * that this will only happen if the session UI has requested input focus in its window; 846 * otherwise, the back key will go to whatever window has focus and do whatever behavior 847 * it normally has there. 848 */ 849 public void onBackPressed() { 850 hide(); 851 } 852 853 /** 854 * Sessions automatically watch for requests that all system UI be closed (such as when 855 * the user presses HOME), which will appear here. The default implementation always 856 * calls {@link #finish}. 857 */ 858 public void onCloseSystemDialogs() { 859 hide(); 860 } 861 862 @Override 863 public void onConfigurationChanged(Configuration newConfig) { 864 } 865 866 @Override 867 public void onLowMemory() { 868 } 869 870 @Override 871 public void onTrimMemory(int level) { 872 } 873 874 /** 875 * Compute the interesting insets into your UI. The default implementation 876 * sets {@link Insets#contentInsets outInsets.contentInsets.top} to the height 877 * of the window, meaning it should not adjust content underneath. The default touchable 878 * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}, meaning it consumes all touch 879 * events within its window frame. 880 * 881 * @param outInsets Fill in with the current UI insets. 882 */ 883 public void onComputeInsets(Insets outInsets) { 884 outInsets.contentInsets.left = 0; 885 outInsets.contentInsets.bottom = 0; 886 outInsets.contentInsets.right = 0; 887 View decor = getWindow().getWindow().getDecorView(); 888 outInsets.contentInsets.top = decor.getHeight(); 889 outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME; 890 outInsets.touchableRegion.setEmpty(); 891 } 892 893 /** 894 * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)} 895 * has actually started. 896 * 897 * @param intent The original {@link Intent} supplied to 898 * {@link #startVoiceActivity(android.content.Intent)}. 899 * @param taskId Unique ID of the now running task. 900 */ 901 public void onTaskStarted(Intent intent, int taskId) { 902 } 903 904 /** 905 * Called when the last activity of a task initiated by 906 * {@link #startVoiceActivity(android.content.Intent)} has finished. The default 907 * implementation calls {@link #finish()} on the assumption that this represents 908 * the completion of a voice action. You can override the implementation if you would 909 * like a different behavior. 910 * 911 * @param intent The original {@link Intent} supplied to 912 * {@link #startVoiceActivity(android.content.Intent)}. 913 * @param taskId Unique ID of the finished task. 914 */ 915 public void onTaskFinished(Intent intent, int taskId) { 916 hide(); 917 } 918 919 /** 920 * Request to query for what extended commands the session supports. 921 * 922 * @param caller Who is making the request. 923 * @param commands An array of commands that are being queried. 924 * @return Return an array of booleans indicating which of each entry in the 925 * command array is supported. A true entry in the array indicates the command 926 * is supported; false indicates it is not. The default implementation returns 927 * an array of all false entries. 928 */ 929 public boolean[] onGetSupportedCommands(Caller caller, String[] commands) { 930 return new boolean[commands.length]; 931 } 932 933 /** 934 * Request to confirm with the user before proceeding with an unrecoverable operation, 935 * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest 936 * VoiceInteractor.ConfirmationRequest}. 937 * 938 * @param caller Who is making the request. 939 * @param request The active request. 940 * @param prompt The prompt informing the user of what will happen, as per 941 * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}. 942 * @param extras Any additional information, as per 943 * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}. 944 */ 945 public abstract void onConfirm(Caller caller, Request request, CharSequence prompt, 946 Bundle extras); 947 948 /** 949 * Request for the user to pick one of N options, corresponding to a 950 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 951 * 952 * @param caller Who is making the request. 953 * @param request The active request. 954 * @param prompt The prompt informing the user of what they are picking, as per 955 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 956 * @param options The set of options the user is picking from, as per 957 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 958 * @param extras Any additional information, as per 959 * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. 960 */ 961 public abstract void onPickOption(Caller caller, Request request, CharSequence prompt, 962 VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras); 963 964 /** 965 * Request to complete the voice interaction session because the voice activity successfully 966 * completed its interaction using voice. Corresponds to 967 * {@link android.app.VoiceInteractor.CompleteVoiceRequest 968 * VoiceInteractor.CompleteVoiceRequest}. The default implementation just sends an empty 969 * confirmation back to allow the activity to exit. 970 * 971 * @param caller Who is making the request. 972 * @param request The active request. 973 * @param message The message informing the user of the problem, as per 974 * {@link android.app.VoiceInteractor.CompleteVoiceRequest 975 * VoiceInteractor.CompleteVoiceRequest}. 976 * @param extras Any additional information, as per 977 * {@link android.app.VoiceInteractor.CompleteVoiceRequest 978 * VoiceInteractor.CompleteVoiceRequest}. 979 */ 980 public void onCompleteVoice(Caller caller, Request request, CharSequence message, 981 Bundle extras) { 982 request.sendCompleteVoiceResult(null); 983 } 984 985 /** 986 * Request to abort the voice interaction session because the voice activity can not 987 * complete its interaction using voice. Corresponds to 988 * {@link android.app.VoiceInteractor.AbortVoiceRequest 989 * VoiceInteractor.AbortVoiceRequest}. The default implementation just sends an empty 990 * confirmation back to allow the activity to exit. 991 * 992 * @param caller Who is making the request. 993 * @param request The active request. 994 * @param message The message informing the user of the problem, as per 995 * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. 996 * @param extras Any additional information, as per 997 * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. 998 */ 999 public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) { 1000 request.sendAbortVoiceResult(null); 1001 } 1002 1003 /** 1004 * Process an arbitrary extended command from the caller, 1005 * corresponding to a {@link android.app.VoiceInteractor.CommandRequest 1006 * VoiceInteractor.CommandRequest}. 1007 * 1008 * @param caller Who is making the request. 1009 * @param request The active request. 1010 * @param command The command that is being executed, as per 1011 * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. 1012 * @param extras Any additional information, as per 1013 * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. 1014 */ 1015 public abstract void onCommand(Caller caller, Request request, String command, Bundle extras); 1016 1017 /** 1018 * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request} 1019 * that was previously delivered to {@link #onConfirm} or {@link #onCommand}. 1020 * 1021 * @param request The request that is being canceled. 1022 */ 1023 public abstract void onCancel(Request request); 1024} 1025