InputMethodManager.java revision b798689749c64baba81f02e10cf2157c747d6b46
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 * Explicitly request that the current input method's soft input area be 649 * shown to the user, if needed. Call this if the user interacts with 650 * your view in such a way that they have expressed they would like to 651 * start performing input into it. 652 * 653 * @param view The currently focused view, which would like to receive 654 * soft keyboard input. 655 */ 656 public void showSoftInput(View view) { 657 synchronized (mH) { 658 if (mServedView != view) { 659 return; 660 } 661 662 try { 663 mService.showSoftInput(mClient); 664 } catch (RemoteException e) { 665 } 666 } 667 } 668 669 /** 670 * Request to hide the soft input window from the context of the window 671 * that is currently accepting input. This should be called as a result 672 * of the user doing some actually than fairly explicitly requests to 673 * have the input window hidden. 674 * 675 * @param windowToken The token of the window that is making the request, 676 * as returned by {@link View#getWindowToken() View.getWindowToken()}. 677 */ 678 public void hideSoftInputFromWindow(IBinder windowToken) { 679 synchronized (mH) { 680 if (mServedView == null || mServedView.getWindowToken() != windowToken) { 681 return; 682 } 683 684 try { 685 mService.hideSoftInput(mClient); 686 } catch (RemoteException e) { 687 } 688 } 689 } 690 691 /** 692 * If the input method is currently connected to the given view, 693 * restart it with its new contents. You should call this when the text 694 * within your view changes outside of the normal input method or key 695 * input flow, such as when an application calls TextView.setText(). 696 * 697 * @param view The view whose text has changed. 698 */ 699 public void restartInput(View view) { 700 synchronized (mH) { 701 if (mServedView != view) { 702 return; 703 } 704 705 mServedConnecting = true; 706 } 707 708 startInputInner(); 709 } 710 711 void startInputInner() { 712 final View view; 713 synchronized (mH) { 714 view = mServedView; 715 716 // Make sure we have a window token for the served view. 717 if (DEBUG) Log.v(TAG, "Starting input: view=" + view); 718 if (view == null) { 719 if (DEBUG) Log.v(TAG, "ABORT input: no served view!"); 720 return; 721 } 722 } 723 724 // Now we need to get an input connection from the served view. 725 // This is complicated in a couple ways: we can't be holding our lock 726 // when calling out to the view, and we need to make sure we call into 727 // the view on the same thread that is driving its view hierarchy. 728 Handler vh = view.getHandler(); 729 if (vh == null) { 730 // If the view doesn't have a handler, something has changed out 731 // from under us, so just bail. 732 if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!"); 733 return; 734 } 735 if (vh.getLooper() != Looper.myLooper()) { 736 // The view is running on a different thread than our own, so 737 // we need to reschedule our work for over there. 738 if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); 739 vh.post(new Runnable() { 740 public void run() { 741 startInputInner(); 742 } 743 }); 744 } 745 746 // Okay we are now ready to call into the served view and have it 747 // do its stuff. 748 // Life is good: let's hook everything up! 749 EditorInfo tba = new EditorInfo(); 750 InputConnection ic = view.onCreateInputConnection(tba); 751 if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); 752 753 synchronized (mH) { 754 // Now that we are locked again, validate that our state hasn't 755 // changed. 756 if (mServedView != view || !mServedConnecting) { 757 // Something else happened, so abort. 758 if (DEBUG) Log.v(TAG, 759 "Starting input: finished by someone else (view=" 760 + mServedView + " conn=" + mServedConnecting + ")"); 761 return; 762 } 763 764 // If we already have a text box, then this view is already 765 // connected so we want to restart it. 766 final boolean initial = mCurrentTextBoxAttribute == null; 767 768 // Hook 'em up and let 'er rip. 769 mCurrentTextBoxAttribute = tba; 770 mServedConnecting = false; 771 mServedInputConnection = ic; 772 if (ic != null) { 773 mCursorSelStart = tba.initialSelStart; 774 mCursorSelEnd = tba.initialSelEnd; 775 mCursorCandStart = -1; 776 mCursorCandEnd = -1; 777 mCursorRect.setEmpty(); 778 setCurrentInputConnection(ic); 779 } else { 780 setCurrentInputConnection(mDummyInputConnection); 781 } 782 783 try { 784 if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic=" 785 + ic + " tba=" + tba); 786 InputBindResult res = mService.startInput(mClient, tba, initial, 787 mCurMethod == null); 788 if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); 789 if (res != null) { 790 if (res.id != null) { 791 mBindSequence = res.sequence; 792 mCurMethod = res.method; 793 } else { 794 // This means there is no input method available. 795 if (DEBUG) Log.v(TAG, "ABORT input: no input method!"); 796 return; 797 } 798 } 799 if (mCurMethod != null && mCompletions != null) { 800 try { 801 mCurMethod.displayCompletions(mCompletions); 802 } catch (RemoteException e) { 803 } 804 } 805 } catch (RemoteException e) { 806 Log.w(TAG, "IME died: " + mCurId, e); 807 } 808 } 809 } 810 811 /** 812 * When the focused window is dismissed, this method is called to finish the 813 * input method started before. 814 * @hide 815 */ 816 public void windowDismissed(IBinder appWindowToken) { 817 synchronized (mH) { 818 if (mServedView != null && 819 mServedView.getWindowToken() == appWindowToken) { 820 finishInputLocked(); 821 } 822 } 823 } 824 825 /** 826 * Call this when a view receives focus. 827 * @hide 828 */ 829 public void focusIn(View view) { 830 synchronized (mH) { 831 if (DEBUG) Log.v(TAG, "focusIn: " + view); 832 // Okay we have a new view that is being served. 833 mServedView = view; 834 mCompletions = null; 835 mServedConnecting = true; 836 } 837 838 startInputInner(); 839 } 840 841 /** 842 * Call this when a view loses focus. 843 * @hide 844 */ 845 public void focusOut(View view) { 846 InputConnection ic = null; 847 synchronized (mH) { 848 if (DEBUG) Log.v(TAG, "focusOut: " + view 849 + " mServedView=" + mServedView 850 + " winFocus=" + view.hasWindowFocus()); 851 if (mServedView == view) { 852 ic = mServedInputConnection; 853 if (view.hasWindowFocus()) { 854 mLastServedView = view; 855 mH.removeMessages(MSG_CHECK_FOCUS); 856 mH.sendEmptyMessage(MSG_CHECK_FOCUS); 857 } 858 } 859 } 860 861 if (ic != null) { 862 ic.finishComposingText(); 863 } 864 } 865 866 void checkFocus() { 867 synchronized (mH) { 868 if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView 869 + " last=" + mLastServedView); 870 if (mServedView == mLastServedView) { 871 finishInputLocked(); 872 // In this case, we used to have a focused view on the window, 873 // but no longer do. We should make sure the input method is 874 // no longer shown, since it serves no purpose. 875 closeCurrentInput(); 876 } 877 mLastServedView = null; 878 } 879 } 880 881 void closeCurrentInput() { 882 try { 883 mService.hideSoftInput(mClient); 884 } catch (RemoteException e) { 885 } 886 } 887 888 /** 889 * Called by ViewRoot the first time it gets window focus. 890 */ 891 public void onWindowFocus(View focusedView, int softInputMode, 892 boolean first, int windowFlags) { 893 synchronized (mH) { 894 if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView 895 + " softInputMode=" + softInputMode 896 + " first=" + first + " flags=#" 897 + Integer.toHexString(windowFlags)); 898 try { 899 mService.windowGainedFocus(mClient, focusedView != null, 900 softInputMode, first, windowFlags); 901 } catch (RemoteException e) { 902 } 903 } 904 } 905 906 /** 907 * Report the current selection range. 908 */ 909 public void updateSelection(View view, int selStart, int selEnd, 910 int candidatesStart, int candidatesEnd) { 911 synchronized (mH) { 912 if (mServedView != view || mCurrentTextBoxAttribute == null 913 || mCurMethod == null) { 914 return; 915 } 916 917 if (mCursorSelStart != selStart || mCursorSelEnd != selEnd 918 || mCursorCandStart != candidatesStart 919 || mCursorCandEnd != candidatesEnd) { 920 if (DEBUG) Log.d(TAG, "updateSelection"); 921 922 try { 923 if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod); 924 mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd, 925 selStart, selEnd, candidatesStart, candidatesEnd); 926 mCursorSelStart = selStart; 927 mCursorSelEnd = selEnd; 928 mCursorCandStart = candidatesStart; 929 mCursorCandEnd = candidatesEnd; 930 } catch (RemoteException e) { 931 Log.w(TAG, "IME died: " + mCurId, e); 932 } 933 } 934 } 935 } 936 937 /** 938 * Returns true if the current input method wants to watch the location 939 * of the input editor's cursor in its window. 940 */ 941 public boolean isWatchingCursor(View view) { 942 return false; 943 } 944 945 /** 946 * Report the current cursor location in its window. 947 */ 948 public void updateCursor(View view, int left, int top, int right, int bottom) { 949 synchronized (mH) { 950 if (mServedView != view || mCurrentTextBoxAttribute == null 951 || mCurMethod == null) { 952 return; 953 } 954 955 mTmpCursorRect.set(left, top, right, bottom); 956 if (!mCursorRect.equals(mTmpCursorRect)) { 957 if (DEBUG) Log.d(TAG, "updateCursor"); 958 959 try { 960 if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod); 961 mCurMethod.updateCursor(mTmpCursorRect); 962 mCursorRect.set(mTmpCursorRect); 963 } catch (RemoteException e) { 964 Log.w(TAG, "IME died: " + mCurId, e); 965 } 966 } 967 } 968 } 969 970 /** 971 * Force switch to a new input method component. This can only be called 972 * from the currently active input method, as validated by the given token. 973 * @param token Supplies the identifying token given to an input method 974 * when it was started, which allows it to perform this operation on 975 * itself. 976 * @param id The unique identifier for the new input method to be switched to. 977 */ 978 public void setInputMethod(IBinder token, String id) { 979 try { 980 mService.setInputMethod(token, id); 981 } catch (RemoteException e) { 982 throw new RuntimeException(e); 983 } 984 } 985 986 /** 987 * Close/hide the input method's soft input area, so the user no longer 988 * sees it or can interact with it. This can only be called 989 * from the currently active input method, as validated by the given token. 990 * @param token Supplies the identifying token given to an input method 991 * when it was started, which allows it to perform this operation on 992 * itself. 993 */ 994 public void hideSoftInputFromInputMethod(IBinder token) { 995 try { 996 mService.hideMySoftInput(token); 997 } catch (RemoteException e) { 998 throw new RuntimeException(e); 999 } 1000 } 1001 1002 /** 1003 * @hide 1004 */ 1005 public void dispatchKeyEvent(Context context, int seq, KeyEvent key, 1006 IInputMethodCallback callback) { 1007 synchronized (mH) { 1008 if (DEBUG) Log.d(TAG, "dispatchKeyEvent"); 1009 1010 if (mCurMethod == null || mCurrentTextBoxAttribute == null) { 1011 try { 1012 callback.finishedEvent(seq, false); 1013 } catch (RemoteException e) { 1014 } 1015 return; 1016 } 1017 1018 if (key.getAction() == KeyEvent.ACTION_DOWN 1019 && key.getKeyCode() == KeyEvent.KEYCODE_SYM) { 1020 showInputMethodPicker(); 1021 try { 1022 callback.finishedEvent(seq, true); 1023 } catch (RemoteException e) { 1024 } 1025 return; 1026 } 1027 try { 1028 if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod); 1029 mCurMethod.dispatchKeyEvent(seq, key, callback); 1030 } catch (RemoteException e) { 1031 Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e); 1032 try { 1033 callback.finishedEvent(seq, false); 1034 } catch (RemoteException ex) { 1035 } 1036 } 1037 } 1038 } 1039 1040 /** 1041 * @hide 1042 */ 1043 void dispatchTrackballEvent(Context context, int seq, MotionEvent motion, 1044 IInputMethodCallback callback) { 1045 synchronized (mH) { 1046 if (DEBUG) Log.d(TAG, "dispatchTrackballEvent"); 1047 1048 if (mCurMethod == null || mCurrentTextBoxAttribute == null) { 1049 try { 1050 callback.finishedEvent(seq, false); 1051 } catch (RemoteException e) { 1052 } 1053 return; 1054 } 1055 1056 try { 1057 if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod); 1058 mCurMethod.dispatchTrackballEvent(seq, motion, callback); 1059 } catch (RemoteException e) { 1060 Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e); 1061 try { 1062 callback.finishedEvent(seq, false); 1063 } catch (RemoteException ex) { 1064 } 1065 } 1066 } 1067 } 1068 1069 public void showInputMethodPicker() { 1070 synchronized (mH) { 1071 try { 1072 mService.showInputMethodPickerFromClient(mClient); 1073 } catch (RemoteException e) { 1074 Log.w(TAG, "IME died: " + mCurId, e); 1075 } 1076 } 1077 } 1078} 1079