summaryrefslogtreecommitdiffstats
path: root/core/java/android/inputmethodservice/InputMethodService.java
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-02-10 15:44:00 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-02-10 15:44:00 -0800
commitd24b8183b93e781080b2c16c487e60d51c12da31 (patch)
treefbb89154858984eb8e41556da7e9433040d55cd4 /core/java/android/inputmethodservice/InputMethodService.java
parentf1e484acb594a726fb57ad0ae4cfe902c7f35858 (diff)
downloadframeworks_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.java555
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);
+ }
}