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