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