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