diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-02-10 15:44:00 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-02-10 15:44:00 -0800 |
commit | d24b8183b93e781080b2c16c487e60d51c12da31 (patch) | |
tree | fbb89154858984eb8e41556da7e9433040d55cd4 /core/java/android/inputmethodservice/InputMethodService.java | |
parent | f1e484acb594a726fb57ad0ae4cfe902c7f35858 (diff) | |
download | frameworks_base-d24b8183b93e781080b2c16c487e60d51c12da31.zip frameworks_base-d24b8183b93e781080b2c16c487e60d51c12da31.tar.gz frameworks_base-d24b8183b93e781080b2c16c487e60d51c12da31.tar.bz2 |
auto import from //branches/cupcake/...@130745
Diffstat (limited to 'core/java/android/inputmethodservice/InputMethodService.java')
-rw-r--r-- | core/java/android/inputmethodservice/InputMethodService.java | 555 |
1 files changed, 498 insertions, 57 deletions
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 3a9b26a..ea5f741 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -26,7 +26,13 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.IBinder; +import android.provider.Settings; +import android.text.Layout; +import android.text.Spannable; +import android.text.method.MovementMethod; import android.util.Log; +import android.util.PrintWriterPrinter; +import android.util.Printer; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -45,12 +51,31 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.EditorInfo; import android.widget.FrameLayout; +import java.io.FileDescriptor; +import java.io.PrintWriter; + /** * InputMethodService provides a standard implementation of an InputMethod, * which final implementations can derive from and customize. See the * base class {@link AbstractInputMethodService} and the {@link InputMethod} * interface for more information on the basics of writing input methods. * + * <p>In addition to the normal Service lifecycle methods, this class + * introduces some new specific callbacks that most subclasses will want + * to make use of:</p> + * <ul> + * <li> {@link #onInitializeInterface()} for user-interface initialization, + * in particular to deal with configuration changes while the service is + * running. + * <li> {@link #onBindInput} to find out about switching to a new client. + * <li> {@link #onStartInput} to deal with an input session starting with + * the client. + * <li> {@link #onCreateInputView()}, {@link #onCreateCandidatesView()}, + * and {@link #onCreateExtractTextView()} for non-demand generation of the UI. + * <li> {@link #onStartInputView(EditorInfo, boolean)} to deal with input + * starting within the input area of the IME. + * </ul> + * * <p>An input method has significant discretion in how it goes about its * work: the {@link android.inputmethodservice.InputMethodService} provides * a basic framework for standard UI elements (input view, candidates view, @@ -184,6 +209,7 @@ public class InputMethodService extends AbstractInputMethodService { LayoutInflater mInflater; View mRootView; SoftInputWindow mWindow; + boolean mInitialized; boolean mWindowCreated; boolean mWindowAdded; boolean mWindowVisible; @@ -196,11 +222,16 @@ public class InputMethodService extends AbstractInputMethodService { InputBinding mInputBinding; InputConnection mInputConnection; boolean mInputStarted; + boolean mInputViewStarted; + boolean mCandidatesViewStarted; + InputConnection mStartedInputConnection; EditorInfo mInputEditorInfo; boolean mShowInputRequested; boolean mLastShowInputRequested; - boolean mShowCandidatesRequested; + int mCandidatesVisibility; + + boolean mShowInputForced; boolean mFullscreenApplied; boolean mIsFullscreen; @@ -262,6 +293,7 @@ public class InputMethodService extends AbstractInputMethodService { mInputConnection = binding.getConnection(); if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding + " ic=" + mInputConnection); + initialize(); onBindInput(); } @@ -277,14 +309,14 @@ public class InputMethodService extends AbstractInputMethodService { mInputConnection = null; } - public void startInput(EditorInfo attribute) { + public void startInput(InputConnection ic, EditorInfo attribute) { if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute); - doStartInput(attribute, false); + doStartInput(ic, attribute, false); } - public void restartInput(EditorInfo attribute) { + public void restartInput(InputConnection ic, EditorInfo attribute) { if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute); - doStartInput(attribute, true); + doStartInput(ic, attribute, true); } /** @@ -293,6 +325,7 @@ public class InputMethodService extends AbstractInputMethodService { public void hideSoftInput() { if (DEBUG) Log.v(TAG, "hideSoftInput()"); mShowInputRequested = false; + mShowInputForced = false; hideWindow(); } @@ -316,8 +349,7 @@ public class InputMethodService extends AbstractInputMethodService { return; } if (DEBUG) Log.v(TAG, "finishInput() in " + this); - onFinishInput(); - mInputStarted = false; + doFinishInput(); } /** @@ -444,17 +476,37 @@ public class InputMethodService extends AbstractInputMethodService { mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT); } + /** + * This is a hook that subclasses can use to perform initialization of + * their interface. It is called for you prior to any of your UI objects + * being created, both after the service is first created and after a + * configuration change happens. + */ + public void onInitializeInterface() { + } + + void initialize() { + if (!mInitialized) { + mInitialized = true; + onInitializeInterface(); + } + } + void initViews() { - mWindowVisible = false; + mInitialized = false; mWindowCreated = false; mShowInputRequested = false; - mShowCandidatesRequested = false; + mShowInputForced = false; mRootView = mInflater.inflate( com.android.internal.R.layout.input_method, null); mWindow.setContentView(mRootView); mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); - + if (Settings.System.getInt(getContentResolver(), + Settings.System.FANCY_IME_ANIMATIONS, 0) != 0) { + mWindow.getWindow().setWindowAnimations( + com.android.internal.R.style.Animation_InputMethodFancy); + } mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea); mExtractView = null; mExtractEditText = null; @@ -466,7 +518,8 @@ public class InputMethodService extends AbstractInputMethodService { mIsInputViewShown = false; mExtractFrame.setVisibility(View.GONE); - mCandidatesFrame.setVisibility(View.INVISIBLE); + mCandidatesVisibility = getCandidatesHiddenVisibility(); + mCandidatesFrame.setVisibility(mCandidatesVisibility); mInputFrame.setVisibility(View.GONE); } @@ -486,19 +539,36 @@ public class InputMethodService extends AbstractInputMethodService { * regenerating the input method UI as a result of the configuration * change, so you can rely on your {@link #onCreateInputView} and * other methods being called as appropriate due to a configuration change. + * + * <p>When a configuration change does happen, + * {@link #onInitializeInterface()} is guaranteed to be called the next + * time prior to any of the other input or UI creation callbacks. The + * following will be called immediately depending if appropriate for current + * state: {@link #onStartInput} if input is active, and + * {@link #onCreateInputView} and {@link #onStartInputView} and related + * appropriate functions if the UI is displayed. */ @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); boolean visible = mWindowVisible; boolean showingInput = mShowInputRequested; - boolean showingCandidates = mShowCandidatesRequested; + boolean showingForced = mShowInputForced; + boolean showingCandidates = mCandidatesVisibility == View.VISIBLE; initViews(); + mInputViewStarted = false; + mCandidatesViewStarted = false; + if (mInputStarted) { + doStartInput(getCurrentInputConnection(), + getCurrentInputEditorInfo(), true); + } if (visible) { - if (showingCandidates) { - setCandidatesViewShown(true); - } - if (showingInput) { + if (showingForced) { + // If we are showing the full soft keyboard, then go through + // this path to take care of current decisions about fullscreen + // etc. + onShowRequested(InputMethod.SHOW_FORCED|InputMethod.SHOW_EXPLICIT); + } else if (showingInput) { // If we are showing the full soft keyboard, then go through // this path to take care of current decisions about fullscreen // etc. @@ -507,6 +577,9 @@ public class InputMethodService extends AbstractInputMethodService { // Otherwise just put it back for its candidates. showWindow(false); } + if (showingCandidates) { + setCandidatesViewShown(true); + } } } @@ -568,6 +641,10 @@ public class InputMethodService extends AbstractInputMethodService { * the input method, or null if there is none. */ public InputConnection getCurrentInputConnection() { + InputConnection ic = mStartedInputConnection; + if (ic != null) { + return ic; + } return mInputConnection; } @@ -594,6 +671,7 @@ public class InputMethodService extends AbstractInputMethodService { changed = true; mIsFullscreen = isFullscreen; mFullscreenApplied = true; + initialize(); Drawable bg = onCreateBackgroundDrawable(); if (bg == null) { // We need to give the window a real drawable, so that it @@ -708,6 +786,7 @@ public class InputMethodService extends AbstractInputMethodService { mIsInputViewShown = isShown; mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE); if (mInputView == null) { + initialize(); View v = onCreateInputView(); if (v != null) { setInputView(v); @@ -717,12 +796,19 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Returns true if we have been asked to show our input view. + */ + public boolean isShowInputRequested() { + return mShowInputRequested; + } + + /** * Return whether the soft input view is <em>currently</em> shown to the * user. This is the state that was last determined and * applied by {@link #updateInputViewShown()}. */ public boolean isInputViewShown() { - return mIsInputViewShown; + return mIsInputViewShown && mWindowVisible; } /** @@ -744,9 +830,10 @@ public class InputMethodService extends AbstractInputMethodService { * it is hidden. */ public void setCandidatesViewShown(boolean shown) { - if (mShowCandidatesRequested != shown) { - mCandidatesFrame.setVisibility(shown ? View.VISIBLE : View.INVISIBLE); - mShowCandidatesRequested = shown; + int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility(); + if (mCandidatesVisibility != vis) { + mCandidatesFrame.setVisibility(vis); + mCandidatesVisibility = vis; } if (!mShowInputRequested && mWindowVisible != shown) { // If we are being asked to show the candidates view while the app @@ -760,10 +847,24 @@ public class InputMethodService extends AbstractInputMethodService { } } + /** + * Returns the visibility mode (either {@link View#INVISIBLE View.INVISIBLE} + * or {@link View#GONE View.GONE}) of the candidates view when it is not + * shown. The default implementation returns GONE when in fullscreen mode, + * otherwise VISIBLE. Be careful if you change this to return GONE in + * other situations -- if showing or hiding the candidates view causes + * your window to resize, this can cause temporary drawing artifacts as + * the resize takes place. + */ + public int getCandidatesHiddenVisibility() { + return isFullscreenMode() ? View.GONE : View.INVISIBLE; + } + public void setStatusIcon(int iconResId) { mStatusIcon = iconResId; - if (mInputConnection != null && mWindowVisible) { - mInputConnection.showStatusIcon(getPackageName(), iconResId); + InputConnection ic = getCurrentInputConnection(); + if (ic != null && mWindowVisible) { + ic.showStatusIcon(getPackageName(), iconResId); } } @@ -783,11 +884,12 @@ public class InputMethodService extends AbstractInputMethodService { mExtractFrame.removeAllViews(); mExtractFrame.addView(view, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); + ViewGroup.LayoutParams.FILL_PARENT)); mExtractView = view; if (view != null) { mExtractEditText = (ExtractEditText)view.findViewById( com.android.internal.R.id.inputExtractEditText); + mExtractEditText.setIME(this); startExtractingText(); } else { mExtractEditText = null; @@ -890,6 +992,72 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Called when the input view is being hidden from the user. This will + * be called either prior to hiding the window, or prior to switching to + * another target for editing. + * + * <p>The default + * implementation uses the InputConnection to clear any active composing + * text; you can override this (not calling the base class implementation) + * to perform whatever behavior you would like. + * + * @boolean finishingInput If true, {@link #onFinishInput} will be + * called immediately after. + */ + public void onFinishInputView(boolean finishingInput) { + if (!finishingInput) { + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.finishComposingText(); + } + } + } + + /** + * Called when only the candidates view has been shown for showing + * processing as the user enters text through a hard keyboard. + * This will always be called after {@link #onStartInput}, + * allowing you to do your general setup there and just view-specific + * setup here. You are guaranteed that {@link #onCreateCandidatesView()} + * will have been called some time before this function is called. + * + * <p>Note that this will <em>not</em> be called when the input method + * is running in full editing mode, and thus receiving + * {@link #onStartInputView} to initiate that operation. This is only + * for the case when candidates are being shown while the input method + * editor is hidden but wants to show its candidates UI as text is + * entered through some other mechanism. + * + * @param info Description of the type of text being edited. + * @param restarting Set to true if we are restarting input on the + * same text field as before. + */ + public void onStartCandidatesView(EditorInfo info, boolean restarting) { + } + + /** + * Called when the candidates view is being hidden from the user. This will + * be called either prior to hiding the window, or prior to switching to + * another target for editing. + * + * <p>The default + * implementation uses the InputConnection to clear any active composing + * text; you can override this (not calling the base class implementation) + * to perform whatever behavior you would like. + * + * @boolean finishingInput If true, {@link #onFinishInput} will be + * called immediately after. + */ + public void onFinishCandidatesView(boolean finishingInput) { + if (!finishingInput) { + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.finishComposingText(); + } + } + } + + /** * The system has decided that it may be time to show your input method. * This is called due to a corresponding call to your * {@link InputMethod#showSoftInput(int) InputMethod.showSoftInput(int)} @@ -905,10 +1073,22 @@ public class InputMethodService extends AbstractInputMethodService { if (!onEvaluateInputViewShown()) { return; } - if ((flags&InputMethod.SHOW_EXPLICIT) == 0 && onEvaluateFullscreenMode()) { - // Don't show if this is not explicit requested by the user and - // the input method is fullscreen. That would be too disruptive. - return; + if ((flags&InputMethod.SHOW_EXPLICIT) == 0) { + if (onEvaluateFullscreenMode()) { + // Don't show if this is not explicitly requested by the user and + // the input method is fullscreen. That would be too disruptive. + return; + } + Configuration config = getResources().getConfiguration(); + if (config.keyboard != Configuration.KEYBOARD_NOKEYS) { + // And if the device has a hard keyboard, even if it is + // currently hidden, don't show the input method implicitly. + // These kinds of devices don't need it that much. + return; + } + } + if ((flags&InputMethod.SHOW_FORCED) != 0) { + mShowInputForced = true; } showWindow(true); } @@ -922,6 +1102,7 @@ public class InputMethodService extends AbstractInputMethodService { + " mInputStarted=" + mInputStarted); boolean doShowInput = false; boolean wasVisible = mWindowVisible; + boolean wasCreated = mWindowCreated; mWindowVisible = true; if (!mShowInputRequested) { if (mInputStarted) { @@ -935,41 +1116,66 @@ public class InputMethodService extends AbstractInputMethodService { } if (DEBUG) Log.v(TAG, "showWindow: updating UI"); + initialize(); updateFullscreenMode(); updateInputViewShown(); if (!mWindowAdded || !mWindowCreated) { mWindowAdded = true; mWindowCreated = true; + initialize(); + if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView"); View v = onCreateCandidatesView(); if (DEBUG) Log.v(TAG, "showWindow: candidates=" + v); if (v != null) { setCandidatesView(v); } } - if (doShowInput) { - if (mInputStarted) { - if (DEBUG) Log.v(TAG, "showWindow: starting input view"); + if (mShowInputRequested) { + if (!mInputViewStarted) { + if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); + mInputViewStarted = true; onStartInputView(mInputEditorInfo, false); } + } else if (!mCandidatesViewStarted) { + if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView"); + mCandidatesViewStarted = true; + onStartCandidatesView(mInputEditorInfo, false); + } + + if (doShowInput) { startExtractingText(); } if (!wasVisible) { if (DEBUG) Log.v(TAG, "showWindow: showing!"); mWindow.show(); - if (mInputConnection != null) { - mInputConnection.showStatusIcon(getPackageName(), mStatusIcon); + } + + if (!wasVisible || !wasCreated) { + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.showStatusIcon(getPackageName(), mStatusIcon); } } } public void hideWindow() { + if (mInputViewStarted) { + if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); + onFinishInputView(false); + } else if (mCandidatesViewStarted) { + if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); + onFinishCandidatesView(false); + } + mInputViewStarted = false; + mCandidatesViewStarted = false; if (mWindowVisible) { mWindow.hide(); mWindowVisible = false; - if (mInputConnection != null) { - mInputConnection.hideStatusIcon(); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.hideStatusIcon(); } } } @@ -1008,18 +1214,45 @@ public class InputMethodService extends AbstractInputMethodService { public void onStartInput(EditorInfo attribute, boolean restarting) { } - void doStartInput(EditorInfo attribute, boolean restarting) { - if (mInputStarted && !restarting) { + void doFinishInput() { + if (mInputViewStarted) { + if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); + onFinishInputView(true); + } else if (mCandidatesViewStarted) { + if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); + onFinishCandidatesView(true); + } + mInputViewStarted = false; + mCandidatesViewStarted = false; + if (mInputStarted) { + if (DEBUG) Log.v(TAG, "CALL: onFinishInput"); onFinishInput(); } + mInputStarted = false; + mStartedInputConnection = null; + } + + void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { + if (!restarting) { + doFinishInput(); + } mInputStarted = true; + mStartedInputConnection = ic; mInputEditorInfo = attribute; + initialize(); + if (DEBUG) Log.v(TAG, "CALL: onStartInput"); onStartInput(attribute, restarting); if (mWindowVisible) { if (mShowInputRequested) { + if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); + mInputViewStarted = true; onStartInputView(mInputEditorInfo, restarting); + startExtractingText(); + } else if (mCandidatesVisibility == View.VISIBLE) { + if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView"); + mCandidatesViewStarted = true; + onStartCandidatesView(mInputEditorInfo, restarting); } - startExtractingText(); } } @@ -1029,8 +1262,17 @@ public class InputMethodService extends AbstractInputMethodService { * {@link #onStartInput(EditorInfo, boolean)} to perform input in a * new editor, or the input method may be left idle. This method is * <em>not</em> called when input restarts in the same editor. + * + * <p>The default + * implementation uses the InputConnection to clear any active composing + * text; you can override this (not calling the base class implementation) + * to perform whatever behavior you would like. */ public void onFinishInput() { + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.finishComposingText(); + } } /** @@ -1073,9 +1315,12 @@ public class InputMethodService extends AbstractInputMethodService { public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { - if (mExtractEditText != null && mExtractedText != null) { + final ExtractEditText eet = mExtractEditText; + if (eet != null && mExtractedText != null) { final int off = mExtractedText.startOffset; - mExtractEditText.setSelection(newSelStart-off, newSelEnd-off); + eet.startInternalChanges(); + eet.setSelection(newSelStart-off, newSelEnd-off); + eet.finishInternalChanges(); } } @@ -1100,6 +1345,18 @@ public class InputMethodService extends AbstractInputMethodService { .hideSoftInputFromInputMethod(mToken, flags); } + /** + * Override this to intercept key down events before they are processed by the + * application. If you return true, the application will not itself + * process the event. If you return true, the normal application processing + * will occur as if the IME had not seen the event at all. + * + * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK + * KeyEvent.KEYCODE_BACK} to hide the current IME UI if it is shown. In + * additional, in fullscreen mode only, it will consume DPAD movement + * events to move the cursor in the extracted text view, not allowing + * them to perform navigation in the underlying application. + */ public boolean onKeyDown(int keyCode, KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { @@ -1108,25 +1365,54 @@ public class InputMethodService extends AbstractInputMethodService { // consume the back key. dismissSoftInput(0); return true; - } - if (mShowCandidatesRequested) { - // If the candidates are shown, we just want to make sure - // they are now hidden but otherwise let the app execute - // the back. - // XXX this needs better interaction with the soft input - // implementation. - //setCandidatesViewShown(false); + } else if (mWindowVisible) { + if (mCandidatesVisibility == View.VISIBLE) { + // If we are showing candidates even if no input area, then + // hide them. + setCandidatesViewShown(false); + return true; + } else { + // If we have the window visible for some other reason -- + // most likely to show candidates -- then just get rid + // of it. This really shouldn't happen, but just in case... + hideWindow(); + return true; + } } } - return false; + + return doMovementKey(keyCode, event, MOVEMENT_DOWN); } + /** + * Override this to intercept special key multiple events before they are + * processed by the + * application. If you return true, the application will not itself + * process the event. If you return true, the normal application processing + * will occur as if the IME had not seen the event at all. + * + * <p>The default implementation always returns false, except when + * in fullscreen mode, where it will consume DPAD movement + * events to move the cursor in the extracted text view, not allowing + * them to perform navigation in the underlying application. + */ public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { - return false; + return doMovementKey(keyCode, event, count); } + /** + * Override this to intercept key up events before they are processed by the + * application. If you return true, the application will not itself + * process the event. If you return true, the normal application processing + * will occur as if the IME had not seen the event at all. + * + * <p>The default implementation always returns false, except when + * in fullscreen mode, where it will consume DPAD movement + * events to move the cursor in the extracted text view, not allowing + * them to perform navigation in the underlying application. + */ public boolean onKeyUp(int keyCode, KeyEvent event) { - return false; + return doMovementKey(keyCode, event, MOVEMENT_UP); } public boolean onTrackballEvent(MotionEvent event) { @@ -1136,21 +1422,176 @@ public class InputMethodService extends AbstractInputMethodService { public void onAppPrivateCommand(String action, Bundle data) { } + static final int MOVEMENT_DOWN = -1; + static final int MOVEMENT_UP = -2; + + boolean doMovementKey(int keyCode, KeyEvent event, int count) { + final ExtractEditText eet = mExtractEditText; + if (isFullscreenMode() && isInputViewShown() && eet != null) { + // If we are in fullscreen mode, the cursor will move around + // the extract edit text, but should NOT cause focus to move + // to other fields. + MovementMethod movement = eet.getMovementMethod(); + Layout layout = eet.getLayout(); + if (movement != null && layout != null) { + // We want our own movement method to handle the key, so the + // cursor will properly move in our own word wrapping. + if (count == MOVEMENT_DOWN) { + if (movement.onKeyDown(eet, + (Spannable)eet.getText(), keyCode, event)) { + return true; + } + } else if (count == MOVEMENT_UP) { + if (movement.onKeyUp(eet, + (Spannable)eet.getText(), keyCode, event)) { + return true; + } + } else { + KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); + if (movement.onKeyDown(eet, + (Spannable)eet.getText(), keyCode, down)) { + KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); + movement.onKeyUp(eet, + (Spannable)eet.getText(), keyCode, up); + while (--count > 0) { + movement.onKeyDown(eet, + (Spannable)eet.getText(), keyCode, down); + movement.onKeyUp(eet, + (Spannable)eet.getText(), keyCode, up); + } + } + } + } + // Regardless of whether the movement method handled the key, + // we never allow DPAD navigation to the application. + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + return true; + } + } + + return false; + } + + /** + * This is called when the user has moved the cursor in the extracted + * text view, when running in fullsreen mode. The default implementation + * performs the corresponding selection change on the underlying text + * editor. + */ + public void onExtractedSelectionChanged(int start, int end) { + InputConnection conn = getCurrentInputConnection(); + if (conn != null) { + conn.setSelection(start, end); + } + } + + /** + * This is called when the user has clicked on the extracted text view, + * when running in fullscreen mode. The default implementation hides + * the candidates view when this happens. Re-implement this to provide + * whatever behavior you want. + */ + public void onExtractedTextClicked() { + setCandidatesViewShown(false); + } + + /** + * This is called when the user has selected a context menu item from the + * extracted text view, when running in fullscreen mode. The default + * implementation sends this action to the current InputConnection's + * {@link InputConnection#performContextMenuAction(int)}, for it + * to be processed in underlying "real" editor. Re-implement this to + * provide whatever behavior you want. + */ + public boolean onExtractTextContextMenuItem(int id) { + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.performContextMenuAction(id); + } + return true; + } + void startExtractingText() { - if (mExtractEditText != null && getCurrentInputStarted() + final ExtractEditText eet = mExtractEditText; + if (eet != null && getCurrentInputStarted() && isFullscreenMode()) { mExtractedToken++; ExtractedTextRequest req = new ExtractedTextRequest(); req.token = mExtractedToken; + req.flags = InputConnection.GET_TEXT_WITH_STYLES; req.hintMaxLines = 10; req.hintMaxChars = 10000; - mExtractedText = mInputConnection.getExtractedText(req, - InputConnection.EXTRACTED_TEXT_MONITOR); - if (mExtractedText != null) { - mExtractEditText.setExtractedText(mExtractedText); + mExtractedText = getCurrentInputConnection().getExtractedText(req, + InputConnection.GET_EXTRACTED_TEXT_MONITOR); + try { + eet.startInternalChanges(); + int inputType = getCurrentInputEditorInfo().inputType; + if ((inputType&EditorInfo.TYPE_MASK_CLASS) + == EditorInfo.TYPE_CLASS_TEXT) { + if ((inputType&EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE) != 0) { + inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; + } + } + eet.setInputType(inputType); + eet.setHint(mInputEditorInfo.hintText); + if (mExtractedText != null) { + eet.setExtractedText(mExtractedText); + } + } finally { + eet.finishInternalChanges(); } - mExtractEditText.setInputType(getCurrentInputEditorInfo().inputType); - mExtractEditText.setHint(mInputEditorInfo.hintText); } } + + /** + * Performs a dump of the InputMethodService's internal state. Override + * to add your own information to the dump. + */ + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + final Printer p = new PrintWriterPrinter(fout); + p.println("Input method service state for " + this + ":"); + p.println(" mWindowCreated=" + mWindowCreated + + " mWindowAdded=" + mWindowAdded + + " mWindowVisible=" + mWindowVisible); + p.println(" Configuration=" + getResources().getConfiguration()); + p.println(" mToken=" + mToken); + p.println(" mInputBinding=" + mInputBinding); + p.println(" mInputConnection=" + mInputConnection); + p.println(" mStartedInputConnection=" + mStartedInputConnection); + p.println(" mInputStarted=" + mInputStarted + + " mInputViewStarted=" + mInputViewStarted + + " mCandidatesViewStarted=" + mCandidatesViewStarted); + + if (mInputEditorInfo != null) { + p.println(" mInputEditorInfo:"); + mInputEditorInfo.dump(p, " "); + } else { + p.println(" mInputEditorInfo: null"); + } + + p.println(" mShowInputRequested=" + mShowInputRequested + + " mLastShowInputRequested=" + mLastShowInputRequested + + " mShowInputForced=" + mShowInputForced); + p.println(" mCandidatesVisibility=" + mCandidatesVisibility + + " mFullscreenApplied=" + mFullscreenApplied + + " mIsFullscreen=" + mIsFullscreen); + + if (mExtractedText != null) { + p.println(" mExtractedText:"); + p.println(" text=" + mExtractedText.text.length() + " chars" + + " startOffset=" + mExtractedText.startOffset); + p.println(" selectionStart=" + mExtractedText.selectionStart + + " selectionEnd=" + mExtractedText.selectionEnd + + " flags=0x" + Integer.toHexString(mExtractedText.flags)); + } else { + p.println(" mExtractedText: null"); + } + p.println(" mExtractedToken=" + mExtractedToken); + p.println(" mIsInputViewShown=" + mIsInputViewShown + + " mStatusIcon=" + mStatusIcon); + } } |