InputMethodManager.java revision f1e484acb594a726fb57ad0ae4cfe902c7f35858
1/* 2 * Copyright (C) 2007-2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package android.view.inputmethod; 18 19import android.content.Context; 20import android.graphics.Rect; 21import android.os.Bundle; 22import android.os.Handler; 23import android.os.IBinder; 24import android.os.Looper; 25import android.os.Message; 26import android.os.RemoteException; 27import android.os.ServiceManager; 28import android.util.Log; 29import android.view.KeyEvent; 30import android.view.MotionEvent; 31import android.view.View; 32import android.view.ViewRoot; 33 34import com.android.internal.view.IInputConnectionWrapper; 35import com.android.internal.view.IInputContext; 36import com.android.internal.view.IInputMethodCallback; 37import com.android.internal.view.IInputMethodClient; 38import com.android.internal.view.IInputMethodManager; 39import com.android.internal.view.IInputMethodSession; 40import com.android.internal.view.InputBindResult; 41 42import java.util.List; 43 44/** 45 * Central system API to the overall input method framework (IMF) architecture, 46 * which arbitrates interaction between applications and the current input method. 47 * You can retrieve an instance of this interface with 48 * {@link Context#getSystemService(String) Context.getSystemService()}. 49 * 50 * <p>Topics covered here: 51 * <ol> 52 * <li><a href="#ArchitectureOverview">Architecture Overview</a> 53 * </ol> 54 * 55 * <a name="ArchitectureOverview"></a> 56 * <h3>Architecture Overview</h3> 57 * 58 * <p>There are three primary parties involved in the input method 59 * framework (IMF) architecture:</p> 60 * 61 * <ul> 62 * <li> The <strong>input method manager</strong> as expressed by this class 63 * is the central point of the system that manages interaction between all 64 * other parts. It is expressed as the client-side API here which exists 65 * in each application context and communicates with a global system service 66 * that manages the interaction across all processes. 67 * <li> An <strong>input method (IME)</strong> implements a particular 68 * interaction model allowing the user to generate text. The system binds 69 * to the current input method that is use, causing it to be created and run, 70 * and tells it when to hide and show its UI. Only one IME is running at a time. 71 * <li> Multiple <strong>client applications</strong> arbitrate with the input 72 * method manager for input focus and control over the state of the IME. Only 73 * one such client is ever active (working with the IME) at a time. 74 * </ul> 75 * 76 * 77 * <a name="Applications"></a> 78 * <h3>Applications</h3> 79 * 80 * <p>In most cases, applications that are using the standard 81 * {@link android.widget.TextView} or its subclasses will have little they need 82 * to do to work well with soft input methods. The main things you need to 83 * be aware of are:</p> 84 * 85 * <ul> 86 * <li> Properly set the {@link android.R.attr#inputType} if your editable 87 * text views, so that the input method will have enough context to help the 88 * user in entering text into them. 89 * <li> Deal well with losing screen space when the input method is 90 * displayed. Ideally an application should handle its window being resized 91 * smaller, but it can rely on the system performing panning of the window 92 * if needed. You should set the {@link android.R.attr#windowSoftInputMode} 93 * attribute on your activity or the corresponding values on windows you 94 * create to help the system determine whether to pan or resize (it will 95 * try to determine this automatically but may get it wrong). 96 * <li> You can also control the preferred soft input state (open, closed, etc) 97 * for your window using the same {@link android.R.attr#windowSoftInputMode} 98 * attribute. 99 * </ul> 100 * 101 * <p>More finer-grained control is available through the APIs here to directly 102 * interact with the IMF and its IME -- either showing or hiding the input 103 * area, letting the user pick an input method, etc.</p> 104 * 105 * <p>For the rare people amongst us writing their own text editors, you 106 * will need to implement {@link android.view.View#onCreateInputConnection} 107 * to return a new instance of your own {@link InputConnection} interface 108 * allowing the IME to interact with your editor.</p> 109 * 110 * 111 * <a name="InputMethods"></a> 112 * <h3>Input Methods</h3> 113 * 114 * <p>An input method (IME) is implemented 115 * as a {@link android.app.Service}, typically deriving from 116 * {@link android.inputmethodservice.InputMethodService}. It must provide 117 * the core {@link InputMethod} interface, though this is normally handled by 118 * {@link android.inputmethodservice.InputMethodService} and implementors will 119 * only need to deal with the higher-level API there.</p> 120 * 121 * See the {@link android.inputmethodservice.InputMethodService} class for 122 * more information on implementing IMEs. 123 * 124 * 125 * <a name="Security"></a> 126 * <h3>Security</h3> 127 * 128 * <p>There are a lot of security issues associated with input methods, 129 * since they essentially have freedom to completely drive the UI and monitor 130 * everything the user enters. The Android input method framework also allows 131 * arbitrary third party IMEs, so care must be taken to restrict their 132 * selection and interactions.</p> 133 * 134 * <p>Here are some key points about the security architecture behind the 135 * IMF:</p> 136 * 137 * <ul> 138 * <li> <p>Only the system is allowed to directly access an IME's 139 * {@link InputMethod} interface, via the 140 * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission. This is 141 * enforced in the system by not binding to an input method service that does 142 * not require this permission, so the system can guarantee no other untrusted 143 * clients are accessing the current input method outside of its control.</p> 144 * 145 * <li> <p>There may be many client processes of the IMF, but only one may 146 * be active at a time. The inactive clients can not interact with key 147 * parts of the IMF through the mechanisms described below.</p> 148 * 149 * <li> <p>Clients of an input method are only given access to its 150 * {@link InputMethodSession} interface. One instance of this interface is 151 * created for each client, and only calls from the session associated with 152 * the active client will be processed by the current IME. This is enforced 153 * by {@link android.inputmethodservice.AbstractInputMethodService} for normal 154 * IMEs, but must be explicitly handled by an IME that is customizing the 155 * raw {@link InputMethodSession} implementation.</p> 156 * 157 * <li> <p>Only the active client's {@link InputConnection} will accept 158 * operations. The IMF tells each client process whether it is active, and 159 * the framework enforces that in inactive processes calls on to the current 160 * InputConnection will be ignored. This ensures that the current IME can 161 * only deliver events and text edits to the UI that the user sees as 162 * being in focus.</p> 163 * 164 * <li> <p>An IME can never interact with an {@link InputConnection} while 165 * the screen is off. This is enforced by making all clients inactive while 166 * the screen is off, and prevents bad IMEs from driving the UI when the user 167 * can not be aware of its behavior.</p> 168 * 169 * <li> <p>A client application can ask that the system let the user pick a 170 * new IME, but can not programmatically switch to one itself. This avoids 171 * malicious applications from switching the user to their own IME, which 172 * remains running when the user navigates away to another application. An 173 * IME, on the other hand, <em>is</em> allowed to programmatically switch 174 * the system to another IME, since it already has full control of user 175 * input.</p> 176 * 177 * <li> <p>The user must explicitly enable a new IME in settings before 178 * they can switch to it, to confirm with the system that they know about it 179 * and want to make it available for use.</p> 180 * </ul> 181 */ 182public final class InputMethodManager { 183 static final boolean DEBUG = false; 184 static final String TAG = "InputMethodManager"; 185 186 /** 187 * The package name of the build-in input method. 188 * {@hide} 189 */ 190 public static final String BUILDIN_INPUTMETHOD_PACKAGE = "android.text.inputmethod"; 191 192 static final Object mInstanceSync = new Object(); 193 static InputMethodManager mInstance; 194 195 final IInputMethodManager mService; 196 final Looper mMainLooper; 197 198 // For scheduling work on the main thread. This also serves as our 199 // global lock. 200 final H mH; 201 202 // The currently active input connection. 203 final MutableInputConnectionWrapper mInputConnectionWrapper; 204 final IInputContext mIInputContext; 205 206 /** 207 * True if this input method client is active, initially false. 208 */ 209 boolean mActive = false; 210 211 /** 212 * The current base input connection, used when mActive is true. 213 */ 214 InputConnection mCurrentInputConnection; 215 216 // ----------------------------------------------------------- 217 218 /** 219 * This is the view that should currently be served by an input method, 220 * regardless of the state of setting that up. 221 */ 222 View mServedView; 223 /** 224 * For evaluating the state after a focus change, this is the view that 225 * had focus. 226 */ 227 View mLastServedView; 228 /** 229 * This is set when we are in the process of connecting, to determine 230 * when we have actually finished. 231 */ 232 boolean mServedConnecting; 233 /** 234 * This is non-null when we have connected the served view; it holds 235 * the attributes that were last retrieved from the served view and given 236 * to the input connection. 237 */ 238 EditorInfo mCurrentTextBoxAttribute; 239 /** 240 * The InputConnection that was last retrieved from the served view. 241 */ 242 InputConnection mServedInputConnection; 243 /** 244 * The completions that were last provided by the served view. 245 */ 246 CompletionInfo[] mCompletions; 247 248 // Cursor position on the screen. 249 Rect mTmpCursorRect = new Rect(); 250 Rect mCursorRect = new Rect(); 251 int mCursorSelStart; 252 int mCursorSelEnd; 253 int mCursorCandStart; 254 int mCursorCandEnd; 255 256 // ----------------------------------------------------------- 257 258 /** 259 * Sequence number of this binding, as returned by the server. 260 */ 261 int mBindSequence = -1; 262 /** 263 * ID of the method we are bound to. 264 */ 265 String mCurId; 266 /** 267 * The actual instance of the method to make calls on it. 268 */ 269 IInputMethodSession mCurMethod; 270 271 // ----------------------------------------------------------- 272 273 static final int MSG_CHECK_FOCUS = 1; 274 275 class H extends Handler { 276 H(Looper looper) { 277 super(looper); 278 } 279 280 @Override 281 public void handleMessage(Message msg) { 282 switch (msg.what) { 283 case MSG_CHECK_FOCUS: 284 checkFocus(); 285 return; 286 } 287 } 288 } 289 290 static class NoOpInputConnection implements InputConnection { 291 292 public boolean clearMetaKeyStates(int states) { 293 return false; 294 } 295 296 public boolean beginBatchEdit() { 297 return false; 298 } 299 300 public boolean endBatchEdit() { 301 return false; 302 } 303 304 public boolean commitCompletion(CompletionInfo text) { 305 return false; 306 } 307 308 public boolean commitText(CharSequence text, int newCursorPosition) { 309 return false; 310 } 311 312 public boolean deleteSurroundingText(int leftLength, int rightLength) { 313 return false; 314 } 315 316 public int getCursorCapsMode(int reqModes) { 317 return 0; 318 } 319 320 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 321 return null; 322 } 323 324 public CharSequence getTextAfterCursor(int n) { 325 return null; 326 } 327 328 public CharSequence getTextBeforeCursor(int n) { 329 return null; 330 } 331 332 public boolean hideStatusIcon() { 333 return false; 334 } 335 336 public boolean performPrivateCommand(String action, Bundle data) { 337 return false; 338 } 339 340 public boolean sendKeyEvent(KeyEvent event) { 341 return false; 342 } 343 344 public boolean setComposingText(CharSequence text, int newCursorPosition) { 345 return false; 346 } 347 348 public boolean finishComposingText() { 349 return false; 350 } 351 352 public boolean showStatusIcon(String packageName, int resId) { 353 return false; 354 } 355 } 356 357 final NoOpInputConnection mNoOpInputConnection = new NoOpInputConnection(); 358 359 final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { 360 public void setUsingInputMethod(boolean state) { 361 362 } 363 364 public void onBindMethod(InputBindResult res) { 365 synchronized (mH) { 366 if (mBindSequence < 0 || mBindSequence != res.sequence) { 367 Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence 368 + ", given seq=" + res.sequence); 369 return; 370 } 371 372 mCurMethod = res.method; 373 mCurId = res.id; 374 mBindSequence = res.sequence; 375 } 376 startInputInner(); 377 } 378 379 public void onUnbindMethod(int sequence) { 380 synchronized (mH) { 381 if (mBindSequence == sequence) { 382 if (false) { 383 // XXX the server has already unbound! 384 if (mCurMethod != null && mCurrentTextBoxAttribute != null) { 385 try { 386 mCurMethod.finishInput(); 387 } catch (RemoteException e) { 388 Log.w(TAG, "IME died: " + mCurId, e); 389 } 390 } 391 } 392 clearBindingLocked(); 393 394 // If we were actively using the last input method, then 395 // we would like to re-connect to the next input method. 396 if (mServedView != null && mServedView.isFocused()) { 397 mServedConnecting = true; 398 } 399 } 400 startInputInner(); 401 } 402 } 403 404 public void setActive(boolean active) { 405 mActive = active; 406 mInputConnectionWrapper.setBaseInputConnection(active 407 ? mCurrentInputConnection : mNoOpInputConnection); 408 } 409 }; 410 411 final InputConnection mDummyInputConnection = new BaseInputConnection(this) { 412 public boolean beginBatchEdit() { 413 return false; 414 } 415 public boolean endBatchEdit() { 416 return false; 417 } 418 public boolean commitText(CharSequence text, int newCursorPosition) { 419 return false; 420 } 421 public boolean commitCompletion(CompletionInfo text) { 422 return false; 423 } 424 public boolean deleteSurroundingText(int leftLength, int rightLength) { 425 return false; 426 } 427 public ExtractedText getExtractedText(ExtractedTextRequest request, 428 int flags) { 429 return null; 430 } 431 public CharSequence getTextAfterCursor(int n) { 432 return null; 433 } 434 public CharSequence getTextBeforeCursor(int n) { 435 return null; 436 } 437 public int getCursorCapsMode(int reqModes) { 438 return 0; 439 } 440 public boolean clearMetaKeyStates(int states) { 441 return false; 442 } 443 public boolean performPrivateCommand(String action, Bundle data) { 444 return false; 445 } 446 public boolean setComposingText(CharSequence text, int newCursorPosition) { 447 return false; 448 } 449 public boolean finishComposingText() { 450 return false; 451 } 452 }; 453 454 InputMethodManager(IInputMethodManager service, Looper looper) { 455 mService = service; 456 mMainLooper = looper; 457 mH = new H(looper); 458 mInputConnectionWrapper = new MutableInputConnectionWrapper(mNoOpInputConnection); 459 mIInputContext = new IInputConnectionWrapper(looper, 460 mInputConnectionWrapper); 461 setCurrentInputConnection(mDummyInputConnection); 462 463 if (mInstance == null) { 464 mInstance = this; 465 } 466 } 467 468 /** 469 * Retrieve the global InputMethodManager instance, creating it if it 470 * doesn't already exist. 471 * @hide 472 */ 473 static public InputMethodManager getInstance(Context context) { 474 synchronized (mInstanceSync) { 475 if (mInstance != null) { 476 return mInstance; 477 } 478 IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); 479 IInputMethodManager service = IInputMethodManager.Stub.asInterface(b); 480 mInstance = new InputMethodManager(service, context.getMainLooper()); 481 } 482 return mInstance; 483 } 484 485 /** 486 * Private optimization: retrieve the global InputMethodManager instance, 487 * if it exists. 488 * @hide 489 */ 490 static public InputMethodManager peekInstance() { 491 return mInstance; 492 } 493 494 /** @hide */ 495 public IInputMethodClient getClient() { 496 return mClient; 497 } 498 499 /** @hide */ 500 public IInputContext getInputContext() { 501 return mIInputContext; 502 } 503 504 public List<InputMethodInfo> getInputMethodList() { 505 try { 506 return mService.getInputMethodList(); 507 } catch (RemoteException e) { 508 throw new RuntimeException(e); 509 } 510 } 511 512 public List<InputMethodInfo> getEnabledInputMethodList() { 513 try { 514 return mService.getEnabledInputMethodList(); 515 } catch (RemoteException e) { 516 throw new RuntimeException(e); 517 } 518 } 519 520 public void updateStatusIcon(int iconId, String iconPackage) { 521 try { 522 mService.updateStatusIcon(iconId, iconPackage); 523 } catch (RemoteException e) { 524 throw new RuntimeException(e); 525 } 526 } 527 528 /** 529 * Return true if the given view is the currently active view for the 530 * input method. 531 */ 532 public boolean isActive(View view) { 533 synchronized (mH) { 534 return mServedView == view && mCurrentTextBoxAttribute != null; 535 } 536 } 537 538 /** 539 * Return true if any view is currently active in the input method. 540 */ 541 public boolean isActive() { 542 synchronized (mH) { 543 return mServedView != null && mCurrentTextBoxAttribute != null; 544 } 545 } 546 547 /** 548 * Return true if the currently served view is accepting full text edits. 549 * If false, it has no input connection, so can only handle raw key events. 550 */ 551 public boolean isAcceptingText() { 552 return mServedInputConnection != null; 553 } 554 555 /** 556 * Reset all of the state associated with being bound to an input method. 557 */ 558 void clearBindingLocked() { 559 clearConnectionLocked(); 560 mBindSequence = -1; 561 mCurId = null; 562 mCurMethod = null; 563 } 564 565 /** 566 * Record the desired input connection, but only set it if mActive is true. 567 */ 568 void setCurrentInputConnection(InputConnection connection) { 569 mCurrentInputConnection = connection; 570 mInputConnectionWrapper.setBaseInputConnection(mActive 571 ? connection : mNoOpInputConnection); 572 } 573 574 /** 575 * Reset all of the state associated with a served view being connected 576 * to an input method 577 */ 578 void clearConnectionLocked() { 579 mCurrentTextBoxAttribute = null; 580 mServedInputConnection = null; 581 setCurrentInputConnection(mDummyInputConnection); 582 } 583 584 /** 585 * Disconnect any existing input connection, clearing the served view. 586 */ 587 void finishInputLocked() { 588 if (mServedView != null) { 589 if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); 590 updateStatusIcon(0, null); 591 592 if (mCurrentTextBoxAttribute != null) { 593 try { 594 mService.finishInput(mClient); 595 } catch (RemoteException e) { 596 } 597 } 598 599 if (mServedInputConnection != null) { 600 // We need to tell the previously served view that it is no 601 // longer the input target, so it can reset its state. Schedule 602 // this call on its window's Handler so it will be on the correct 603 // thread and outside of our lock. 604 Handler vh = mServedView.getHandler(); 605 if (vh != null) { 606 // This will result in a call to reportFinishInputConnection() 607 // below. 608 vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION, 609 mServedInputConnection)); 610 } 611 } 612 613 mServedView = null; 614 mCompletions = null; 615 mServedConnecting = false; 616 clearConnectionLocked(); 617 } 618 } 619 620 /** 621 * Called from the FINISH_INPUT_CONNECTION message above. 622 * @hide 623 */ 624 public void reportFinishInputConnection(InputConnection ic) { 625 if (mServedInputConnection != ic) { 626 ic.finishComposingText(); 627 } 628 } 629 630 public void displayCompletions(View view, CompletionInfo[] completions) { 631 synchronized (mH) { 632 if (mServedView != view) { 633 return; 634 } 635 636 mCompletions = completions; 637 if (mCurMethod != null) { 638 try { 639 mCurMethod.displayCompletions(mCompletions); 640 } catch (RemoteException e) { 641 } 642 } 643 } 644 } 645 646 public void updateExtractedText(View view, int token, ExtractedText text) { 647 synchronized (mH) { 648 if (mServedView != view) { 649 return; 650 } 651 652 if (mCurMethod != null) { 653 try { 654 mCurMethod.updateExtractedText(token, text); 655 } catch (RemoteException e) { 656 } 657 } 658 } 659 } 660 661 /** 662 * Flag for {@link #showSoftInput} to indicate that the this is an implicit 663 * request to show the input window, not as the result of a direct request 664 * by the user. The window may not be shown in this case. 665 */ 666 public static final int SHOW_IMPLICIT = 0x0001; 667 668 /** 669 * Explicitly request that the current input method's soft input area be 670 * shown to the user, if needed. Call this if the user interacts with 671 * your view in such a way that they have expressed they would like to 672 * start performing input into it. 673 * 674 * @param view The currently focused view, which would like to receive 675 * soft keyboard input. 676 * @param flags Provides additional operating flags. Currently may be 677 * 0 or have the {@link #SHOW_IMPLICIT} bit set. 678 */ 679 public void showSoftInput(View view, int flags) { 680 synchronized (mH) { 681 if (mServedView != view) { 682 return; 683 } 684 685 try { 686 mService.showSoftInput(mClient, flags); 687 } catch (RemoteException e) { 688 } 689 } 690 } 691 692 /** 693 * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft 694 * input window should only be hidden if it was not explicitly shown 695 * by the user. 696 */ 697 public static final int HIDE_IMPLICIT_ONLY = 0x0001; 698 699 /** 700 * Request to hide the soft input window from the context of the window 701 * that is currently accepting input. This should be called as a result 702 * of the user doing some actually than fairly explicitly requests to 703 * have the input window hidden. 704 * 705 * @param windowToken The token of the window that is making the request, 706 * as returned by {@link View#getWindowToken() View.getWindowToken()}. 707 * @param flags Provides additional operating flags. Currently may be 708 * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. 709 */ 710 public void hideSoftInputFromWindow(IBinder windowToken, int flags) { 711 synchronized (mH) { 712 if (mServedView == null || mServedView.getWindowToken() != windowToken) { 713 return; 714 } 715 716 try { 717 mService.hideSoftInput(mClient, flags); 718 } catch (RemoteException e) { 719 } 720 } 721 } 722 723 /** 724 * If the input method is currently connected to the given view, 725 * restart it with its new contents. You should call this when the text 726 * within your view changes outside of the normal input method or key 727 * input flow, such as when an application calls TextView.setText(). 728 * 729 * @param view The view whose text has changed. 730 */ 731 public void restartInput(View view) { 732 synchronized (mH) { 733 if (mServedView != view) { 734 return; 735 } 736 737 mServedConnecting = true; 738 } 739 740 startInputInner(); 741 } 742 743 void startInputInner() { 744 final View view; 745 synchronized (mH) { 746 view = mServedView; 747 748 // Make sure we have a window token for the served view. 749 if (DEBUG) Log.v(TAG, "Starting input: view=" + view); 750 if (view == null) { 751 if (DEBUG) Log.v(TAG, "ABORT input: no served view!"); 752 return; 753 } 754 } 755 756 // Now we need to get an input connection from the served view. 757 // This is complicated in a couple ways: we can't be holding our lock 758 // when calling out to the view, and we need to make sure we call into 759 // the view on the same thread that is driving its view hierarchy. 760 Handler vh = view.getHandler(); 761 if (vh == null) { 762 // If the view doesn't have a handler, something has changed out 763 // from under us, so just bail. 764 if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!"); 765 return; 766 } 767 if (vh.getLooper() != Looper.myLooper()) { 768 // The view is running on a different thread than our own, so 769 // we need to reschedule our work for over there. 770 if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); 771 vh.post(new Runnable() { 772 public void run() { 773 startInputInner(); 774 } 775 }); 776 } 777 778 // Okay we are now ready to call into the served view and have it 779 // do its stuff. 780 // Life is good: let's hook everything up! 781 EditorInfo tba = new EditorInfo(); 782 InputConnection ic = view.onCreateInputConnection(tba); 783 if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); 784 785 synchronized (mH) { 786 // Now that we are locked again, validate that our state hasn't 787 // changed. 788 if (mServedView != view || !mServedConnecting) { 789 // Something else happened, so abort. 790 if (DEBUG) Log.v(TAG, 791 "Starting input: finished by someone else (view=" 792 + mServedView + " conn=" + mServedConnecting + ")"); 793 return; 794 } 795 796 // If we already have a text box, then this view is already 797 // connected so we want to restart it. 798 final boolean initial = mCurrentTextBoxAttribute == null; 799 800 // Hook 'em up and let 'er rip. 801 mCurrentTextBoxAttribute = tba; 802 mServedConnecting = false; 803 mServedInputConnection = ic; 804 if (ic != null) { 805 mCursorSelStart = tba.initialSelStart; 806 mCursorSelEnd = tba.initialSelEnd; 807 mCursorCandStart = -1; 808 mCursorCandEnd = -1; 809 mCursorRect.setEmpty(); 810 setCurrentInputConnection(ic); 811 } else { 812 setCurrentInputConnection(mDummyInputConnection); 813 } 814 815 try { 816 if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic=" 817 + ic + " tba=" + tba + " initial=" + initial); 818 InputBindResult res = mService.startInput(mClient, tba, initial, 819 mCurMethod == null); 820 if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); 821 if (res != null) { 822 if (res.id != null) { 823 mBindSequence = res.sequence; 824 mCurMethod = res.method; 825 } else { 826 // This means there is no input method available. 827 if (DEBUG) Log.v(TAG, "ABORT input: no input method!"); 828 return; 829 } 830 } 831 if (mCurMethod != null && mCompletions != null) { 832 try { 833 mCurMethod.displayCompletions(mCompletions); 834 } catch (RemoteException e) { 835 } 836 } 837 } catch (RemoteException e) { 838 Log.w(TAG, "IME died: " + mCurId, e); 839 } 840 } 841 } 842 843 /** 844 * When the focused window is dismissed, this method is called to finish the 845 * input method started before. 846 * @hide 847 */ 848 public void windowDismissed(IBinder appWindowToken) { 849 synchronized (mH) { 850 if (mServedView != null && 851 mServedView.getWindowToken() == appWindowToken) { 852 finishInputLocked(); 853 } 854 } 855 } 856 857 /** 858 * Call this when a view receives focus. 859 * @hide 860 */ 861 public void focusIn(View view) { 862 synchronized (mH) { 863 if (DEBUG) Log.v(TAG, "focusIn: " + view); 864 // Okay we have a new view that is being served. 865 if (mServedView != view) { 866 mCurrentTextBoxAttribute = null; 867 } 868 mServedView = view; 869 mCompletions = null; 870 mServedConnecting = true; 871 } 872 873 startInputInner(); 874 } 875 876 /** 877 * Call this when a view loses focus. 878 * @hide 879 */ 880 public void focusOut(View view) { 881 InputConnection ic = null; 882 synchronized (mH) { 883 if (DEBUG) Log.v(TAG, "focusOut: " + view 884 + " mServedView=" + mServedView 885 + " winFocus=" + view.hasWindowFocus()); 886 if (mServedView == view) { 887 ic = mServedInputConnection; 888 if (view.hasWindowFocus()) { 889 mLastServedView = view; 890 mH.removeMessages(MSG_CHECK_FOCUS); 891 mH.sendEmptyMessage(MSG_CHECK_FOCUS); 892 } 893 } 894 } 895 896 if (ic != null) { 897 ic.finishComposingText(); 898 } 899 } 900 901 void checkFocus() { 902 synchronized (mH) { 903 if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView 904 + " last=" + mLastServedView); 905 if (mServedView == mLastServedView) { 906 finishInputLocked(); 907 // In this case, we used to have a focused view on the window, 908 // but no longer do. We should make sure the input method is 909 // no longer shown, since it serves no purpose. 910 closeCurrentInput(); 911 } 912 mLastServedView = null; 913 } 914 } 915 916 void closeCurrentInput() { 917 try { 918 mService.hideSoftInput(mClient, 0); 919 } catch (RemoteException e) { 920 } 921 } 922 923 /** 924 * Called by ViewRoot the first time it gets window focus. 925 * @hide 926 */ 927 public void onWindowFocus(View focusedView, int softInputMode, 928 boolean first, int windowFlags) { 929 synchronized (mH) { 930 if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView 931 + " softInputMode=" + softInputMode 932 + " first=" + first + " flags=#" 933 + Integer.toHexString(windowFlags)); 934 try { 935 final boolean isTextEditor = focusedView != null && 936 focusedView.onCheckIsTextEditor(); 937 mService.windowGainedFocus(mClient, focusedView != null, 938 isTextEditor, softInputMode, first, windowFlags); 939 } catch (RemoteException e) { 940 } 941 } 942 } 943 944 /** 945 * Report the current selection range. 946 */ 947 public void updateSelection(View view, int selStart, int selEnd, 948 int candidatesStart, int candidatesEnd) { 949 synchronized (mH) { 950 if (mServedView != view || mCurrentTextBoxAttribute == null 951 || mCurMethod == null) { 952 return; 953 } 954 955 if (mCursorSelStart != selStart || mCursorSelEnd != selEnd 956 || mCursorCandStart != candidatesStart 957 || mCursorCandEnd != candidatesEnd) { 958 if (DEBUG) Log.d(TAG, "updateSelection"); 959 960 try { 961 if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod); 962 mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd, 963 selStart, selEnd, candidatesStart, candidatesEnd); 964 mCursorSelStart = selStart; 965 mCursorSelEnd = selEnd; 966 mCursorCandStart = candidatesStart; 967 mCursorCandEnd = candidatesEnd; 968 } catch (RemoteException e) { 969 Log.w(TAG, "IME died: " + mCurId, e); 970 } 971 } 972 } 973 } 974 975 /** 976 * Returns true if the current input method wants to watch the location 977 * of the input editor's cursor in its window. 978 */ 979 public boolean isWatchingCursor(View view) { 980 return false; 981 } 982 983 /** 984 * Report the current cursor location in its window. 985 */ 986 public void updateCursor(View view, int left, int top, int right, int bottom) { 987 synchronized (mH) { 988 if (mServedView != view || mCurrentTextBoxAttribute == null 989 || mCurMethod == null) { 990 return; 991 } 992 993 mTmpCursorRect.set(left, top, right, bottom); 994 if (!mCursorRect.equals(mTmpCursorRect)) { 995 if (DEBUG) Log.d(TAG, "updateCursor"); 996 997 try { 998 if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod); 999 mCurMethod.updateCursor(mTmpCursorRect); 1000 mCursorRect.set(mTmpCursorRect); 1001 } catch (RemoteException e) { 1002 Log.w(TAG, "IME died: " + mCurId, e); 1003 } 1004 } 1005 } 1006 } 1007 1008 /** 1009 * Force switch to a new input method component. This can only be called 1010 * from the currently active input method, as validated by the given token. 1011 * @param token Supplies the identifying token given to an input method 1012 * when it was started, which allows it to perform this operation on 1013 * itself. 1014 * @param id The unique identifier for the new input method to be switched to. 1015 */ 1016 public void setInputMethod(IBinder token, String id) { 1017 try { 1018 mService.setInputMethod(token, id); 1019 } catch (RemoteException e) { 1020 throw new RuntimeException(e); 1021 } 1022 } 1023 1024 /** 1025 * Close/hide the input method's soft input area, so the user no longer 1026 * sees it or can interact with it. This can only be called 1027 * from the currently active input method, as validated by the given token. 1028 * 1029 * @param token Supplies the identifying token given to an input method 1030 * when it was started, which allows it to perform this operation on 1031 * itself. 1032 * @param flags Provides additional operating flags. Currently may be 1033 * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. 1034 */ 1035 public void hideSoftInputFromInputMethod(IBinder token, int flags) { 1036 try { 1037 mService.hideMySoftInput(token, flags); 1038 } catch (RemoteException e) { 1039 throw new RuntimeException(e); 1040 } 1041 } 1042 1043 /** 1044 * @hide 1045 */ 1046 public void dispatchKeyEvent(Context context, int seq, KeyEvent key, 1047 IInputMethodCallback callback) { 1048 synchronized (mH) { 1049 if (DEBUG) Log.d(TAG, "dispatchKeyEvent"); 1050 1051 if (mCurMethod == null || mCurrentTextBoxAttribute == null) { 1052 try { 1053 callback.finishedEvent(seq, false); 1054 } catch (RemoteException e) { 1055 } 1056 return; 1057 } 1058 1059 if (key.getAction() == KeyEvent.ACTION_DOWN 1060 && key.getKeyCode() == KeyEvent.KEYCODE_SYM) { 1061 showInputMethodPicker(); 1062 try { 1063 callback.finishedEvent(seq, true); 1064 } catch (RemoteException e) { 1065 } 1066 return; 1067 } 1068 try { 1069 if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod); 1070 mCurMethod.dispatchKeyEvent(seq, key, callback); 1071 } catch (RemoteException e) { 1072 Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e); 1073 try { 1074 callback.finishedEvent(seq, false); 1075 } catch (RemoteException ex) { 1076 } 1077 } 1078 } 1079 } 1080 1081 /** 1082 * @hide 1083 */ 1084 void dispatchTrackballEvent(Context context, int seq, MotionEvent motion, 1085 IInputMethodCallback callback) { 1086 synchronized (mH) { 1087 if (DEBUG) Log.d(TAG, "dispatchTrackballEvent"); 1088 1089 if (mCurMethod == null || mCurrentTextBoxAttribute == null) { 1090 try { 1091 callback.finishedEvent(seq, false); 1092 } catch (RemoteException e) { 1093 } 1094 return; 1095 } 1096 1097 try { 1098 if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod); 1099 mCurMethod.dispatchTrackballEvent(seq, motion, callback); 1100 } catch (RemoteException e) { 1101 Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e); 1102 try { 1103 callback.finishedEvent(seq, false); 1104 } catch (RemoteException ex) { 1105 } 1106 } 1107 } 1108 } 1109 1110 public void showInputMethodPicker() { 1111 synchronized (mH) { 1112 try { 1113 mService.showInputMethodPickerFromClient(mClient); 1114 } catch (RemoteException e) { 1115 Log.w(TAG, "IME died: " + mCurId, e); 1116 } 1117 } 1118 } 1119} 1120