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