InputMethodManager.java revision 9266c558bf1d21ff647525ff99f7dadbca417309
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 commitCompletion(CompletionInfo text) { 297 return false; 298 } 299 300 public boolean commitText(CharSequence text, int newCursorPosition) { 301 return false; 302 } 303 304 public boolean deleteSurroundingText(int leftLength, int rightLength) { 305 return false; 306 } 307 308 public int getCursorCapsMode(int reqModes) { 309 return 0; 310 } 311 312 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 313 return null; 314 } 315 316 public CharSequence getTextAfterCursor(int n) { 317 return null; 318 } 319 320 public CharSequence getTextBeforeCursor(int n) { 321 return null; 322 } 323 324 public boolean hideStatusIcon() { 325 return false; 326 } 327 328 public boolean performPrivateCommand(String action, Bundle data) { 329 return false; 330 } 331 332 public boolean sendKeyEvent(KeyEvent event) { 333 return false; 334 } 335 336 public boolean setComposingText(CharSequence text, int newCursorPosition) { 337 return false; 338 } 339 340 public boolean finishComposingText() { 341 return false; 342 } 343 344 public boolean showStatusIcon(String packageName, int resId) { 345 return false; 346 } 347 } 348 349 final NoOpInputConnection mNoOpInputConnection = new NoOpInputConnection(); 350 351 final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { 352 public void setUsingInputMethod(boolean state) { 353 354 } 355 356 public void onBindMethod(InputBindResult res) { 357 synchronized (mH) { 358 if (mBindSequence < 0 || mBindSequence != res.sequence) { 359 Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence 360 + ", given seq=" + res.sequence); 361 return; 362 } 363 364 mCurMethod = res.method; 365 mCurId = res.id; 366 mBindSequence = res.sequence; 367 } 368 startInputInner(); 369 } 370 371 public void onUnbindMethod(int sequence) { 372 synchronized (mH) { 373 if (mBindSequence == sequence) { 374 if (false) { 375 // XXX the server has already unbound! 376 if (mCurMethod != null && mCurrentTextBoxAttribute != null) { 377 try { 378 mCurMethod.finishInput(); 379 } catch (RemoteException e) { 380 Log.w(TAG, "IME died: " + mCurId, e); 381 } 382 } 383 } 384 clearBindingLocked(); 385 386 // If we were actively using the last input method, then 387 // we would like to re-connect to the next input method. 388 if (mServedView != null && mServedView.isFocused()) { 389 mServedConnecting = true; 390 } 391 } 392 startInputInner(); 393 } 394 } 395 396 public void setActive(boolean active) { 397 mActive = active; 398 mInputConnectionWrapper.setBaseInputConnection(active 399 ? mCurrentInputConnection : mNoOpInputConnection); 400 } 401 }; 402 403 final InputConnection mDummyInputConnection = new BaseInputConnection(this) { 404 public boolean commitText(CharSequence text, int newCursorPosition) { 405 return false; 406 } 407 public boolean commitCompletion(CompletionInfo text) { 408 return false; 409 } 410 public boolean deleteSurroundingText(int leftLength, int rightLength) { 411 return false; 412 } 413 public ExtractedText getExtractedText(ExtractedTextRequest request, 414 int flags) { 415 return null; 416 } 417 public CharSequence getTextAfterCursor(int n) { 418 return null; 419 } 420 public CharSequence getTextBeforeCursor(int n) { 421 return null; 422 } 423 public int getCursorCapsMode(int reqModes) { 424 return 0; 425 } 426 public boolean clearMetaKeyStates(int states) { 427 return false; 428 } 429 public boolean performPrivateCommand(String action, Bundle data) { 430 return false; 431 } 432 public boolean setComposingText(CharSequence text, int newCursorPosition) { 433 return false; 434 } 435 public boolean finishComposingText() { 436 return false; 437 } 438 }; 439 440 InputMethodManager(IInputMethodManager service, Looper looper) { 441 mService = service; 442 mMainLooper = looper; 443 mH = new H(looper); 444 mInputConnectionWrapper = new MutableInputConnectionWrapper(mNoOpInputConnection); 445 mIInputContext = new IInputConnectionWrapper(looper, 446 mInputConnectionWrapper); 447 setCurrentInputConnection(mDummyInputConnection); 448 449 if (mInstance == null) { 450 mInstance = this; 451 } 452 } 453 454 /** 455 * Retrieve the global InputMethodManager instance, creating it if it 456 * doesn't already exist. 457 * @hide 458 */ 459 static public InputMethodManager getInstance(Context context) { 460 synchronized (mInstanceSync) { 461 if (mInstance != null) { 462 return mInstance; 463 } 464 IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); 465 IInputMethodManager service = IInputMethodManager.Stub.asInterface(b); 466 mInstance = new InputMethodManager(service, context.getMainLooper()); 467 } 468 return mInstance; 469 } 470 471 /** 472 * Private optimization: retrieve the global InputMethodManager instance, 473 * if it exists. 474 * @hide 475 */ 476 static public InputMethodManager peekInstance() { 477 return mInstance; 478 } 479 480 /** @hide */ 481 public IInputMethodClient getClient() { 482 return mClient; 483 } 484 485 /** @hide */ 486 public IInputContext getInputContext() { 487 return mIInputContext; 488 } 489 490 public List<InputMethodInfo> getInputMethodList() { 491 try { 492 return mService.getInputMethodList(); 493 } catch (RemoteException e) { 494 throw new RuntimeException(e); 495 } 496 } 497 498 public List<InputMethodInfo> getEnabledInputMethodList() { 499 try { 500 return mService.getEnabledInputMethodList(); 501 } catch (RemoteException e) { 502 throw new RuntimeException(e); 503 } 504 } 505 506 public void updateStatusIcon(int iconId, String iconPackage) { 507 try { 508 mService.updateStatusIcon(iconId, iconPackage); 509 } catch (RemoteException e) { 510 throw new RuntimeException(e); 511 } 512 } 513 514 /** 515 * Return true if the given view is the currently active view for the 516 * input method. 517 */ 518 public boolean isActive(View view) { 519 synchronized (mH) { 520 return mServedView == view && mCurrentTextBoxAttribute != null; 521 } 522 } 523 524 /** 525 * Return true if any view is currently active in the input method. 526 */ 527 public boolean isActive() { 528 synchronized (mH) { 529 return mServedView != null && mCurrentTextBoxAttribute != null; 530 } 531 } 532 533 /** 534 * Return true if the currently served view is accepting full text edits. 535 * If false, it has no input connection, so can only handle raw key events. 536 */ 537 public boolean isAcceptingText() { 538 return mServedInputConnection != null; 539 } 540 541 /** 542 * Reset all of the state associated with being bound to an input method. 543 */ 544 void clearBindingLocked() { 545 clearConnectionLocked(); 546 mBindSequence = -1; 547 mCurId = null; 548 mCurMethod = null; 549 } 550 551 /** 552 * Record the desired input connection, but only set it if mActive is true. 553 */ 554 void setCurrentInputConnection(InputConnection connection) { 555 mCurrentInputConnection = connection; 556 mInputConnectionWrapper.setBaseInputConnection(mActive 557 ? connection : mNoOpInputConnection); 558 } 559 560 /** 561 * Reset all of the state associated with a served view being connected 562 * to an input method 563 */ 564 void clearConnectionLocked() { 565 mCurrentTextBoxAttribute = null; 566 mServedInputConnection = null; 567 setCurrentInputConnection(mDummyInputConnection); 568 } 569 570 /** 571 * Disconnect any existing input connection, clearing the served view. 572 */ 573 void finishInputLocked() { 574 if (mServedView != null) { 575 if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); 576 updateStatusIcon(0, null); 577 578 if (mCurrentTextBoxAttribute != null) { 579 try { 580 mService.finishInput(mClient); 581 } catch (RemoteException e) { 582 } 583 } 584 585 if (mServedInputConnection != null) { 586 // We need to tell the previously served view that it is no 587 // longer the input target, so it can reset its state. Schedule 588 // this call on its window's Handler so it will be on the correct 589 // thread and outside of our lock. 590 Handler vh = mServedView.getHandler(); 591 if (vh != null) { 592 // This will result in a call to reportFinishInputConnection() 593 // below. 594 vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION, 595 mServedInputConnection)); 596 } 597 } 598 599 mServedView = null; 600 mCompletions = null; 601 mServedConnecting = false; 602 clearConnectionLocked(); 603 } 604 } 605 606 /** 607 * Called from the FINISH_INPUT_CONNECTION message above. 608 * @hide 609 */ 610 public void reportFinishInputConnection(InputConnection ic) { 611 if (mServedInputConnection != ic) { 612 ic.finishComposingText(); 613 } 614 } 615 616 public void displayCompletions(View view, CompletionInfo[] completions) { 617 synchronized (mH) { 618 if (mServedView != view) { 619 return; 620 } 621 622 mCompletions = completions; 623 if (mCurMethod != null) { 624 try { 625 mCurMethod.displayCompletions(mCompletions); 626 } catch (RemoteException e) { 627 } 628 } 629 } 630 } 631 632 public void updateExtractedText(View view, int token, ExtractedText text) { 633 synchronized (mH) { 634 if (mServedView != view) { 635 return; 636 } 637 638 if (mCurMethod != null) { 639 try { 640 mCurMethod.updateExtractedText(token, text); 641 } catch (RemoteException e) { 642 } 643 } 644 } 645 } 646 647 /** 648 * Flag for {@link #showSoftInput} to indicate that the this is an implicit 649 * request to show the input window, not as the result of a direct request 650 * by the user. The window may not be shown in this case. 651 */ 652 public static final int SHOW_IMPLICIT = 0x0001; 653 654 /** 655 * Explicitly request that the current input method's soft input area be 656 * shown to the user, if needed. Call this if the user interacts with 657 * your view in such a way that they have expressed they would like to 658 * start performing input into it. 659 * 660 * @param view The currently focused view, which would like to receive 661 * soft keyboard input. 662 * @param flags Provides additional operating flags. Currently may be 663 * 0 or have the {@link #SHOW_IMPLICIT} bit set. 664 */ 665 public void showSoftInput(View view, int flags) { 666 synchronized (mH) { 667 if (mServedView != view) { 668 return; 669 } 670 671 try { 672 mService.showSoftInput(mClient, flags); 673 } catch (RemoteException e) { 674 } 675 } 676 } 677 678 /** 679 * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft 680 * input window should only be hidden if it was not explicitly shown 681 * by the user. 682 */ 683 public static final int HIDE_IMPLICIT_ONLY = 0x0001; 684 685 /** 686 * Request to hide the soft input window from the context of the window 687 * that is currently accepting input. This should be called as a result 688 * of the user doing some actually than fairly explicitly requests to 689 * have the input window hidden. 690 * 691 * @param windowToken The token of the window that is making the request, 692 * as returned by {@link View#getWindowToken() View.getWindowToken()}. 693 * @param flags Provides additional operating flags. Currently may be 694 * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. 695 */ 696 public void hideSoftInputFromWindow(IBinder windowToken, int flags) { 697 synchronized (mH) { 698 if (mServedView == null || mServedView.getWindowToken() != windowToken) { 699 return; 700 } 701 702 try { 703 mService.hideSoftInput(mClient, flags); 704 } catch (RemoteException e) { 705 } 706 } 707 } 708 709 /** 710 * If the input method is currently connected to the given view, 711 * restart it with its new contents. You should call this when the text 712 * within your view changes outside of the normal input method or key 713 * input flow, such as when an application calls TextView.setText(). 714 * 715 * @param view The view whose text has changed. 716 */ 717 public void restartInput(View view) { 718 synchronized (mH) { 719 if (mServedView != view) { 720 return; 721 } 722 723 mServedConnecting = true; 724 } 725 726 startInputInner(); 727 } 728 729 void startInputInner() { 730 final View view; 731 synchronized (mH) { 732 view = mServedView; 733 734 // Make sure we have a window token for the served view. 735 if (DEBUG) Log.v(TAG, "Starting input: view=" + view); 736 if (view == null) { 737 if (DEBUG) Log.v(TAG, "ABORT input: no served view!"); 738 return; 739 } 740 } 741 742 // Now we need to get an input connection from the served view. 743 // This is complicated in a couple ways: we can't be holding our lock 744 // when calling out to the view, and we need to make sure we call into 745 // the view on the same thread that is driving its view hierarchy. 746 Handler vh = view.getHandler(); 747 if (vh == null) { 748 // If the view doesn't have a handler, something has changed out 749 // from under us, so just bail. 750 if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!"); 751 return; 752 } 753 if (vh.getLooper() != Looper.myLooper()) { 754 // The view is running on a different thread than our own, so 755 // we need to reschedule our work for over there. 756 if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); 757 vh.post(new Runnable() { 758 public void run() { 759 startInputInner(); 760 } 761 }); 762 } 763 764 // Okay we are now ready to call into the served view and have it 765 // do its stuff. 766 // Life is good: let's hook everything up! 767 EditorInfo tba = new EditorInfo(); 768 InputConnection ic = view.onCreateInputConnection(tba); 769 if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); 770 771 synchronized (mH) { 772 // Now that we are locked again, validate that our state hasn't 773 // changed. 774 if (mServedView != view || !mServedConnecting) { 775 // Something else happened, so abort. 776 if (DEBUG) Log.v(TAG, 777 "Starting input: finished by someone else (view=" 778 + mServedView + " conn=" + mServedConnecting + ")"); 779 return; 780 } 781 782 // If we already have a text box, then this view is already 783 // connected so we want to restart it. 784 final boolean initial = mCurrentTextBoxAttribute == null; 785 786 // Hook 'em up and let 'er rip. 787 mCurrentTextBoxAttribute = tba; 788 mServedConnecting = false; 789 mServedInputConnection = ic; 790 if (ic != null) { 791 mCursorSelStart = tba.initialSelStart; 792 mCursorSelEnd = tba.initialSelEnd; 793 mCursorCandStart = -1; 794 mCursorCandEnd = -1; 795 mCursorRect.setEmpty(); 796 setCurrentInputConnection(ic); 797 } else { 798 setCurrentInputConnection(mDummyInputConnection); 799 } 800 801 try { 802 if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic=" 803 + ic + " tba=" + tba); 804 InputBindResult res = mService.startInput(mClient, tba, initial, 805 mCurMethod == null); 806 if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); 807 if (res != null) { 808 if (res.id != null) { 809 mBindSequence = res.sequence; 810 mCurMethod = res.method; 811 } else { 812 // This means there is no input method available. 813 if (DEBUG) Log.v(TAG, "ABORT input: no input method!"); 814 return; 815 } 816 } 817 if (mCurMethod != null && mCompletions != null) { 818 try { 819 mCurMethod.displayCompletions(mCompletions); 820 } catch (RemoteException e) { 821 } 822 } 823 } catch (RemoteException e) { 824 Log.w(TAG, "IME died: " + mCurId, e); 825 } 826 } 827 } 828 829 /** 830 * When the focused window is dismissed, this method is called to finish the 831 * input method started before. 832 * @hide 833 */ 834 public void windowDismissed(IBinder appWindowToken) { 835 synchronized (mH) { 836 if (mServedView != null && 837 mServedView.getWindowToken() == appWindowToken) { 838 finishInputLocked(); 839 } 840 } 841 } 842 843 /** 844 * Call this when a view receives focus. 845 * @hide 846 */ 847 public void focusIn(View view) { 848 synchronized (mH) { 849 if (DEBUG) Log.v(TAG, "focusIn: " + view); 850 // Okay we have a new view that is being served. 851 mServedView = view; 852 mCompletions = null; 853 mServedConnecting = true; 854 } 855 856 startInputInner(); 857 } 858 859 /** 860 * Call this when a view loses focus. 861 * @hide 862 */ 863 public void focusOut(View view) { 864 InputConnection ic = null; 865 synchronized (mH) { 866 if (DEBUG) Log.v(TAG, "focusOut: " + view 867 + " mServedView=" + mServedView 868 + " winFocus=" + view.hasWindowFocus()); 869 if (mServedView == view) { 870 ic = mServedInputConnection; 871 if (view.hasWindowFocus()) { 872 mLastServedView = view; 873 mH.removeMessages(MSG_CHECK_FOCUS); 874 mH.sendEmptyMessage(MSG_CHECK_FOCUS); 875 } 876 } 877 } 878 879 if (ic != null) { 880 ic.finishComposingText(); 881 } 882 } 883 884 void checkFocus() { 885 synchronized (mH) { 886 if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView 887 + " last=" + mLastServedView); 888 if (mServedView == mLastServedView) { 889 finishInputLocked(); 890 // In this case, we used to have a focused view on the window, 891 // but no longer do. We should make sure the input method is 892 // no longer shown, since it serves no purpose. 893 closeCurrentInput(); 894 } 895 mLastServedView = null; 896 } 897 } 898 899 void closeCurrentInput() { 900 try { 901 mService.hideSoftInput(mClient, 0); 902 } catch (RemoteException e) { 903 } 904 } 905 906 /** 907 * Called by ViewRoot the first time it gets window focus. 908 * @hide 909 */ 910 public void onWindowFocus(View focusedView, int softInputMode, 911 boolean first, int windowFlags) { 912 synchronized (mH) { 913 if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView 914 + " softInputMode=" + softInputMode 915 + " first=" + first + " flags=#" 916 + Integer.toHexString(windowFlags)); 917 try { 918 final boolean isTextEditor = focusedView != null && 919 focusedView.onCheckIsTextEditor(); 920 mService.windowGainedFocus(mClient, focusedView != null, 921 isTextEditor, softInputMode, first, windowFlags); 922 } catch (RemoteException e) { 923 } 924 } 925 } 926 927 /** 928 * Report the current selection range. 929 */ 930 public void updateSelection(View view, int selStart, int selEnd, 931 int candidatesStart, int candidatesEnd) { 932 synchronized (mH) { 933 if (mServedView != view || mCurrentTextBoxAttribute == null 934 || mCurMethod == null) { 935 return; 936 } 937 938 if (mCursorSelStart != selStart || mCursorSelEnd != selEnd 939 || mCursorCandStart != candidatesStart 940 || mCursorCandEnd != candidatesEnd) { 941 if (DEBUG) Log.d(TAG, "updateSelection"); 942 943 try { 944 if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod); 945 mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd, 946 selStart, selEnd, candidatesStart, candidatesEnd); 947 mCursorSelStart = selStart; 948 mCursorSelEnd = selEnd; 949 mCursorCandStart = candidatesStart; 950 mCursorCandEnd = candidatesEnd; 951 } catch (RemoteException e) { 952 Log.w(TAG, "IME died: " + mCurId, e); 953 } 954 } 955 } 956 } 957 958 /** 959 * Returns true if the current input method wants to watch the location 960 * of the input editor's cursor in its window. 961 */ 962 public boolean isWatchingCursor(View view) { 963 return false; 964 } 965 966 /** 967 * Report the current cursor location in its window. 968 */ 969 public void updateCursor(View view, int left, int top, int right, int bottom) { 970 synchronized (mH) { 971 if (mServedView != view || mCurrentTextBoxAttribute == null 972 || mCurMethod == null) { 973 return; 974 } 975 976 mTmpCursorRect.set(left, top, right, bottom); 977 if (!mCursorRect.equals(mTmpCursorRect)) { 978 if (DEBUG) Log.d(TAG, "updateCursor"); 979 980 try { 981 if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod); 982 mCurMethod.updateCursor(mTmpCursorRect); 983 mCursorRect.set(mTmpCursorRect); 984 } catch (RemoteException e) { 985 Log.w(TAG, "IME died: " + mCurId, e); 986 } 987 } 988 } 989 } 990 991 /** 992 * Force switch to a new input method component. This can only be called 993 * from the currently active input method, as validated by the given token. 994 * @param token Supplies the identifying token given to an input method 995 * when it was started, which allows it to perform this operation on 996 * itself. 997 * @param id The unique identifier for the new input method to be switched to. 998 */ 999 public void setInputMethod(IBinder token, String id) { 1000 try { 1001 mService.setInputMethod(token, id); 1002 } catch (RemoteException e) { 1003 throw new RuntimeException(e); 1004 } 1005 } 1006 1007 /** 1008 * Close/hide the input method's soft input area, so the user no longer 1009 * sees it or can interact with it. This can only be called 1010 * from the currently active input method, as validated by the given token. 1011 * 1012 * @param token Supplies the identifying token given to an input method 1013 * when it was started, which allows it to perform this operation on 1014 * itself. 1015 * @param flags Provides additional operating flags. Currently may be 1016 * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. 1017 */ 1018 public void hideSoftInputFromInputMethod(IBinder token, int flags) { 1019 try { 1020 mService.hideMySoftInput(token, flags); 1021 } catch (RemoteException e) { 1022 throw new RuntimeException(e); 1023 } 1024 } 1025 1026 /** 1027 * @hide 1028 */ 1029 public void dispatchKeyEvent(Context context, int seq, KeyEvent key, 1030 IInputMethodCallback callback) { 1031 synchronized (mH) { 1032 if (DEBUG) Log.d(TAG, "dispatchKeyEvent"); 1033 1034 if (mCurMethod == null || mCurrentTextBoxAttribute == null) { 1035 try { 1036 callback.finishedEvent(seq, false); 1037 } catch (RemoteException e) { 1038 } 1039 return; 1040 } 1041 1042 if (key.getAction() == KeyEvent.ACTION_DOWN 1043 && key.getKeyCode() == KeyEvent.KEYCODE_SYM) { 1044 showInputMethodPicker(); 1045 try { 1046 callback.finishedEvent(seq, true); 1047 } catch (RemoteException e) { 1048 } 1049 return; 1050 } 1051 try { 1052 if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod); 1053 mCurMethod.dispatchKeyEvent(seq, key, callback); 1054 } catch (RemoteException e) { 1055 Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e); 1056 try { 1057 callback.finishedEvent(seq, false); 1058 } catch (RemoteException ex) { 1059 } 1060 } 1061 } 1062 } 1063 1064 /** 1065 * @hide 1066 */ 1067 void dispatchTrackballEvent(Context context, int seq, MotionEvent motion, 1068 IInputMethodCallback callback) { 1069 synchronized (mH) { 1070 if (DEBUG) Log.d(TAG, "dispatchTrackballEvent"); 1071 1072 if (mCurMethod == null || mCurrentTextBoxAttribute == null) { 1073 try { 1074 callback.finishedEvent(seq, false); 1075 } catch (RemoteException e) { 1076 } 1077 return; 1078 } 1079 1080 try { 1081 if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod); 1082 mCurMethod.dispatchTrackballEvent(seq, motion, callback); 1083 } catch (RemoteException e) { 1084 Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e); 1085 try { 1086 callback.finishedEvent(seq, false); 1087 } catch (RemoteException ex) { 1088 } 1089 } 1090 } 1091 } 1092 1093 public void showInputMethodPicker() { 1094 synchronized (mH) { 1095 try { 1096 mService.showInputMethodPickerFromClient(mClient); 1097 } catch (RemoteException e) { 1098 Log.w(TAG, "IME died: " + mCurId, e); 1099 } 1100 } 1101 } 1102} 1103