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 17 package android.inputmethodservice; 18 19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 21 22 import android.annotation.DrawableRes; 23 import android.app.ActivityManager; 24 import android.app.Dialog; 25 import android.content.Context; 26 import android.content.res.Configuration; 27 import android.content.res.Resources; 28 import android.content.res.TypedArray; 29 import android.graphics.Rect; 30 import android.graphics.Region; 31 import android.os.Bundle; 32 import android.os.IBinder; 33 import android.os.ResultReceiver; 34 import android.os.SystemClock; 35 import android.provider.Settings; 36 import android.text.InputType; 37 import android.text.Layout; 38 import android.text.Spannable; 39 import android.text.method.MovementMethod; 40 import android.util.Log; 41 import android.util.PrintWriterPrinter; 42 import android.util.Printer; 43 import android.view.Gravity; 44 import android.view.KeyCharacterMap; 45 import android.view.KeyEvent; 46 import android.view.LayoutInflater; 47 import android.view.MotionEvent; 48 import android.view.View; 49 import android.view.ViewGroup; 50 import android.view.ViewTreeObserver; 51 import android.view.Window; 52 import android.view.WindowManager; 53 import android.view.WindowManager.BadTokenException; 54 import android.view.animation.AnimationUtils; 55 import android.view.inputmethod.CompletionInfo; 56 import android.view.inputmethod.CursorAnchorInfo; 57 import android.view.inputmethod.EditorInfo; 58 import android.view.inputmethod.ExtractedText; 59 import android.view.inputmethod.ExtractedTextRequest; 60 import android.view.inputmethod.InputBinding; 61 import android.view.inputmethod.InputConnection; 62 import android.view.inputmethod.InputMethod; 63 import android.view.inputmethod.InputMethodManager; 64 import android.view.inputmethod.InputMethodSubtype; 65 import android.widget.Button; 66 import android.widget.FrameLayout; 67 import android.widget.LinearLayout; 68 69 import java.io.FileDescriptor; 70 import java.io.PrintWriter; 71 72 /** 73 * InputMethodService provides a standard implementation of an InputMethod, 74 * which final implementations can derive from and customize. See the 75 * base class {@link AbstractInputMethodService} and the {@link InputMethod} 76 * interface for more information on the basics of writing input methods. 77 * 78 * <p>In addition to the normal Service lifecycle methods, this class 79 * introduces some new specific callbacks that most subclasses will want 80 * to make use of:</p> 81 * <ul> 82 * <li> {@link #onInitializeInterface()} for user-interface initialization, 83 * in particular to deal with configuration changes while the service is 84 * running. 85 * <li> {@link #onBindInput} to find out about switching to a new client. 86 * <li> {@link #onStartInput} to deal with an input session starting with 87 * the client. 88 * <li> {@link #onCreateInputView()}, {@link #onCreateCandidatesView()}, 89 * and {@link #onCreateExtractTextView()} for non-demand generation of the UI. 90 * <li> {@link #onStartInputView(EditorInfo, boolean)} to deal with input 91 * starting within the input area of the IME. 92 * </ul> 93 * 94 * <p>An input method has significant discretion in how it goes about its 95 * work: the {@link android.inputmethodservice.InputMethodService} provides 96 * a basic framework for standard UI elements (input view, candidates view, 97 * and running in fullscreen mode), but it is up to a particular implementor 98 * to decide how to use them. For example, one input method could implement 99 * an input area with a keyboard, another could allow the user to draw text, 100 * while a third could have no input area (and thus not be visible to the 101 * user) but instead listen to audio and perform text to speech conversion.</p> 102 * 103 * <p>In the implementation provided here, all of these elements are placed 104 * together in a single window managed by the InputMethodService. It will 105 * execute callbacks as it needs information about them, and provides APIs for 106 * programmatic control over them. They layout of these elements is explicitly 107 * defined:</p> 108 * 109 * <ul> 110 * <li>The soft input view, if available, is placed at the bottom of the 111 * screen. 112 * <li>The candidates view, if currently shown, is placed above the soft 113 * input view. 114 * <li>If not running fullscreen, the application is moved or resized to be 115 * above these views; if running fullscreen, the window will completely cover 116 * the application and its top part will contain the extract text of what is 117 * currently being edited by the application. 118 * </ul> 119 * 120 * 121 * <a name="SoftInputView"></a> 122 * <h3>Soft Input View</h3> 123 * 124 * <p>Central to most input methods is the soft input view. This is where most 125 * user interaction occurs: pressing on soft keys, drawing characters, or 126 * however else your input method wants to generate text. Most implementations 127 * will simply have their own view doing all of this work, and return a new 128 * instance of it when {@link #onCreateInputView()} is called. At that point, 129 * as long as the input view is visible, you will see user interaction in 130 * that view and can call back on the InputMethodService to interact with the 131 * application as appropriate.</p> 132 * 133 * <p>There are some situations where you want to decide whether or not your 134 * soft input view should be shown to the user. This is done by implementing 135 * the {@link #onEvaluateInputViewShown()} to return true or false based on 136 * whether it should be shown in the current environment. If any of your 137 * state has changed that may impact this, call 138 * {@link #updateInputViewShown()} to have it re-evaluated. The default 139 * implementation always shows the input view unless there is a hard 140 * keyboard available, which is the appropriate behavior for most input 141 * methods.</p> 142 * 143 * 144 * <a name="CandidatesView"></a> 145 * <h3>Candidates View</h3> 146 * 147 * <p>Often while the user is generating raw text, an input method wants to 148 * provide them with a list of possible interpretations of that text that can 149 * be selected for use. This is accomplished with the candidates view, and 150 * like the soft input view you implement {@link #onCreateCandidatesView()} 151 * to instantiate your own view implementing your candidates UI.</p> 152 * 153 * <p>Management of the candidates view is a little different than the input 154 * view, because the candidates view tends to be more transient, being shown 155 * only when there are possible candidates for the current text being entered 156 * by the user. To control whether the candidates view is shown, you use 157 * {@link #setCandidatesViewShown(boolean)}. Note that because the candidate 158 * view tends to be shown and hidden a lot, it does not impact the application 159 * UI in the same way as the soft input view: it will never cause application 160 * windows to resize, only cause them to be panned if needed for the user to 161 * see the current focus.</p> 162 * 163 * 164 * <a name="FullscreenMode"></a> 165 * <h3>Fullscreen Mode</h3> 166 * 167 * <p>Sometimes your input method UI is too large to integrate with the 168 * application UI, so you just want to take over the screen. This is 169 * accomplished by switching to full-screen mode, causing the input method 170 * window to fill the entire screen and add its own "extracted text" editor 171 * showing the user the text that is being typed. Unlike the other UI elements, 172 * there is a standard implementation for the extract editor that you should 173 * not need to change. The editor is placed at the top of the IME, above the 174 * input and candidates views.</p> 175 * 176 * <p>Similar to the input view, you control whether the IME is running in 177 * fullscreen mode by implementing {@link #onEvaluateFullscreenMode()} 178 * to return true or false based on 179 * whether it should be fullscreen in the current environment. If any of your 180 * state has changed that may impact this, call 181 * {@link #updateFullscreenMode()} to have it re-evaluated. The default 182 * implementation selects fullscreen mode when the screen is in a landscape 183 * orientation, which is appropriate behavior for most input methods that have 184 * a significant input area.</p> 185 * 186 * <p>When in fullscreen mode, you have some special requirements because the 187 * user can not see the application UI. In particular, you should implement 188 * {@link #onDisplayCompletions(CompletionInfo[])} to show completions 189 * generated by your application, typically in your candidates view like you 190 * would normally show candidates. 191 * 192 * 193 * <a name="GeneratingText"></a> 194 * <h3>Generating Text</h3> 195 * 196 * <p>The key part of an IME is of course generating text for the application. 197 * This is done through calls to the 198 * {@link android.view.inputmethod.InputConnection} interface to the 199 * application, which can be retrieved from {@link #getCurrentInputConnection()}. 200 * This interface allows you to generate raw key events or, if the target 201 * supports it, directly edit in strings of candidates and committed text.</p> 202 * 203 * <p>Information about what the target is expected and supports can be found 204 * through the {@link android.view.inputmethod.EditorInfo} class, which is 205 * retrieved with {@link #getCurrentInputEditorInfo()} method. The most 206 * important part of this is {@link android.view.inputmethod.EditorInfo#inputType 207 * EditorInfo.inputType}; in particular, if this is 208 * {@link android.view.inputmethod.EditorInfo#TYPE_NULL EditorInfo.TYPE_NULL}, 209 * then the target does not support complex edits and you need to only deliver 210 * raw key events to it. An input method will also want to look at other 211 * values here, to for example detect password mode, auto complete text views, 212 * phone number entry, etc.</p> 213 * 214 * <p>When the user switches between input targets, you will receive calls to 215 * {@link #onFinishInput()} and {@link #onStartInput(EditorInfo, boolean)}. 216 * You can use these to reset and initialize your input state for the current 217 * target. For example, you will often want to clear any input state, and 218 * update a soft keyboard to be appropriate for the new inputType.</p> 219 * 220 * @attr ref android.R.styleable#InputMethodService_imeFullscreenBackground 221 * @attr ref android.R.styleable#InputMethodService_imeExtractEnterAnimation 222 * @attr ref android.R.styleable#InputMethodService_imeExtractExitAnimation 223 */ 224 public class InputMethodService extends AbstractInputMethodService { 225 static final String TAG = "InputMethodService"; 226 static final boolean DEBUG = false; 227 228 /** 229 * The back button will close the input window. 230 */ 231 public static final int BACK_DISPOSITION_DEFAULT = 0; // based on window 232 233 /** 234 * This input method will not consume the back key. 235 */ 236 public static final int BACK_DISPOSITION_WILL_NOT_DISMISS = 1; // back 237 238 /** 239 * This input method will consume the back key. 240 */ 241 public static final int BACK_DISPOSITION_WILL_DISMISS = 2; // down 242 243 /** 244 * @hide 245 * The IME is active. It may or may not be visible. 246 */ 247 public static final int IME_ACTIVE = 0x1; 248 249 /** 250 * @hide 251 * The IME is visible. 252 */ 253 public static final int IME_VISIBLE = 0x2; 254 255 InputMethodManager mImm; 256 257 int mTheme = 0; 258 boolean mHardwareAccelerated = false; 259 260 LayoutInflater mInflater; 261 TypedArray mThemeAttrs; 262 View mRootView; 263 SoftInputWindow mWindow; 264 boolean mInitialized; 265 boolean mWindowCreated; 266 boolean mWindowAdded; 267 boolean mWindowVisible; 268 boolean mWindowWasVisible; 269 boolean mInShowWindow; 270 ViewGroup mFullscreenArea; 271 FrameLayout mExtractFrame; 272 FrameLayout mCandidatesFrame; 273 FrameLayout mInputFrame; 274 275 IBinder mToken; 276 277 InputBinding mInputBinding; 278 InputConnection mInputConnection; 279 boolean mInputStarted; 280 boolean mInputViewStarted; 281 boolean mCandidatesViewStarted; 282 InputConnection mStartedInputConnection; 283 EditorInfo mInputEditorInfo; 284 285 int mShowInputFlags; 286 boolean mShowInputRequested; 287 boolean mLastShowInputRequested; 288 int mCandidatesVisibility; 289 CompletionInfo[] mCurCompletions; 290 291 boolean mShowInputForced; 292 293 boolean mFullscreenApplied; 294 boolean mIsFullscreen; 295 View mExtractView; 296 boolean mExtractViewHidden; 297 ExtractEditText mExtractEditText; 298 ViewGroup mExtractAccessories; 299 Button mExtractAction; 300 ExtractedText mExtractedText; 301 int mExtractedToken; 302 303 View mInputView; 304 boolean mIsInputViewShown; 305 306 int mStatusIcon; 307 int mBackDisposition; 308 309 /** 310 * {@code true} when the previous IME had non-empty inset at the bottom of the screen and we 311 * have not shown our own window yet. In this situation, the previous inset continues to be 312 * shown as an empty region until it is explicitly updated. Basically we can trigger the update 313 * by calling 1) {@code mWindow.show()} or 2) {@link #clearInsetOfPreviousIme()}. 314 */ 315 boolean mShouldClearInsetOfPreviousIme; 316 317 final Insets mTmpInsets = new Insets(); 318 final int[] mTmpLocation = new int[2]; 319 320 final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = 321 new ViewTreeObserver.OnComputeInternalInsetsListener() { 322 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { 323 if (isExtractViewShown()) { 324 // In true fullscreen mode, we just say the window isn't covering 325 // any content so we don't impact whatever is behind. 326 View decor = getWindow().getWindow().getDecorView(); 327 info.contentInsets.top = info.visibleInsets.top 328 = decor.getHeight(); 329 info.touchableRegion.setEmpty(); 330 info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); 331 } else { 332 onComputeInsets(mTmpInsets); 333 info.contentInsets.top = mTmpInsets.contentTopInsets; 334 info.visibleInsets.top = mTmpInsets.visibleTopInsets; 335 info.touchableRegion.set(mTmpInsets.touchableRegion); 336 info.setTouchableInsets(mTmpInsets.touchableInsets); 337 } 338 } 339 }; 340 341 final View.OnClickListener mActionClickListener = new View.OnClickListener() { 342 public void onClick(View v) { 343 final EditorInfo ei = getCurrentInputEditorInfo(); 344 final InputConnection ic = getCurrentInputConnection(); 345 if (ei != null && ic != null) { 346 if (ei.actionId != 0) { 347 ic.performEditorAction(ei.actionId); 348 } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION) 349 != EditorInfo.IME_ACTION_NONE) { 350 ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION); 351 } 352 } 353 } 354 }; 355 356 /** 357 * Concrete implementation of 358 * {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides 359 * all of the standard behavior for an input method. 360 */ 361 public class InputMethodImpl extends AbstractInputMethodImpl { 362 /** 363 * Take care of attaching the given window token provided by the system. 364 */ attachToken(IBinder token)365 public void attachToken(IBinder token) { 366 if (mToken == null) { 367 mToken = token; 368 mWindow.setToken(token); 369 } 370 } 371 372 /** 373 * Handle a new input binding, calling 374 * {@link InputMethodService#onBindInput InputMethodService.onBindInput()} 375 * when done. 376 */ bindInput(InputBinding binding)377 public void bindInput(InputBinding binding) { 378 mInputBinding = binding; 379 mInputConnection = binding.getConnection(); 380 if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding 381 + " ic=" + mInputConnection); 382 InputConnection ic = getCurrentInputConnection(); 383 if (ic != null) ic.reportFullscreenMode(mIsFullscreen); 384 initialize(); 385 onBindInput(); 386 } 387 388 /** 389 * Clear the current input binding. 390 */ unbindInput()391 public void unbindInput() { 392 if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding 393 + " ic=" + mInputConnection); 394 onUnbindInput(); 395 mInputBinding = null; 396 mInputConnection = null; 397 } 398 startInput(InputConnection ic, EditorInfo attribute)399 public void startInput(InputConnection ic, EditorInfo attribute) { 400 if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute); 401 doStartInput(ic, attribute, false); 402 } 403 restartInput(InputConnection ic, EditorInfo attribute)404 public void restartInput(InputConnection ic, EditorInfo attribute) { 405 if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute); 406 doStartInput(ic, attribute, true); 407 } 408 409 /** 410 * Handle a request by the system to hide the soft input area. 411 */ hideSoftInput(int flags, ResultReceiver resultReceiver)412 public void hideSoftInput(int flags, ResultReceiver resultReceiver) { 413 if (DEBUG) Log.v(TAG, "hideSoftInput()"); 414 boolean wasVis = isInputViewShown(); 415 mShowInputFlags = 0; 416 mShowInputRequested = false; 417 mShowInputForced = false; 418 doHideWindow(); 419 clearInsetOfPreviousIme(); 420 if (resultReceiver != null) { 421 resultReceiver.send(wasVis != isInputViewShown() 422 ? InputMethodManager.RESULT_HIDDEN 423 : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN 424 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null); 425 } 426 } 427 428 /** 429 * Handle a request by the system to show the soft input area. 430 */ showSoftInput(int flags, ResultReceiver resultReceiver)431 public void showSoftInput(int flags, ResultReceiver resultReceiver) { 432 if (DEBUG) Log.v(TAG, "showSoftInput()"); 433 boolean wasVis = isInputViewShown(); 434 mShowInputFlags = 0; 435 if (onShowInputRequested(flags, false)) { 436 try { 437 showWindow(true); 438 } catch (BadTokenException e) { 439 if (DEBUG) Log.v(TAG, "BadTokenException: IME is done."); 440 mWindowVisible = false; 441 mWindowAdded = false; 442 } 443 } 444 clearInsetOfPreviousIme(); 445 // If user uses hard keyboard, IME button should always be shown. 446 boolean showing = isInputViewShown(); 447 mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0), 448 mBackDisposition); 449 if (resultReceiver != null) { 450 resultReceiver.send(wasVis != isInputViewShown() 451 ? InputMethodManager.RESULT_SHOWN 452 : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN 453 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null); 454 } 455 } 456 changeInputMethodSubtype(InputMethodSubtype subtype)457 public void changeInputMethodSubtype(InputMethodSubtype subtype) { 458 onCurrentInputMethodSubtypeChanged(subtype); 459 } 460 } 461 462 /** 463 * Concrete implementation of 464 * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides 465 * all of the standard behavior for an input method session. 466 */ 467 public class InputMethodSessionImpl extends AbstractInputMethodSessionImpl { finishInput()468 public void finishInput() { 469 if (!isEnabled()) { 470 return; 471 } 472 if (DEBUG) Log.v(TAG, "finishInput() in " + this); 473 doFinishInput(); 474 } 475 476 /** 477 * Call {@link InputMethodService#onDisplayCompletions 478 * InputMethodService.onDisplayCompletions()}. 479 */ displayCompletions(CompletionInfo[] completions)480 public void displayCompletions(CompletionInfo[] completions) { 481 if (!isEnabled()) { 482 return; 483 } 484 mCurCompletions = completions; 485 onDisplayCompletions(completions); 486 } 487 488 /** 489 * Call {@link InputMethodService#onUpdateExtractedText 490 * InputMethodService.onUpdateExtractedText()}. 491 */ updateExtractedText(int token, ExtractedText text)492 public void updateExtractedText(int token, ExtractedText text) { 493 if (!isEnabled()) { 494 return; 495 } 496 onUpdateExtractedText(token, text); 497 } 498 499 /** 500 * Call {@link InputMethodService#onUpdateSelection 501 * InputMethodService.onUpdateSelection()}. 502 */ updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)503 public void updateSelection(int oldSelStart, int oldSelEnd, 504 int newSelStart, int newSelEnd, 505 int candidatesStart, int candidatesEnd) { 506 if (!isEnabled()) { 507 return; 508 } 509 InputMethodService.this.onUpdateSelection(oldSelStart, oldSelEnd, 510 newSelStart, newSelEnd, candidatesStart, candidatesEnd); 511 } 512 513 @Override viewClicked(boolean focusChanged)514 public void viewClicked(boolean focusChanged) { 515 if (!isEnabled()) { 516 return; 517 } 518 InputMethodService.this.onViewClicked(focusChanged); 519 } 520 521 /** 522 * Call {@link InputMethodService#onUpdateCursor 523 * InputMethodService.onUpdateCursor()}. 524 */ updateCursor(Rect newCursor)525 public void updateCursor(Rect newCursor) { 526 if (!isEnabled()) { 527 return; 528 } 529 InputMethodService.this.onUpdateCursor(newCursor); 530 } 531 532 /** 533 * Call {@link InputMethodService#onAppPrivateCommand 534 * InputMethodService.onAppPrivateCommand()}. 535 */ appPrivateCommand(String action, Bundle data)536 public void appPrivateCommand(String action, Bundle data) { 537 if (!isEnabled()) { 538 return; 539 } 540 InputMethodService.this.onAppPrivateCommand(action, data); 541 } 542 543 /** 544 * 545 */ toggleSoftInput(int showFlags, int hideFlags)546 public void toggleSoftInput(int showFlags, int hideFlags) { 547 InputMethodService.this.onToggleSoftInput(showFlags, hideFlags); 548 } 549 550 /** 551 * Call {@link InputMethodService#onUpdateCursorAnchorInfo 552 * InputMethodService.onUpdateCursorAnchorInfo()}. 553 */ updateCursorAnchorInfo(CursorAnchorInfo info)554 public void updateCursorAnchorInfo(CursorAnchorInfo info) { 555 if (!isEnabled()) { 556 return; 557 } 558 InputMethodService.this.onUpdateCursorAnchorInfo(info); 559 } 560 } 561 562 /** 563 * Information about where interesting parts of the input method UI appear. 564 */ 565 public static final class Insets { 566 /** 567 * This is the top part of the UI that is the main content. It is 568 * used to determine the basic space needed, to resize/pan the 569 * application behind. It is assumed that this inset does not 570 * change very much, since any change will cause a full resize/pan 571 * of the application behind. This value is relative to the top edge 572 * of the input method window. 573 */ 574 public int contentTopInsets; 575 576 /** 577 * This is the top part of the UI that is visibly covering the 578 * application behind it. This provides finer-grained control over 579 * visibility, allowing you to change it relatively frequently (such 580 * as hiding or showing candidates) without disrupting the underlying 581 * UI too much. For example, this will never resize the application 582 * UI, will only pan if needed to make the current focus visible, and 583 * will not aggressively move the pan position when this changes unless 584 * needed to make the focus visible. This value is relative to the top edge 585 * of the input method window. 586 */ 587 public int visibleTopInsets; 588 589 /** 590 * This is the region of the UI that is touchable. It is used when 591 * {@link #touchableInsets} is set to {@link #TOUCHABLE_INSETS_REGION}. 592 * The region should be specified relative to the origin of the window frame. 593 */ 594 public final Region touchableRegion = new Region(); 595 596 /** 597 * Option for {@link #touchableInsets}: the entire window frame 598 * can be touched. 599 */ 600 public static final int TOUCHABLE_INSETS_FRAME 601 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; 602 603 /** 604 * Option for {@link #touchableInsets}: the area inside of 605 * the content insets can be touched. 606 */ 607 public static final int TOUCHABLE_INSETS_CONTENT 608 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; 609 610 /** 611 * Option for {@link #touchableInsets}: the area inside of 612 * the visible insets can be touched. 613 */ 614 public static final int TOUCHABLE_INSETS_VISIBLE 615 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE; 616 617 /** 618 * Option for {@link #touchableInsets}: the region specified by 619 * {@link #touchableRegion} can be touched. 620 */ 621 public static final int TOUCHABLE_INSETS_REGION 622 = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; 623 624 /** 625 * Determine which area of the window is touchable by the user. May 626 * be one of: {@link #TOUCHABLE_INSETS_FRAME}, 627 * {@link #TOUCHABLE_INSETS_CONTENT}, {@link #TOUCHABLE_INSETS_VISIBLE}, 628 * or {@link #TOUCHABLE_INSETS_REGION}. 629 */ 630 public int touchableInsets; 631 } 632 633 /** 634 * You can call this to customize the theme used by your IME's window. 635 * This theme should typically be one that derives from 636 * {@link android.R.style#Theme_InputMethod}, which is the default theme 637 * you will get. This must be set before {@link #onCreate}, so you 638 * will typically call it in your constructor with the resource ID 639 * of your custom theme. 640 */ 641 @Override setTheme(int theme)642 public void setTheme(int theme) { 643 if (mWindow != null) { 644 throw new IllegalStateException("Must be called before onCreate()"); 645 } 646 mTheme = theme; 647 } 648 649 /** 650 * You can call this to try to enable hardware accelerated drawing for 651 * your IME. This must be set before {@link #onCreate}, so you 652 * will typically call it in your constructor. It is not always possible 653 * to use hardware accelerated drawing in an IME (for example on low-end 654 * devices that do not have the resources to support this), so the call 655 * returns true if it succeeds otherwise false if you will need to draw 656 * in software. You must be able to handle either case. 657 * 658 * @deprecated Starting in API 21, hardware acceleration is always enabled 659 * on capable devices. 660 */ enableHardwareAcceleration()661 public boolean enableHardwareAcceleration() { 662 if (mWindow != null) { 663 throw new IllegalStateException("Must be called before onCreate()"); 664 } 665 if (ActivityManager.isHighEndGfx()) { 666 mHardwareAccelerated = true; 667 return true; 668 } 669 return false; 670 } 671 onCreate()672 @Override public void onCreate() { 673 mTheme = Resources.selectSystemTheme(mTheme, 674 getApplicationInfo().targetSdkVersion, 675 android.R.style.Theme_InputMethod, 676 android.R.style.Theme_Holo_InputMethod, 677 android.R.style.Theme_DeviceDefault_InputMethod, 678 android.R.style.Theme_DeviceDefault_InputMethod); 679 super.setTheme(mTheme); 680 super.onCreate(); 681 mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); 682 // If the previous IME has occupied non-empty inset in the screen, we need to decide whether 683 // we continue to use the same size of the inset or update it 684 mShouldClearInsetOfPreviousIme = (mImm.getInputMethodWindowVisibleHeight() > 0); 685 mInflater = (LayoutInflater)getSystemService( 686 Context.LAYOUT_INFLATER_SERVICE); 687 mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, 688 WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false); 689 if (mHardwareAccelerated) { 690 mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 691 } 692 initViews(); 693 mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); 694 } 695 696 /** 697 * This is a hook that subclasses can use to perform initialization of 698 * their interface. It is called for you prior to any of your UI objects 699 * being created, both after the service is first created and after a 700 * configuration change happens. 701 */ onInitializeInterface()702 public void onInitializeInterface() { 703 // Intentionally empty 704 } 705 initialize()706 void initialize() { 707 if (!mInitialized) { 708 mInitialized = true; 709 onInitializeInterface(); 710 } 711 } 712 initViews()713 void initViews() { 714 mInitialized = false; 715 mWindowCreated = false; 716 mShowInputRequested = false; 717 mShowInputForced = false; 718 719 mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService); 720 mRootView = mInflater.inflate( 721 com.android.internal.R.layout.input_method, null); 722 mRootView.setSystemUiVisibility( 723 View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 724 mWindow.setContentView(mRootView); 725 mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsComputer); 726 mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); 727 if (Settings.Global.getInt(getContentResolver(), 728 Settings.Global.FANCY_IME_ANIMATIONS, 0) != 0) { 729 mWindow.getWindow().setWindowAnimations( 730 com.android.internal.R.style.Animation_InputMethodFancy); 731 } 732 mFullscreenArea = (ViewGroup)mRootView.findViewById(com.android.internal.R.id.fullscreenArea); 733 mExtractViewHidden = false; 734 mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea); 735 mExtractView = null; 736 mExtractEditText = null; 737 mExtractAccessories = null; 738 mExtractAction = null; 739 mFullscreenApplied = false; 740 741 mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea); 742 mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea); 743 mInputView = null; 744 mIsInputViewShown = false; 745 746 mExtractFrame.setVisibility(View.GONE); 747 mCandidatesVisibility = getCandidatesHiddenVisibility(); 748 mCandidatesFrame.setVisibility(mCandidatesVisibility); 749 mInputFrame.setVisibility(View.GONE); 750 } 751 onDestroy()752 @Override public void onDestroy() { 753 super.onDestroy(); 754 mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( 755 mInsetsComputer); 756 doFinishInput(); 757 if (mWindowAdded) { 758 // Disable exit animation for the current IME window 759 // to avoid the race condition between the exit and enter animations 760 // when the current IME is being switched to another one. 761 mWindow.getWindow().setWindowAnimations(0); 762 mWindow.dismiss(); 763 } 764 } 765 766 /** 767 * Take care of handling configuration changes. Subclasses of 768 * InputMethodService generally don't need to deal directly with 769 * this on their own; the standard implementation here takes care of 770 * regenerating the input method UI as a result of the configuration 771 * change, so you can rely on your {@link #onCreateInputView} and 772 * other methods being called as appropriate due to a configuration change. 773 * 774 * <p>When a configuration change does happen, 775 * {@link #onInitializeInterface()} is guaranteed to be called the next 776 * time prior to any of the other input or UI creation callbacks. The 777 * following will be called immediately depending if appropriate for current 778 * state: {@link #onStartInput} if input is active, and 779 * {@link #onCreateInputView} and {@link #onStartInputView} and related 780 * appropriate functions if the UI is displayed. 781 */ onConfigurationChanged(Configuration newConfig)782 @Override public void onConfigurationChanged(Configuration newConfig) { 783 super.onConfigurationChanged(newConfig); 784 785 boolean visible = mWindowVisible; 786 int showFlags = mShowInputFlags; 787 boolean showingInput = mShowInputRequested; 788 CompletionInfo[] completions = mCurCompletions; 789 initViews(); 790 mInputViewStarted = false; 791 mCandidatesViewStarted = false; 792 if (mInputStarted) { 793 doStartInput(getCurrentInputConnection(), 794 getCurrentInputEditorInfo(), true); 795 } 796 if (visible) { 797 if (showingInput) { 798 // If we were last showing the soft keyboard, try to do so again. 799 if (onShowInputRequested(showFlags, true)) { 800 showWindow(true); 801 if (completions != null) { 802 mCurCompletions = completions; 803 onDisplayCompletions(completions); 804 } 805 } else { 806 doHideWindow(); 807 } 808 } else if (mCandidatesVisibility == View.VISIBLE) { 809 // If the candidates are currently visible, make sure the 810 // window is shown for them. 811 showWindow(false); 812 } else { 813 // Otherwise hide the window. 814 doHideWindow(); 815 } 816 // If user uses hard keyboard, IME button should always be shown. 817 boolean showing = onEvaluateInputViewShown(); 818 mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0), 819 mBackDisposition); 820 } 821 } 822 823 /** 824 * Implement to return our standard {@link InputMethodImpl}. Subclasses 825 * can override to provide their own customized version. 826 */ 827 @Override onCreateInputMethodInterface()828 public AbstractInputMethodImpl onCreateInputMethodInterface() { 829 return new InputMethodImpl(); 830 } 831 832 /** 833 * Implement to return our standard {@link InputMethodSessionImpl}. Subclasses 834 * can override to provide their own customized version. 835 */ 836 @Override onCreateInputMethodSessionInterface()837 public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() { 838 return new InputMethodSessionImpl(); 839 } 840 getLayoutInflater()841 public LayoutInflater getLayoutInflater() { 842 return mInflater; 843 } 844 getWindow()845 public Dialog getWindow() { 846 return mWindow; 847 } 848 setBackDisposition(int disposition)849 public void setBackDisposition(int disposition) { 850 mBackDisposition = disposition; 851 } 852 getBackDisposition()853 public int getBackDisposition() { 854 return mBackDisposition; 855 } 856 857 /** 858 * Return the maximum width, in pixels, available the input method. 859 * Input methods are positioned at the bottom of the screen and, unless 860 * running in fullscreen, will generally want to be as short as possible 861 * so should compute their height based on their contents. However, they 862 * can stretch as much as needed horizontally. The function returns to 863 * you the maximum amount of space available horizontally, which you can 864 * use if needed for UI placement. 865 * 866 * <p>In many cases this is not needed, you can just rely on the normal 867 * view layout mechanisms to position your views within the full horizontal 868 * space given to the input method. 869 * 870 * <p>Note that this value can change dynamically, in particular when the 871 * screen orientation changes. 872 */ getMaxWidth()873 public int getMaxWidth() { 874 WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 875 return wm.getDefaultDisplay().getWidth(); 876 } 877 878 /** 879 * Return the currently active InputBinding for the input method, or 880 * null if there is none. 881 */ getCurrentInputBinding()882 public InputBinding getCurrentInputBinding() { 883 return mInputBinding; 884 } 885 886 /** 887 * Retrieve the currently active InputConnection that is bound to 888 * the input method, or null if there is none. 889 */ getCurrentInputConnection()890 public InputConnection getCurrentInputConnection() { 891 InputConnection ic = mStartedInputConnection; 892 if (ic != null) { 893 return ic; 894 } 895 return mInputConnection; 896 } 897 getCurrentInputStarted()898 public boolean getCurrentInputStarted() { 899 return mInputStarted; 900 } 901 getCurrentInputEditorInfo()902 public EditorInfo getCurrentInputEditorInfo() { 903 return mInputEditorInfo; 904 } 905 906 /** 907 * Re-evaluate whether the input method should be running in fullscreen 908 * mode, and update its UI if this has changed since the last time it 909 * was evaluated. This will call {@link #onEvaluateFullscreenMode()} to 910 * determine whether it should currently run in fullscreen mode. You 911 * can use {@link #isFullscreenMode()} to determine if the input method 912 * is currently running in fullscreen mode. 913 */ updateFullscreenMode()914 public void updateFullscreenMode() { 915 boolean isFullscreen = mShowInputRequested && onEvaluateFullscreenMode(); 916 boolean changed = mLastShowInputRequested != mShowInputRequested; 917 if (mIsFullscreen != isFullscreen || !mFullscreenApplied) { 918 changed = true; 919 mIsFullscreen = isFullscreen; 920 InputConnection ic = getCurrentInputConnection(); 921 if (ic != null) ic.reportFullscreenMode(isFullscreen); 922 mFullscreenApplied = true; 923 initialize(); 924 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) 925 mFullscreenArea.getLayoutParams(); 926 if (isFullscreen) { 927 mFullscreenArea.setBackgroundDrawable(mThemeAttrs.getDrawable( 928 com.android.internal.R.styleable.InputMethodService_imeFullscreenBackground)); 929 lp.height = 0; 930 lp.weight = 1; 931 } else { 932 mFullscreenArea.setBackgroundDrawable(null); 933 lp.height = LinearLayout.LayoutParams.WRAP_CONTENT; 934 lp.weight = 0; 935 } 936 ((ViewGroup)mFullscreenArea.getParent()).updateViewLayout( 937 mFullscreenArea, lp); 938 if (isFullscreen) { 939 if (mExtractView == null) { 940 View v = onCreateExtractTextView(); 941 if (v != null) { 942 setExtractView(v); 943 } 944 } 945 startExtractingText(false); 946 } 947 updateExtractFrameVisibility(); 948 } 949 950 if (changed) { 951 onConfigureWindow(mWindow.getWindow(), isFullscreen, !mShowInputRequested); 952 mLastShowInputRequested = mShowInputRequested; 953 } 954 } 955 956 /** 957 * Update the given window's parameters for the given mode. This is called 958 * when the window is first displayed and each time the fullscreen or 959 * candidates only mode changes. 960 * 961 * <p>The default implementation makes the layout for the window 962 * MATCH_PARENT x MATCH_PARENT when in fullscreen mode, and 963 * MATCH_PARENT x WRAP_CONTENT when in non-fullscreen mode. 964 * 965 * @param win The input method's window. 966 * @param isFullscreen If true, the window is running in fullscreen mode 967 * and intended to cover the entire application display. 968 * @param isCandidatesOnly If true, the window is only showing the 969 * candidates view and none of the rest of its UI. This is mutually 970 * exclusive with fullscreen mode. 971 */ onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly)972 public void onConfigureWindow(Window win, boolean isFullscreen, 973 boolean isCandidatesOnly) { 974 final int currentHeight = mWindow.getWindow().getAttributes().height; 975 final int newHeight = isFullscreen ? MATCH_PARENT : WRAP_CONTENT; 976 if (mIsInputViewShown && currentHeight != newHeight) { 977 Log.w(TAG, "Window size has been changed. This may cause jankiness of resizing window: " 978 + currentHeight + " -> " + newHeight); 979 } 980 mWindow.getWindow().setLayout(MATCH_PARENT, newHeight); 981 } 982 983 /** 984 * Return whether the input method is <em>currently</em> running in 985 * fullscreen mode. This is the mode that was last determined and 986 * applied by {@link #updateFullscreenMode()}. 987 */ isFullscreenMode()988 public boolean isFullscreenMode() { 989 return mIsFullscreen; 990 } 991 992 /** 993 * Override this to control when the input method should run in 994 * fullscreen mode. The default implementation runs in fullsceen only 995 * when the screen is in landscape mode. If you change what 996 * this returns, you will need to call {@link #updateFullscreenMode()} 997 * yourself whenever the returned value may have changed to have it 998 * re-evaluated and applied. 999 */ onEvaluateFullscreenMode()1000 public boolean onEvaluateFullscreenMode() { 1001 Configuration config = getResources().getConfiguration(); 1002 if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) { 1003 return false; 1004 } 1005 if (mInputEditorInfo != null 1006 && (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0) { 1007 return false; 1008 } 1009 return true; 1010 } 1011 1012 /** 1013 * Controls the visibility of the extracted text area. This only applies 1014 * when the input method is in fullscreen mode, and thus showing extracted 1015 * text. When false, the extracted text will not be shown, allowing some 1016 * of the application to be seen behind. This is normally set for you 1017 * by {@link #onUpdateExtractingVisibility}. This controls the visibility 1018 * of both the extracted text and candidate view; the latter since it is 1019 * not useful if there is no text to see. 1020 */ setExtractViewShown(boolean shown)1021 public void setExtractViewShown(boolean shown) { 1022 if (mExtractViewHidden == shown) { 1023 mExtractViewHidden = !shown; 1024 updateExtractFrameVisibility(); 1025 } 1026 } 1027 1028 /** 1029 * Return whether the fullscreen extract view is shown. This will only 1030 * return true if {@link #isFullscreenMode()} returns true, and in that 1031 * case its value depends on the last call to 1032 * {@link #setExtractViewShown(boolean)}. This effectively lets you 1033 * determine if the application window is entirely covered (when this 1034 * returns true) or if some part of it may be shown (if this returns 1035 * false, though if {@link #isFullscreenMode()} returns true in that case 1036 * then it is probably only a sliver of the application). 1037 */ isExtractViewShown()1038 public boolean isExtractViewShown() { 1039 return mIsFullscreen && !mExtractViewHidden; 1040 } 1041 updateExtractFrameVisibility()1042 void updateExtractFrameVisibility() { 1043 final int vis; 1044 if (isFullscreenMode()) { 1045 vis = mExtractViewHidden ? View.INVISIBLE : View.VISIBLE; 1046 // "vis" should be applied for the extract frame as well in the fullscreen mode. 1047 mExtractFrame.setVisibility(vis); 1048 } else { 1049 vis = View.VISIBLE; 1050 mExtractFrame.setVisibility(View.GONE); 1051 } 1052 updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE); 1053 if (mWindowWasVisible && mFullscreenArea.getVisibility() != vis) { 1054 int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE 1055 ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation 1056 : com.android.internal.R.styleable.InputMethodService_imeExtractExitAnimation, 1057 0); 1058 if (animRes != 0) { 1059 mFullscreenArea.startAnimation(AnimationUtils.loadAnimation( 1060 this, animRes)); 1061 } 1062 } 1063 mFullscreenArea.setVisibility(vis); 1064 } 1065 1066 /** 1067 * Compute the interesting insets into your UI. The default implementation 1068 * uses the top of the candidates frame for the visible insets, and the 1069 * top of the input frame for the content insets. The default touchable 1070 * insets are {@link Insets#TOUCHABLE_INSETS_VISIBLE}. 1071 * 1072 * <p>Note that this method is not called when 1073 * {@link #isExtractViewShown} returns true, since 1074 * in that case the application is left as-is behind the input method and 1075 * not impacted by anything in its UI. 1076 * 1077 * @param outInsets Fill in with the current UI insets. 1078 */ onComputeInsets(Insets outInsets)1079 public void onComputeInsets(Insets outInsets) { 1080 int[] loc = mTmpLocation; 1081 if (mInputFrame.getVisibility() == View.VISIBLE) { 1082 mInputFrame.getLocationInWindow(loc); 1083 } else { 1084 View decor = getWindow().getWindow().getDecorView(); 1085 loc[1] = decor.getHeight(); 1086 } 1087 if (isFullscreenMode()) { 1088 // In fullscreen mode, we never resize the underlying window. 1089 View decor = getWindow().getWindow().getDecorView(); 1090 outInsets.contentTopInsets = decor.getHeight(); 1091 } else { 1092 outInsets.contentTopInsets = loc[1]; 1093 } 1094 if (mCandidatesFrame.getVisibility() == View.VISIBLE) { 1095 mCandidatesFrame.getLocationInWindow(loc); 1096 } 1097 outInsets.visibleTopInsets = loc[1]; 1098 outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE; 1099 outInsets.touchableRegion.setEmpty(); 1100 } 1101 1102 /** 1103 * Re-evaluate whether the soft input area should currently be shown, and 1104 * update its UI if this has changed since the last time it 1105 * was evaluated. This will call {@link #onEvaluateInputViewShown()} to 1106 * determine whether the input view should currently be shown. You 1107 * can use {@link #isInputViewShown()} to determine if the input view 1108 * is currently shown. 1109 */ updateInputViewShown()1110 public void updateInputViewShown() { 1111 boolean isShown = mShowInputRequested && onEvaluateInputViewShown(); 1112 if (mIsInputViewShown != isShown && mWindowVisible) { 1113 mIsInputViewShown = isShown; 1114 mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE); 1115 if (mInputView == null) { 1116 initialize(); 1117 View v = onCreateInputView(); 1118 if (v != null) { 1119 setInputView(v); 1120 } 1121 } 1122 } 1123 } 1124 1125 /** 1126 * Returns true if we have been asked to show our input view. 1127 */ isShowInputRequested()1128 public boolean isShowInputRequested() { 1129 return mShowInputRequested; 1130 } 1131 1132 /** 1133 * Return whether the soft input view is <em>currently</em> shown to the 1134 * user. This is the state that was last determined and 1135 * applied by {@link #updateInputViewShown()}. 1136 */ isInputViewShown()1137 public boolean isInputViewShown() { 1138 return mIsInputViewShown && mWindowVisible; 1139 } 1140 1141 /** 1142 * Override this to control when the soft input area should be shown to 1143 * the user. The default implementation only shows the input view when 1144 * there is no hard keyboard or the keyboard is hidden. If you change what 1145 * this returns, you will need to call {@link #updateInputViewShown()} 1146 * yourself whenever the returned value may have changed to have it 1147 * re-evaluated and applied. 1148 */ onEvaluateInputViewShown()1149 public boolean onEvaluateInputViewShown() { 1150 Configuration config = getResources().getConfiguration(); 1151 return config.keyboard == Configuration.KEYBOARD_NOKEYS 1152 || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES; 1153 } 1154 1155 /** 1156 * Controls the visibility of the candidates display area. By default 1157 * it is hidden. 1158 */ setCandidatesViewShown(boolean shown)1159 public void setCandidatesViewShown(boolean shown) { 1160 updateCandidatesVisibility(shown); 1161 if (!mShowInputRequested && mWindowVisible != shown) { 1162 // If we are being asked to show the candidates view while the app 1163 // has not asked for the input view to be shown, then we need 1164 // to update whether the window is shown. 1165 if (shown) { 1166 showWindow(false); 1167 } else { 1168 doHideWindow(); 1169 } 1170 } 1171 } 1172 updateCandidatesVisibility(boolean shown)1173 void updateCandidatesVisibility(boolean shown) { 1174 int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility(); 1175 if (mCandidatesVisibility != vis) { 1176 mCandidatesFrame.setVisibility(vis); 1177 mCandidatesVisibility = vis; 1178 } 1179 } 1180 1181 /** 1182 * Returns the visibility mode (either {@link View#INVISIBLE View.INVISIBLE} 1183 * or {@link View#GONE View.GONE}) of the candidates view when it is not 1184 * shown. The default implementation returns GONE when 1185 * {@link #isExtractViewShown} returns true, 1186 * otherwise VISIBLE. Be careful if you change this to return GONE in 1187 * other situations -- if showing or hiding the candidates view causes 1188 * your window to resize, this can cause temporary drawing artifacts as 1189 * the resize takes place. 1190 */ getCandidatesHiddenVisibility()1191 public int getCandidatesHiddenVisibility() { 1192 return isExtractViewShown() ? View.GONE : View.INVISIBLE; 1193 } 1194 showStatusIcon(@rawableRes int iconResId)1195 public void showStatusIcon(@DrawableRes int iconResId) { 1196 mStatusIcon = iconResId; 1197 mImm.showStatusIcon(mToken, getPackageName(), iconResId); 1198 } 1199 hideStatusIcon()1200 public void hideStatusIcon() { 1201 mStatusIcon = 0; 1202 mImm.hideStatusIcon(mToken); 1203 } 1204 1205 /** 1206 * Force switch to a new input method, as identified by <var>id</var>. This 1207 * input method will be destroyed, and the requested one started on the 1208 * current input field. 1209 * 1210 * @param id Unique identifier of the new input method ot start. 1211 */ switchInputMethod(String id)1212 public void switchInputMethod(String id) { 1213 mImm.setInputMethod(mToken, id); 1214 } 1215 setExtractView(View view)1216 public void setExtractView(View view) { 1217 mExtractFrame.removeAllViews(); 1218 mExtractFrame.addView(view, new FrameLayout.LayoutParams( 1219 ViewGroup.LayoutParams.MATCH_PARENT, 1220 ViewGroup.LayoutParams.MATCH_PARENT)); 1221 mExtractView = view; 1222 if (view != null) { 1223 mExtractEditText = (ExtractEditText)view.findViewById( 1224 com.android.internal.R.id.inputExtractEditText); 1225 mExtractEditText.setIME(this); 1226 mExtractAction = (Button)view.findViewById( 1227 com.android.internal.R.id.inputExtractAction); 1228 if (mExtractAction != null) { 1229 mExtractAccessories = (ViewGroup)view.findViewById( 1230 com.android.internal.R.id.inputExtractAccessories); 1231 } 1232 startExtractingText(false); 1233 } else { 1234 mExtractEditText = null; 1235 mExtractAccessories = null; 1236 mExtractAction = null; 1237 } 1238 } 1239 1240 /** 1241 * Replaces the current candidates view with a new one. You only need to 1242 * call this when dynamically changing the view; normally, you should 1243 * implement {@link #onCreateCandidatesView()} and create your view when 1244 * first needed by the input method. 1245 */ setCandidatesView(View view)1246 public void setCandidatesView(View view) { 1247 mCandidatesFrame.removeAllViews(); 1248 mCandidatesFrame.addView(view, new FrameLayout.LayoutParams( 1249 ViewGroup.LayoutParams.MATCH_PARENT, 1250 ViewGroup.LayoutParams.WRAP_CONTENT)); 1251 } 1252 1253 /** 1254 * Replaces the current input view with a new one. You only need to 1255 * call this when dynamically changing the view; normally, you should 1256 * implement {@link #onCreateInputView()} and create your view when 1257 * first needed by the input method. 1258 */ setInputView(View view)1259 public void setInputView(View view) { 1260 mInputFrame.removeAllViews(); 1261 mInputFrame.addView(view, new FrameLayout.LayoutParams( 1262 ViewGroup.LayoutParams.MATCH_PARENT, 1263 ViewGroup.LayoutParams.WRAP_CONTENT)); 1264 mInputView = view; 1265 } 1266 1267 /** 1268 * Called by the framework to create the layout for showing extacted text. 1269 * Only called when in fullscreen mode. The returned view hierarchy must 1270 * have an {@link ExtractEditText} whose ID is 1271 * {@link android.R.id#inputExtractEditText}. 1272 */ onCreateExtractTextView()1273 public View onCreateExtractTextView() { 1274 return mInflater.inflate( 1275 com.android.internal.R.layout.input_method_extract_view, null); 1276 } 1277 1278 /** 1279 * Create and return the view hierarchy used to show candidates. This will 1280 * be called once, when the candidates are first displayed. You can return 1281 * null to have no candidates view; the default implementation returns null. 1282 * 1283 * <p>To control when the candidates view is displayed, use 1284 * {@link #setCandidatesViewShown(boolean)}. 1285 * To change the candidates view after the first one is created by this 1286 * function, use {@link #setCandidatesView(View)}. 1287 */ onCreateCandidatesView()1288 public View onCreateCandidatesView() { 1289 return null; 1290 } 1291 1292 /** 1293 * Create and return the view hierarchy used for the input area (such as 1294 * a soft keyboard). This will be called once, when the input area is 1295 * first displayed. You can return null to have no input area; the default 1296 * implementation returns null. 1297 * 1298 * <p>To control when the input view is displayed, implement 1299 * {@link #onEvaluateInputViewShown()}. 1300 * To change the input view after the first one is created by this 1301 * function, use {@link #setInputView(View)}. 1302 */ onCreateInputView()1303 public View onCreateInputView() { 1304 return null; 1305 } 1306 1307 /** 1308 * Called when the input view is being shown and input has started on 1309 * a new editor. This will always be called after {@link #onStartInput}, 1310 * allowing you to do your general setup there and just view-specific 1311 * setup here. You are guaranteed that {@link #onCreateInputView()} will 1312 * have been called some time before this function is called. 1313 * 1314 * @param info Description of the type of text being edited. 1315 * @param restarting Set to true if we are restarting input on the 1316 * same text field as before. 1317 */ onStartInputView(EditorInfo info, boolean restarting)1318 public void onStartInputView(EditorInfo info, boolean restarting) { 1319 // Intentionally empty 1320 } 1321 1322 /** 1323 * Called when the input view is being hidden from the user. This will 1324 * be called either prior to hiding the window, or prior to switching to 1325 * another target for editing. 1326 * 1327 * <p>The default 1328 * implementation uses the InputConnection to clear any active composing 1329 * text; you can override this (not calling the base class implementation) 1330 * to perform whatever behavior you would like. 1331 * 1332 * @param finishingInput If true, {@link #onFinishInput} will be 1333 * called immediately after. 1334 */ onFinishInputView(boolean finishingInput)1335 public void onFinishInputView(boolean finishingInput) { 1336 if (!finishingInput) { 1337 InputConnection ic = getCurrentInputConnection(); 1338 if (ic != null) { 1339 ic.finishComposingText(); 1340 } 1341 } 1342 } 1343 1344 /** 1345 * Called when only the candidates view has been shown for showing 1346 * processing as the user enters text through a hard keyboard. 1347 * This will always be called after {@link #onStartInput}, 1348 * allowing you to do your general setup there and just view-specific 1349 * setup here. You are guaranteed that {@link #onCreateCandidatesView()} 1350 * will have been called some time before this function is called. 1351 * 1352 * <p>Note that this will <em>not</em> be called when the input method 1353 * is running in full editing mode, and thus receiving 1354 * {@link #onStartInputView} to initiate that operation. This is only 1355 * for the case when candidates are being shown while the input method 1356 * editor is hidden but wants to show its candidates UI as text is 1357 * entered through some other mechanism. 1358 * 1359 * @param info Description of the type of text being edited. 1360 * @param restarting Set to true if we are restarting input on the 1361 * same text field as before. 1362 */ onStartCandidatesView(EditorInfo info, boolean restarting)1363 public void onStartCandidatesView(EditorInfo info, boolean restarting) { 1364 // Intentionally empty 1365 } 1366 1367 /** 1368 * Called when the candidates view is being hidden from the user. This will 1369 * be called either prior to hiding the window, or prior to switching to 1370 * another target for editing. 1371 * 1372 * <p>The default 1373 * implementation uses the InputConnection to clear any active composing 1374 * text; you can override this (not calling the base class implementation) 1375 * to perform whatever behavior you would like. 1376 * 1377 * @param finishingInput If true, {@link #onFinishInput} will be 1378 * called immediately after. 1379 */ onFinishCandidatesView(boolean finishingInput)1380 public void onFinishCandidatesView(boolean finishingInput) { 1381 if (!finishingInput) { 1382 InputConnection ic = getCurrentInputConnection(); 1383 if (ic != null) { 1384 ic.finishComposingText(); 1385 } 1386 } 1387 } 1388 1389 /** 1390 * The system has decided that it may be time to show your input method. 1391 * This is called due to a corresponding call to your 1392 * {@link InputMethod#showSoftInput InputMethod.showSoftInput()} 1393 * method. The default implementation uses 1394 * {@link #onEvaluateInputViewShown()}, {@link #onEvaluateFullscreenMode()}, 1395 * and the current configuration to decide whether the input view should 1396 * be shown at this point. 1397 * 1398 * @param flags Provides additional information about the show request, 1399 * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}. 1400 * @param configChange This is true if we are re-showing due to a 1401 * configuration change. 1402 * @return Returns true to indicate that the window should be shown. 1403 */ onShowInputRequested(int flags, boolean configChange)1404 public boolean onShowInputRequested(int flags, boolean configChange) { 1405 if (!onEvaluateInputViewShown()) { 1406 return false; 1407 } 1408 if ((flags&InputMethod.SHOW_EXPLICIT) == 0) { 1409 if (!configChange && onEvaluateFullscreenMode()) { 1410 // Don't show if this is not explicitly requested by the user and 1411 // the input method is fullscreen. That would be too disruptive. 1412 // However, we skip this change for a config change, since if 1413 // the IME is already shown we do want to go into fullscreen 1414 // mode at this point. 1415 return false; 1416 } 1417 Configuration config = getResources().getConfiguration(); 1418 if (config.keyboard != Configuration.KEYBOARD_NOKEYS) { 1419 // And if the device has a hard keyboard, even if it is 1420 // currently hidden, don't show the input method implicitly. 1421 // These kinds of devices don't need it that much. 1422 return false; 1423 } 1424 } 1425 if ((flags&InputMethod.SHOW_FORCED) != 0) { 1426 mShowInputForced = true; 1427 } 1428 return true; 1429 } 1430 showWindow(boolean showInput)1431 public void showWindow(boolean showInput) { 1432 if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput 1433 + " mShowInputRequested=" + mShowInputRequested 1434 + " mWindowAdded=" + mWindowAdded 1435 + " mWindowCreated=" + mWindowCreated 1436 + " mWindowVisible=" + mWindowVisible 1437 + " mInputStarted=" + mInputStarted); 1438 1439 if (mInShowWindow) { 1440 Log.w(TAG, "Re-entrance in to showWindow"); 1441 return; 1442 } 1443 1444 try { 1445 mWindowWasVisible = mWindowVisible; 1446 mInShowWindow = true; 1447 showWindowInner(showInput); 1448 } finally { 1449 mWindowWasVisible = true; 1450 mInShowWindow = false; 1451 } 1452 } 1453 showWindowInner(boolean showInput)1454 void showWindowInner(boolean showInput) { 1455 boolean doShowInput = false; 1456 final int previousImeWindowStatus = 1457 (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0); 1458 mWindowVisible = true; 1459 if (!mShowInputRequested) { 1460 if (mInputStarted) { 1461 if (showInput) { 1462 doShowInput = true; 1463 mShowInputRequested = true; 1464 } 1465 } 1466 } else { 1467 showInput = true; 1468 } 1469 1470 if (DEBUG) Log.v(TAG, "showWindow: updating UI"); 1471 initialize(); 1472 updateFullscreenMode(); 1473 updateInputViewShown(); 1474 1475 if (!mWindowAdded || !mWindowCreated) { 1476 mWindowAdded = true; 1477 mWindowCreated = true; 1478 initialize(); 1479 if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView"); 1480 View v = onCreateCandidatesView(); 1481 if (DEBUG) Log.v(TAG, "showWindow: candidates=" + v); 1482 if (v != null) { 1483 setCandidatesView(v); 1484 } 1485 } 1486 if (mShowInputRequested) { 1487 if (!mInputViewStarted) { 1488 if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); 1489 mInputViewStarted = true; 1490 onStartInputView(mInputEditorInfo, false); 1491 } 1492 } else if (!mCandidatesViewStarted) { 1493 if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView"); 1494 mCandidatesViewStarted = true; 1495 onStartCandidatesView(mInputEditorInfo, false); 1496 } 1497 1498 if (doShowInput) { 1499 startExtractingText(false); 1500 } 1501 1502 final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0); 1503 if (previousImeWindowStatus != nextImeWindowStatus) { 1504 mImm.setImeWindowStatus(mToken, nextImeWindowStatus, mBackDisposition); 1505 } 1506 if ((previousImeWindowStatus & IME_ACTIVE) == 0) { 1507 if (DEBUG) Log.v(TAG, "showWindow: showing!"); 1508 onWindowShown(); 1509 mWindow.show(); 1510 // Put here rather than in onWindowShown() in case people forget to call 1511 // super.onWindowShown(). 1512 mShouldClearInsetOfPreviousIme = false; 1513 } 1514 } 1515 finishViews()1516 private void finishViews() { 1517 if (mInputViewStarted) { 1518 if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); 1519 onFinishInputView(false); 1520 } else if (mCandidatesViewStarted) { 1521 if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); 1522 onFinishCandidatesView(false); 1523 } 1524 mInputViewStarted = false; 1525 mCandidatesViewStarted = false; 1526 } 1527 doHideWindow()1528 private void doHideWindow() { 1529 mImm.setImeWindowStatus(mToken, 0, mBackDisposition); 1530 hideWindow(); 1531 } 1532 hideWindow()1533 public void hideWindow() { 1534 finishViews(); 1535 if (mWindowVisible) { 1536 mWindow.hide(); 1537 mWindowVisible = false; 1538 onWindowHidden(); 1539 mWindowWasVisible = false; 1540 } 1541 } 1542 1543 /** 1544 * Called when the input method window has been shown to the user, after 1545 * previously not being visible. This is done after all of the UI setup 1546 * for the window has occurred (creating its views etc). 1547 */ onWindowShown()1548 public void onWindowShown() { 1549 // Intentionally empty 1550 } 1551 1552 /** 1553 * Called when the input method window has been hidden from the user, 1554 * after previously being visible. 1555 */ onWindowHidden()1556 public void onWindowHidden() { 1557 // Intentionally empty 1558 } 1559 1560 /** 1561 * Reset the inset occupied the previous IME when and only when 1562 * {@link #mShouldClearInsetOfPreviousIme} is {@code true}. 1563 */ clearInsetOfPreviousIme()1564 private void clearInsetOfPreviousIme() { 1565 if (DEBUG) Log.v(TAG, "clearInsetOfPreviousIme() " 1566 + " mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme); 1567 if (!mShouldClearInsetOfPreviousIme || mWindow == null) return; 1568 try { 1569 // We do not call onWindowShown() and onWindowHidden() so as not to make the IME author 1570 // confused. 1571 // TODO: Find out a better way which has less side-effect. 1572 mWindow.show(); 1573 mWindow.hide(); 1574 } catch (WindowManager.BadTokenException e) { 1575 if (DEBUG) Log.v(TAG, "clearInsetOfPreviousIme: BadTokenException: IME is done."); 1576 mWindowVisible = false; 1577 mWindowAdded = false; 1578 } 1579 mShouldClearInsetOfPreviousIme = false; 1580 } 1581 1582 /** 1583 * Called when a new client has bound to the input method. This 1584 * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)} 1585 * and {@link #onFinishInput()} calls as the user navigates through its 1586 * UI. Upon this call you know that {@link #getCurrentInputBinding} 1587 * and {@link #getCurrentInputConnection} return valid objects. 1588 */ onBindInput()1589 public void onBindInput() { 1590 // Intentionally empty 1591 } 1592 1593 /** 1594 * Called when the previous bound client is no longer associated 1595 * with the input method. After returning {@link #getCurrentInputBinding} 1596 * and {@link #getCurrentInputConnection} will no longer return 1597 * valid objects. 1598 */ onUnbindInput()1599 public void onUnbindInput() { 1600 // Intentionally empty 1601 } 1602 1603 /** 1604 * Called to inform the input method that text input has started in an 1605 * editor. You should use this callback to initialize the state of your 1606 * input to match the state of the editor given to it. 1607 * 1608 * @param attribute The attributes of the editor that input is starting 1609 * in. 1610 * @param restarting Set to true if input is restarting in the same 1611 * editor such as because the application has changed the text in 1612 * the editor. Otherwise will be false, indicating this is a new 1613 * session with the editor. 1614 */ onStartInput(EditorInfo attribute, boolean restarting)1615 public void onStartInput(EditorInfo attribute, boolean restarting) { 1616 // Intentionally empty 1617 } 1618 doFinishInput()1619 void doFinishInput() { 1620 if (mInputViewStarted) { 1621 if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); 1622 onFinishInputView(true); 1623 } else if (mCandidatesViewStarted) { 1624 if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); 1625 onFinishCandidatesView(true); 1626 } 1627 mInputViewStarted = false; 1628 mCandidatesViewStarted = false; 1629 if (mInputStarted) { 1630 if (DEBUG) Log.v(TAG, "CALL: onFinishInput"); 1631 onFinishInput(); 1632 } 1633 mInputStarted = false; 1634 mStartedInputConnection = null; 1635 mCurCompletions = null; 1636 } 1637 doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting)1638 void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { 1639 if (!restarting) { 1640 doFinishInput(); 1641 } 1642 mInputStarted = true; 1643 mStartedInputConnection = ic; 1644 mInputEditorInfo = attribute; 1645 initialize(); 1646 if (DEBUG) Log.v(TAG, "CALL: onStartInput"); 1647 onStartInput(attribute, restarting); 1648 if (mWindowVisible) { 1649 if (mShowInputRequested) { 1650 if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); 1651 mInputViewStarted = true; 1652 onStartInputView(mInputEditorInfo, restarting); 1653 startExtractingText(true); 1654 } else if (mCandidatesVisibility == View.VISIBLE) { 1655 if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView"); 1656 mCandidatesViewStarted = true; 1657 onStartCandidatesView(mInputEditorInfo, restarting); 1658 } 1659 } 1660 } 1661 1662 /** 1663 * Called to inform the input method that text input has finished in 1664 * the last editor. At this point there may be a call to 1665 * {@link #onStartInput(EditorInfo, boolean)} to perform input in a 1666 * new editor, or the input method may be left idle. This method is 1667 * <em>not</em> called when input restarts in the same editor. 1668 * 1669 * <p>The default 1670 * implementation uses the InputConnection to clear any active composing 1671 * text; you can override this (not calling the base class implementation) 1672 * to perform whatever behavior you would like. 1673 */ onFinishInput()1674 public void onFinishInput() { 1675 InputConnection ic = getCurrentInputConnection(); 1676 if (ic != null) { 1677 ic.finishComposingText(); 1678 } 1679 } 1680 1681 /** 1682 * Called when the application has reported auto-completion candidates that 1683 * it would like to have the input method displayed. Typically these are 1684 * only used when an input method is running in full-screen mode, since 1685 * otherwise the user can see and interact with the pop-up window of 1686 * completions shown by the application. 1687 * 1688 * <p>The default implementation here does nothing. 1689 */ onDisplayCompletions(CompletionInfo[] completions)1690 public void onDisplayCompletions(CompletionInfo[] completions) { 1691 // Intentionally empty 1692 } 1693 1694 /** 1695 * Called when the application has reported new extracted text to be shown 1696 * due to changes in its current text state. The default implementation 1697 * here places the new text in the extract edit text, when the input 1698 * method is running in fullscreen mode. 1699 */ onUpdateExtractedText(int token, ExtractedText text)1700 public void onUpdateExtractedText(int token, ExtractedText text) { 1701 if (mExtractedToken != token) { 1702 return; 1703 } 1704 if (text != null) { 1705 if (mExtractEditText != null) { 1706 mExtractedText = text; 1707 mExtractEditText.setExtractedText(text); 1708 } 1709 } 1710 } 1711 1712 /** 1713 * Called when the application has reported a new selection region of 1714 * the text. This is called whether or not the input method has requested 1715 * extracted text updates, although if so it will not receive this call 1716 * if the extracted text has changed as well. 1717 * 1718 * <p>Be careful about changing the text in reaction to this call with 1719 * methods such as setComposingText, commitText or 1720 * deleteSurroundingText. If the cursor moves as a result, this method 1721 * will be called again, which may result in an infinite loop. 1722 * 1723 * <p>The default implementation takes care of updating the cursor in 1724 * the extract text, if it is being shown. 1725 */ onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)1726 public void onUpdateSelection(int oldSelStart, int oldSelEnd, 1727 int newSelStart, int newSelEnd, 1728 int candidatesStart, int candidatesEnd) { 1729 final ExtractEditText eet = mExtractEditText; 1730 if (eet != null && isFullscreenMode() && mExtractedText != null) { 1731 final int off = mExtractedText.startOffset; 1732 eet.startInternalChanges(); 1733 newSelStart -= off; 1734 newSelEnd -= off; 1735 final int len = eet.getText().length(); 1736 if (newSelStart < 0) newSelStart = 0; 1737 else if (newSelStart > len) newSelStart = len; 1738 if (newSelEnd < 0) newSelEnd = 0; 1739 else if (newSelEnd > len) newSelEnd = len; 1740 eet.setSelection(newSelStart, newSelEnd); 1741 eet.finishInternalChanges(); 1742 } 1743 } 1744 1745 /** 1746 * Called when the user tapped or clicked a text view. 1747 * IMEs can't rely on this method being called because this was not part of the original IME 1748 * protocol, so applications with custom text editing written before this method appeared will 1749 * not call to inform the IME of this interaction. 1750 * @param focusChanged true if the user changed the focused view by this click. 1751 */ onViewClicked(boolean focusChanged)1752 public void onViewClicked(boolean focusChanged) { 1753 // Intentionally empty 1754 } 1755 1756 /** 1757 * Called when the application has reported a new location of its text 1758 * cursor. This is only called if explicitly requested by the input method. 1759 * The default implementation does nothing. 1760 * @deprecated Use {#link onUpdateCursorAnchorInfo(CursorAnchorInfo)} instead. 1761 */ 1762 @Deprecated onUpdateCursor(Rect newCursor)1763 public void onUpdateCursor(Rect newCursor) { 1764 // Intentionally empty 1765 } 1766 1767 /** 1768 * Called when the application has reported a new location of its text insertion point and 1769 * characters in the composition string. This is only called if explicitly requested by the 1770 * input method. The default implementation does nothing. 1771 * @param cursorAnchorInfo The positional information of the text insertion point and the 1772 * composition string. 1773 */ onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo)1774 public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { 1775 // Intentionally empty 1776 } 1777 1778 /** 1779 * Close this input method's soft input area, removing it from the display. 1780 * The input method will continue running, but the user can no longer use 1781 * it to generate input by touching the screen. 1782 * @param flags Provides additional operating flags. Currently may be 1783 * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY 1784 * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set. 1785 */ requestHideSelf(int flags)1786 public void requestHideSelf(int flags) { 1787 mImm.hideSoftInputFromInputMethod(mToken, flags); 1788 } 1789 1790 /** 1791 * Show the input method. This is a call back to the 1792 * IMF to handle showing the input method. 1793 * @param flags Provides additional operating flags. Currently may be 1794 * 0 or have the {@link InputMethodManager#SHOW_FORCED 1795 * InputMethodManager.} bit set. 1796 */ requestShowSelf(int flags)1797 private void requestShowSelf(int flags) { 1798 mImm.showSoftInputFromInputMethod(mToken, flags); 1799 } 1800 handleBack(boolean doIt)1801 private boolean handleBack(boolean doIt) { 1802 if (mShowInputRequested) { 1803 // If the soft input area is shown, back closes it and we 1804 // consume the back key. 1805 if (doIt) requestHideSelf(0); 1806 return true; 1807 } else if (mWindowVisible) { 1808 if (mCandidatesVisibility == View.VISIBLE) { 1809 // If we are showing candidates even if no input area, then 1810 // hide them. 1811 if (doIt) setCandidatesViewShown(false); 1812 } else { 1813 // If we have the window visible for some other reason -- 1814 // most likely to show candidates -- then just get rid 1815 // of it. This really shouldn't happen, but just in case... 1816 if (doIt) doHideWindow(); 1817 } 1818 return true; 1819 } 1820 return false; 1821 } 1822 1823 /** 1824 * @return {#link ExtractEditText} if it is considered to be visible and active. Otherwise 1825 * {@code null} is returned. 1826 */ getExtractEditTextIfVisible()1827 private ExtractEditText getExtractEditTextIfVisible() { 1828 if (!isExtractViewShown() || !isInputViewShown()) { 1829 return null; 1830 } 1831 return mExtractEditText; 1832 } 1833 1834 /** 1835 * Override this to intercept key down events before they are processed by the 1836 * application. If you return true, the application will not 1837 * process the event itself. If you return false, the normal application processing 1838 * will occur as if the IME had not seen the event at all. 1839 * 1840 * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK 1841 * KeyEvent.KEYCODE_BACK} if the IME is currently shown, to 1842 * possibly hide it when the key goes up (if not canceled or long pressed). In 1843 * addition, in fullscreen mode only, it will consume DPAD movement 1844 * events to move the cursor in the extracted text view, not allowing 1845 * them to perform navigation in the underlying application. 1846 */ onKeyDown(int keyCode, KeyEvent event)1847 public boolean onKeyDown(int keyCode, KeyEvent event) { 1848 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1849 final ExtractEditText eet = getExtractEditTextIfVisible(); 1850 if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) { 1851 return true; 1852 } 1853 if (handleBack(false)) { 1854 event.startTracking(); 1855 return true; 1856 } 1857 return false; 1858 } 1859 return doMovementKey(keyCode, event, MOVEMENT_DOWN); 1860 } 1861 1862 /** 1863 * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent) 1864 * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle 1865 * the event). 1866 */ onKeyLongPress(int keyCode, KeyEvent event)1867 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 1868 return false; 1869 } 1870 1871 /** 1872 * Override this to intercept special key multiple events before they are 1873 * processed by the 1874 * application. If you return true, the application will not itself 1875 * process the event. If you return false, the normal application processing 1876 * will occur as if the IME had not seen the event at all. 1877 * 1878 * <p>The default implementation always returns false, except when 1879 * in fullscreen mode, where it will consume DPAD movement 1880 * events to move the cursor in the extracted text view, not allowing 1881 * them to perform navigation in the underlying application. 1882 */ onKeyMultiple(int keyCode, int count, KeyEvent event)1883 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { 1884 return doMovementKey(keyCode, event, count); 1885 } 1886 1887 /** 1888 * Override this to intercept key up events before they are processed by the 1889 * application. If you return true, the application will not itself 1890 * process the event. If you return false, the normal application processing 1891 * will occur as if the IME had not seen the event at all. 1892 * 1893 * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK 1894 * KeyEvent.KEYCODE_BACK} to hide the current IME UI if it is shown. In 1895 * addition, in fullscreen mode only, it will consume DPAD movement 1896 * events to move the cursor in the extracted text view, not allowing 1897 * them to perform navigation in the underlying application. 1898 */ onKeyUp(int keyCode, KeyEvent event)1899 public boolean onKeyUp(int keyCode, KeyEvent event) { 1900 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1901 final ExtractEditText eet = getExtractEditTextIfVisible(); 1902 if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) { 1903 return true; 1904 } 1905 if (event.isTracking() && !event.isCanceled()) { 1906 return handleBack(true); 1907 } 1908 } 1909 return doMovementKey(keyCode, event, MOVEMENT_UP); 1910 } 1911 1912 /** 1913 * Override this to intercept trackball motion events before they are 1914 * processed by the application. 1915 * If you return true, the application will not itself process the event. 1916 * If you return false, the normal application processing will occur as if 1917 * the IME had not seen the event at all. 1918 */ 1919 @Override onTrackballEvent(MotionEvent event)1920 public boolean onTrackballEvent(MotionEvent event) { 1921 if (DEBUG) Log.v(TAG, "onTrackballEvent: " + event); 1922 return false; 1923 } 1924 1925 /** 1926 * Override this to intercept generic motion events before they are 1927 * processed by the application. 1928 * If you return true, the application will not itself process the event. 1929 * If you return false, the normal application processing will occur as if 1930 * the IME had not seen the event at all. 1931 */ 1932 @Override onGenericMotionEvent(MotionEvent event)1933 public boolean onGenericMotionEvent(MotionEvent event) { 1934 if (DEBUG) Log.v(TAG, "onGenericMotionEvent(): event " + event); 1935 return false; 1936 } 1937 onAppPrivateCommand(String action, Bundle data)1938 public void onAppPrivateCommand(String action, Bundle data) { 1939 } 1940 1941 /** 1942 * Handle a request by the system to toggle the soft input area. 1943 */ onToggleSoftInput(int showFlags, int hideFlags)1944 private void onToggleSoftInput(int showFlags, int hideFlags) { 1945 if (DEBUG) Log.v(TAG, "toggleSoftInput()"); 1946 if (isInputViewShown()) { 1947 requestHideSelf(hideFlags); 1948 } else { 1949 requestShowSelf(showFlags); 1950 } 1951 } 1952 1953 static final int MOVEMENT_DOWN = -1; 1954 static final int MOVEMENT_UP = -2; 1955 reportExtractedMovement(int keyCode, int count)1956 void reportExtractedMovement(int keyCode, int count) { 1957 int dx = 0, dy = 0; 1958 switch (keyCode) { 1959 case KeyEvent.KEYCODE_DPAD_LEFT: 1960 dx = -count; 1961 break; 1962 case KeyEvent.KEYCODE_DPAD_RIGHT: 1963 dx = count; 1964 break; 1965 case KeyEvent.KEYCODE_DPAD_UP: 1966 dy = -count; 1967 break; 1968 case KeyEvent.KEYCODE_DPAD_DOWN: 1969 dy = count; 1970 break; 1971 } 1972 onExtractedCursorMovement(dx, dy); 1973 } 1974 doMovementKey(int keyCode, KeyEvent event, int count)1975 boolean doMovementKey(int keyCode, KeyEvent event, int count) { 1976 final ExtractEditText eet = getExtractEditTextIfVisible(); 1977 if (eet != null) { 1978 // If we are in fullscreen mode, the cursor will move around 1979 // the extract edit text, but should NOT cause focus to move 1980 // to other fields. 1981 MovementMethod movement = eet.getMovementMethod(); 1982 Layout layout = eet.getLayout(); 1983 if (movement != null && layout != null) { 1984 // We want our own movement method to handle the key, so the 1985 // cursor will properly move in our own word wrapping. 1986 if (count == MOVEMENT_DOWN) { 1987 if (movement.onKeyDown(eet, 1988 (Spannable)eet.getText(), keyCode, event)) { 1989 reportExtractedMovement(keyCode, 1); 1990 return true; 1991 } 1992 } else if (count == MOVEMENT_UP) { 1993 if (movement.onKeyUp(eet, 1994 (Spannable)eet.getText(), keyCode, event)) { 1995 return true; 1996 } 1997 } else { 1998 if (movement.onKeyOther(eet, (Spannable)eet.getText(), event)) { 1999 reportExtractedMovement(keyCode, count); 2000 } else { 2001 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 2002 if (movement.onKeyDown(eet, 2003 (Spannable)eet.getText(), keyCode, down)) { 2004 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 2005 movement.onKeyUp(eet, 2006 (Spannable)eet.getText(), keyCode, up); 2007 while (--count > 0) { 2008 movement.onKeyDown(eet, 2009 (Spannable)eet.getText(), keyCode, down); 2010 movement.onKeyUp(eet, 2011 (Spannable)eet.getText(), keyCode, up); 2012 } 2013 reportExtractedMovement(keyCode, count); 2014 } 2015 } 2016 } 2017 } 2018 // Regardless of whether the movement method handled the key, 2019 // we never allow DPAD navigation to the application. 2020 switch (keyCode) { 2021 case KeyEvent.KEYCODE_DPAD_LEFT: 2022 case KeyEvent.KEYCODE_DPAD_RIGHT: 2023 case KeyEvent.KEYCODE_DPAD_UP: 2024 case KeyEvent.KEYCODE_DPAD_DOWN: 2025 return true; 2026 } 2027 } 2028 2029 return false; 2030 } 2031 2032 /** 2033 * Send the given key event code (as defined by {@link KeyEvent}) to the 2034 * current input connection is a key down + key up event pair. The sent 2035 * events have {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD} 2036 * set, so that the recipient can identify them as coming from a software 2037 * input method, and 2038 * {@link KeyEvent#FLAG_KEEP_TOUCH_MODE KeyEvent.FLAG_KEEP_TOUCH_MODE}, so 2039 * that they don't impact the current touch mode of the UI. 2040 * 2041 * <p>Note that it's discouraged to send such key events in normal operation; 2042 * this is mainly for use with {@link android.text.InputType#TYPE_NULL} type 2043 * text fields, or for non-rich input methods. A reasonably capable software 2044 * input method should use the 2045 * {@link android.view.inputmethod.InputConnection#commitText} family of methods 2046 * to send text to an application, rather than sending key events.</p> 2047 * 2048 * @param keyEventCode The raw key code to send, as defined by 2049 * {@link KeyEvent}. 2050 */ sendDownUpKeyEvents(int keyEventCode)2051 public void sendDownUpKeyEvents(int keyEventCode) { 2052 InputConnection ic = getCurrentInputConnection(); 2053 if (ic == null) return; 2054 long eventTime = SystemClock.uptimeMillis(); 2055 ic.sendKeyEvent(new KeyEvent(eventTime, eventTime, 2056 KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 2057 KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); 2058 ic.sendKeyEvent(new KeyEvent(eventTime, SystemClock.uptimeMillis(), 2059 KeyEvent.ACTION_UP, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 2060 KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); 2061 } 2062 2063 /** 2064 * Ask the input target to execute its default action via 2065 * {@link InputConnection#performEditorAction 2066 * InputConnection.performEditorAction()}. 2067 * 2068 * @param fromEnterKey If true, this will be executed as if the user had 2069 * pressed an enter key on the keyboard, that is it will <em>not</em> 2070 * be done if the editor has set {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION 2071 * EditorInfo.IME_FLAG_NO_ENTER_ACTION}. If false, the action will be 2072 * sent regardless of how the editor has set that flag. 2073 * 2074 * @return Returns a boolean indicating whether an action has been sent. 2075 * If false, either the editor did not specify a default action or it 2076 * does not want an action from the enter key. If true, the action was 2077 * sent (or there was no input connection at all). 2078 */ sendDefaultEditorAction(boolean fromEnterKey)2079 public boolean sendDefaultEditorAction(boolean fromEnterKey) { 2080 EditorInfo ei = getCurrentInputEditorInfo(); 2081 if (ei != null && 2082 (!fromEnterKey || (ei.imeOptions & 2083 EditorInfo.IME_FLAG_NO_ENTER_ACTION) == 0) && 2084 (ei.imeOptions & EditorInfo.IME_MASK_ACTION) != 2085 EditorInfo.IME_ACTION_NONE) { 2086 // If the enter key was pressed, and the editor has a default 2087 // action associated with pressing enter, then send it that 2088 // explicit action instead of the key event. 2089 InputConnection ic = getCurrentInputConnection(); 2090 if (ic != null) { 2091 ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION); 2092 } 2093 return true; 2094 } 2095 2096 return false; 2097 } 2098 2099 /** 2100 * Send the given UTF-16 character to the current input connection. Most 2101 * characters will be delivered simply by calling 2102 * {@link InputConnection#commitText InputConnection.commitText()} with 2103 * the character; some, however, may be handled different. In particular, 2104 * the enter character ('\n') will either be delivered as an action code 2105 * or a raw key event, as appropriate. Consider this as a convenience 2106 * method for IMEs that do not have a full implementation of actions; a 2107 * fully complying IME will decide of the right action for each event and 2108 * will likely never call this method except maybe to handle events coming 2109 * from an actual hardware keyboard. 2110 * 2111 * @param charCode The UTF-16 character code to send. 2112 */ sendKeyChar(char charCode)2113 public void sendKeyChar(char charCode) { 2114 switch (charCode) { 2115 case '\n': // Apps may be listening to an enter key to perform an action 2116 if (!sendDefaultEditorAction(true)) { 2117 sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); 2118 } 2119 break; 2120 default: 2121 // Make sure that digits go through any text watcher on the client side. 2122 if (charCode >= '0' && charCode <= '9') { 2123 sendDownUpKeyEvents(charCode - '0' + KeyEvent.KEYCODE_0); 2124 } else { 2125 InputConnection ic = getCurrentInputConnection(); 2126 if (ic != null) { 2127 ic.commitText(String.valueOf((char) charCode), 1); 2128 } 2129 } 2130 break; 2131 } 2132 } 2133 2134 /** 2135 * This is called when the user has moved the cursor in the extracted 2136 * text view, when running in fullsreen mode. The default implementation 2137 * performs the corresponding selection change on the underlying text 2138 * editor. 2139 */ onExtractedSelectionChanged(int start, int end)2140 public void onExtractedSelectionChanged(int start, int end) { 2141 InputConnection conn = getCurrentInputConnection(); 2142 if (conn != null) { 2143 conn.setSelection(start, end); 2144 } 2145 } 2146 2147 /** 2148 * @hide 2149 */ onExtractedDeleteText(int start, int end)2150 public void onExtractedDeleteText(int start, int end) { 2151 InputConnection conn = getCurrentInputConnection(); 2152 if (conn != null) { 2153 conn.setSelection(start, start); 2154 conn.deleteSurroundingText(0, end-start); 2155 } 2156 } 2157 2158 /** 2159 * @hide 2160 */ onExtractedReplaceText(int start, int end, CharSequence text)2161 public void onExtractedReplaceText(int start, int end, CharSequence text) { 2162 InputConnection conn = getCurrentInputConnection(); 2163 if (conn != null) { 2164 conn.setComposingRegion(start, end); 2165 conn.commitText(text, 1); 2166 } 2167 } 2168 2169 /** 2170 * @hide 2171 */ onExtractedSetSpan(Object span, int start, int end, int flags)2172 public void onExtractedSetSpan(Object span, int start, int end, int flags) { 2173 InputConnection conn = getCurrentInputConnection(); 2174 if (conn != null) { 2175 if (!conn.setSelection(start, end)) return; 2176 CharSequence text = conn.getSelectedText(InputConnection.GET_TEXT_WITH_STYLES); 2177 if (text instanceof Spannable) { 2178 ((Spannable) text).setSpan(span, 0, text.length(), flags); 2179 conn.setComposingRegion(start, end); 2180 conn.commitText(text, 1); 2181 } 2182 } 2183 } 2184 2185 /** 2186 * This is called when the user has clicked on the extracted text view, 2187 * when running in fullscreen mode. The default implementation hides 2188 * the candidates view when this happens, but only if the extracted text 2189 * editor has a vertical scroll bar because its text doesn't fit. 2190 * Re-implement this to provide whatever behavior you want. 2191 */ onExtractedTextClicked()2192 public void onExtractedTextClicked() { 2193 if (mExtractEditText == null) { 2194 return; 2195 } 2196 if (mExtractEditText.hasVerticalScrollBar()) { 2197 setCandidatesViewShown(false); 2198 } 2199 } 2200 2201 /** 2202 * This is called when the user has performed a cursor movement in the 2203 * extracted text view, when it is running in fullscreen mode. The default 2204 * implementation hides the candidates view when a vertical movement 2205 * happens, but only if the extracted text editor has a vertical scroll bar 2206 * because its text doesn't fit. 2207 * Re-implement this to provide whatever behavior you want. 2208 * @param dx The amount of cursor movement in the x dimension. 2209 * @param dy The amount of cursor movement in the y dimension. 2210 */ onExtractedCursorMovement(int dx, int dy)2211 public void onExtractedCursorMovement(int dx, int dy) { 2212 if (mExtractEditText == null || dy == 0) { 2213 return; 2214 } 2215 if (mExtractEditText.hasVerticalScrollBar()) { 2216 setCandidatesViewShown(false); 2217 } 2218 } 2219 2220 /** 2221 * This is called when the user has selected a context menu item from the 2222 * extracted text view, when running in fullscreen mode. The default 2223 * implementation sends this action to the current InputConnection's 2224 * {@link InputConnection#performContextMenuAction(int)}, for it 2225 * to be processed in underlying "real" editor. Re-implement this to 2226 * provide whatever behavior you want. 2227 */ onExtractTextContextMenuItem(int id)2228 public boolean onExtractTextContextMenuItem(int id) { 2229 InputConnection ic = getCurrentInputConnection(); 2230 if (ic != null) { 2231 ic.performContextMenuAction(id); 2232 } 2233 return true; 2234 } 2235 2236 /** 2237 * Return text that can be used as a button label for the given 2238 * {@link EditorInfo#imeOptions EditorInfo.imeOptions}. Returns null 2239 * if there is no action requested. Note that there is no guarantee that 2240 * the returned text will be relatively short, so you probably do not 2241 * want to use it as text on a soft keyboard key label. 2242 * 2243 * @param imeOptions The value from @link EditorInfo#imeOptions EditorInfo.imeOptions}. 2244 * 2245 * @return Returns a label to use, or null if there is no action. 2246 */ getTextForImeAction(int imeOptions)2247 public CharSequence getTextForImeAction(int imeOptions) { 2248 switch (imeOptions&EditorInfo.IME_MASK_ACTION) { 2249 case EditorInfo.IME_ACTION_NONE: 2250 return null; 2251 case EditorInfo.IME_ACTION_GO: 2252 return getText(com.android.internal.R.string.ime_action_go); 2253 case EditorInfo.IME_ACTION_SEARCH: 2254 return getText(com.android.internal.R.string.ime_action_search); 2255 case EditorInfo.IME_ACTION_SEND: 2256 return getText(com.android.internal.R.string.ime_action_send); 2257 case EditorInfo.IME_ACTION_NEXT: 2258 return getText(com.android.internal.R.string.ime_action_next); 2259 case EditorInfo.IME_ACTION_DONE: 2260 return getText(com.android.internal.R.string.ime_action_done); 2261 case EditorInfo.IME_ACTION_PREVIOUS: 2262 return getText(com.android.internal.R.string.ime_action_previous); 2263 default: 2264 return getText(com.android.internal.R.string.ime_action_default); 2265 } 2266 } 2267 2268 /** 2269 * Called when the fullscreen-mode extracting editor info has changed, 2270 * to determine whether the extracting (extract text and candidates) portion 2271 * of the UI should be shown. The standard implementation hides or shows 2272 * the extract area depending on whether it makes sense for the 2273 * current editor. In particular, a {@link InputType#TYPE_NULL} 2274 * input type or {@link EditorInfo#IME_FLAG_NO_EXTRACT_UI} flag will 2275 * turn off the extract area since there is no text to be shown. 2276 */ onUpdateExtractingVisibility(EditorInfo ei)2277 public void onUpdateExtractingVisibility(EditorInfo ei) { 2278 if (ei.inputType == InputType.TYPE_NULL || 2279 (ei.imeOptions&EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0) { 2280 // No reason to show extract UI! 2281 setExtractViewShown(false); 2282 return; 2283 } 2284 2285 setExtractViewShown(true); 2286 } 2287 2288 /** 2289 * Called when the fullscreen-mode extracting editor info has changed, 2290 * to update the state of its UI such as the action buttons shown. 2291 * You do not need to deal with this if you are using the standard 2292 * full screen extract UI. If replacing it, you will need to re-implement 2293 * this to put the appropriate action button in your own UI and handle it, 2294 * and perform any other changes. 2295 * 2296 * <p>The standard implementation turns on or off its accessory area 2297 * depending on whether there is an action button, and hides or shows 2298 * the entire extract area depending on whether it makes sense for the 2299 * current editor. In particular, a {@link InputType#TYPE_NULL} or 2300 * {@link InputType#TYPE_TEXT_VARIATION_FILTER} input type will turn off the 2301 * extract area since there is no text to be shown. 2302 */ onUpdateExtractingViews(EditorInfo ei)2303 public void onUpdateExtractingViews(EditorInfo ei) { 2304 if (!isExtractViewShown()) { 2305 return; 2306 } 2307 2308 if (mExtractAccessories == null) { 2309 return; 2310 } 2311 final boolean hasAction = ei.actionLabel != null || ( 2312 (ei.imeOptions&EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE && 2313 (ei.imeOptions&EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION) == 0 && 2314 ei.inputType != InputType.TYPE_NULL); 2315 if (hasAction) { 2316 mExtractAccessories.setVisibility(View.VISIBLE); 2317 if (mExtractAction != null) { 2318 if (ei.actionLabel != null) { 2319 mExtractAction.setText(ei.actionLabel); 2320 } else { 2321 mExtractAction.setText(getTextForImeAction(ei.imeOptions)); 2322 } 2323 mExtractAction.setOnClickListener(mActionClickListener); 2324 } 2325 } else { 2326 mExtractAccessories.setVisibility(View.GONE); 2327 if (mExtractAction != null) { 2328 mExtractAction.setOnClickListener(null); 2329 } 2330 } 2331 } 2332 2333 /** 2334 * This is called when, while currently displayed in extract mode, the 2335 * current input target changes. The default implementation will 2336 * auto-hide the IME if the new target is not a full editor, since this 2337 * can be a confusing experience for the user. 2338 */ onExtractingInputChanged(EditorInfo ei)2339 public void onExtractingInputChanged(EditorInfo ei) { 2340 if (ei.inputType == InputType.TYPE_NULL) { 2341 requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS); 2342 } 2343 } 2344 startExtractingText(boolean inputChanged)2345 void startExtractingText(boolean inputChanged) { 2346 final ExtractEditText eet = mExtractEditText; 2347 if (eet != null && getCurrentInputStarted() 2348 && isFullscreenMode()) { 2349 mExtractedToken++; 2350 ExtractedTextRequest req = new ExtractedTextRequest(); 2351 req.token = mExtractedToken; 2352 req.flags = InputConnection.GET_TEXT_WITH_STYLES; 2353 req.hintMaxLines = 10; 2354 req.hintMaxChars = 10000; 2355 InputConnection ic = getCurrentInputConnection(); 2356 mExtractedText = ic == null? null 2357 : ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); 2358 if (mExtractedText == null || ic == null) { 2359 Log.e(TAG, "Unexpected null in startExtractingText : mExtractedText = " 2360 + mExtractedText + ", input connection = " + ic); 2361 } 2362 final EditorInfo ei = getCurrentInputEditorInfo(); 2363 2364 try { 2365 eet.startInternalChanges(); 2366 onUpdateExtractingVisibility(ei); 2367 onUpdateExtractingViews(ei); 2368 int inputType = ei.inputType; 2369 if ((inputType&EditorInfo.TYPE_MASK_CLASS) 2370 == EditorInfo.TYPE_CLASS_TEXT) { 2371 if ((inputType&EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE) != 0) { 2372 inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 2373 } 2374 } 2375 eet.setInputType(inputType); 2376 eet.setHint(ei.hintText); 2377 if (mExtractedText != null) { 2378 eet.setEnabled(true); 2379 eet.setExtractedText(mExtractedText); 2380 } else { 2381 eet.setEnabled(false); 2382 eet.setText(""); 2383 } 2384 } finally { 2385 eet.finishInternalChanges(); 2386 } 2387 2388 if (inputChanged) { 2389 onExtractingInputChanged(ei); 2390 } 2391 } 2392 } 2393 2394 // TODO: Handle the subtype change event 2395 /** 2396 * Called when the subtype was changed. 2397 * @param newSubtype the subtype which is being changed to. 2398 */ onCurrentInputMethodSubtypeChanged(InputMethodSubtype newSubtype)2399 protected void onCurrentInputMethodSubtypeChanged(InputMethodSubtype newSubtype) { 2400 if (DEBUG) { 2401 int nameResId = newSubtype.getNameResId(); 2402 String mode = newSubtype.getMode(); 2403 String output = "changeInputMethodSubtype:" 2404 + (nameResId == 0 ? "<none>" : getString(nameResId)) + "," 2405 + mode + "," 2406 + newSubtype.getLocale() + "," + newSubtype.getExtraValue(); 2407 Log.v(TAG, "--- " + output); 2408 } 2409 } 2410 2411 /** 2412 * @return The recommended height of the input method window. 2413 * An IME author can get the last input method's height as the recommended height 2414 * by calling this in 2415 * {@link android.inputmethodservice.InputMethodService#onStartInputView(EditorInfo, boolean)}. 2416 * If you don't need to use a predefined fixed height, you can avoid the window-resizing of IME 2417 * switching by using this value as a visible inset height. It's efficient for the smooth 2418 * transition between different IMEs. However, note that this may return 0 (or possibly 2419 * unexpectedly low height). You should thus avoid relying on the return value of this method 2420 * all the time. Please make sure to use a reasonable height for the IME. 2421 */ getInputMethodWindowRecommendedHeight()2422 public int getInputMethodWindowRecommendedHeight() { 2423 return mImm.getInputMethodWindowVisibleHeight(); 2424 } 2425 2426 /** 2427 * Performs a dump of the InputMethodService's internal state. Override 2428 * to add your own information to the dump. 2429 */ dump(FileDescriptor fd, PrintWriter fout, String[] args)2430 @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 2431 final Printer p = new PrintWriterPrinter(fout); 2432 p.println("Input method service state for " + this + ":"); 2433 p.println(" mWindowCreated=" + mWindowCreated 2434 + " mWindowAdded=" + mWindowAdded); 2435 p.println(" mWindowVisible=" + mWindowVisible 2436 + " mWindowWasVisible=" + mWindowWasVisible 2437 + " mInShowWindow=" + mInShowWindow); 2438 p.println(" Configuration=" + getResources().getConfiguration()); 2439 p.println(" mToken=" + mToken); 2440 p.println(" mInputBinding=" + mInputBinding); 2441 p.println(" mInputConnection=" + mInputConnection); 2442 p.println(" mStartedInputConnection=" + mStartedInputConnection); 2443 p.println(" mInputStarted=" + mInputStarted 2444 + " mInputViewStarted=" + mInputViewStarted 2445 + " mCandidatesViewStarted=" + mCandidatesViewStarted); 2446 2447 if (mInputEditorInfo != null) { 2448 p.println(" mInputEditorInfo:"); 2449 mInputEditorInfo.dump(p, " "); 2450 } else { 2451 p.println(" mInputEditorInfo: null"); 2452 } 2453 2454 p.println(" mShowInputRequested=" + mShowInputRequested 2455 + " mLastShowInputRequested=" + mLastShowInputRequested 2456 + " mShowInputForced=" + mShowInputForced 2457 + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags)); 2458 p.println(" mCandidatesVisibility=" + mCandidatesVisibility 2459 + " mFullscreenApplied=" + mFullscreenApplied 2460 + " mIsFullscreen=" + mIsFullscreen 2461 + " mExtractViewHidden=" + mExtractViewHidden); 2462 2463 if (mExtractedText != null) { 2464 p.println(" mExtractedText:"); 2465 p.println(" text=" + mExtractedText.text.length() + " chars" 2466 + " startOffset=" + mExtractedText.startOffset); 2467 p.println(" selectionStart=" + mExtractedText.selectionStart 2468 + " selectionEnd=" + mExtractedText.selectionEnd 2469 + " flags=0x" + Integer.toHexString(mExtractedText.flags)); 2470 } else { 2471 p.println(" mExtractedText: null"); 2472 } 2473 p.println(" mExtractedToken=" + mExtractedToken); 2474 p.println(" mIsInputViewShown=" + mIsInputViewShown 2475 + " mStatusIcon=" + mStatusIcon); 2476 p.println("Last computed insets:"); 2477 p.println(" contentTopInsets=" + mTmpInsets.contentTopInsets 2478 + " visibleTopInsets=" + mTmpInsets.visibleTopInsets 2479 + " touchableInsets=" + mTmpInsets.touchableInsets 2480 + " touchableRegion=" + mTmpInsets.touchableRegion); 2481 p.println(" mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme); 2482 } 2483 } 2484