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