summaryrefslogtreecommitdiffstats
path: root/core/java/android/view/inputmethod/InputMethodManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/view/inputmethod/InputMethodManager.java')
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java300
1 files changed, 156 insertions, 144 deletions
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a9a9594..99d5aa5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -26,11 +26,14 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewRoot;
+import com.android.internal.os.HandlerCaller;
import com.android.internal.view.IInputConnectionWrapper;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodCallback;
@@ -39,7 +42,11 @@ import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputBindResult;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Central system API to the overall input method framework (IMF) architecture,
@@ -199,8 +206,7 @@ public final class InputMethodManager {
// global lock.
final H mH;
- // The currently active input connection.
- final MutableInputConnectionWrapper mInputConnectionWrapper;
+ // Our generic input connection if the current target does not have its own.
final IInputContext mIInputContext;
/**
@@ -208,11 +214,6 @@ public final class InputMethodManager {
*/
boolean mActive = false;
- /**
- * The current base input connection, used when mActive is true.
- */
- InputConnection mCurrentInputConnection;
-
// -----------------------------------------------------------
/**
@@ -270,7 +271,7 @@ public final class InputMethodManager {
// -----------------------------------------------------------
- static final int MSG_CHECK_FOCUS = 1;
+ static final int MSG_DUMP = 1;
class H extends Handler {
H(Looper looper) {
@@ -280,85 +281,55 @@ public final class InputMethodManager {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_CHECK_FOCUS:
- checkFocus();
+ case MSG_DUMP: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ try {
+ doDump((FileDescriptor)args.arg1,
+ (PrintWriter)args.arg2, (String[])args.arg3);
+ } catch (RuntimeException e) {
+ ((PrintWriter)args.arg2).println("Exception: " + e);
+ }
+ synchronized (args.arg4) {
+ ((CountDownLatch)args.arg4).countDown();
+ }
return;
+ }
}
}
}
- static class NoOpInputConnection implements InputConnection {
-
- public boolean clearMetaKeyStates(int states) {
- return false;
- }
-
- public boolean beginBatchEdit() {
- return false;
- }
-
- public boolean endBatchEdit() {
- return false;
- }
-
- public boolean commitCompletion(CompletionInfo text) {
- return false;
- }
-
- public boolean commitText(CharSequence text, int newCursorPosition) {
- return false;
- }
-
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- return false;
- }
-
- public int getCursorCapsMode(int reqModes) {
- return 0;
- }
-
- public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- return null;
- }
-
- public CharSequence getTextAfterCursor(int n) {
- return null;
+ class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
+ public ControlledInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
+ super(mainLooper, conn);
}
- public CharSequence getTextBeforeCursor(int n) {
- return null;
- }
-
- public boolean hideStatusIcon() {
- return false;
- }
-
- public boolean performPrivateCommand(String action, Bundle data) {
- return false;
- }
-
- public boolean sendKeyEvent(KeyEvent event) {
- return false;
- }
-
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- return false;
- }
-
- public boolean finishComposingText() {
- return false;
- }
-
- public boolean showStatusIcon(String packageName, int resId) {
- return false;
+ public boolean isActive() {
+ return mActive;
}
}
- final NoOpInputConnection mNoOpInputConnection = new NoOpInputConnection();
-
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
- public void setUsingInputMethod(boolean state) {
+ @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ // No need to check for dump permission, since we only give this
+ // interface to the system.
+ CountDownLatch latch = new CountDownLatch(1);
+ HandlerCaller.SomeArgs sargs = new HandlerCaller.SomeArgs();
+ sargs.arg1 = fd;
+ sargs.arg2 = fout;
+ sargs.arg3 = args;
+ sargs.arg4 = latch;
+ mH.sendMessage(mH.obtainMessage(MSG_DUMP, sargs));
+ try {
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ fout.println("Timeout waiting for dump");
+ }
+ } catch (InterruptedException e) {
+ fout.println("Interrupted waiting for dump");
+ }
+ }
+
+ public void setUsingInputMethod(boolean state) {
}
public void onBindMethod(InputBindResult res) {
@@ -403,62 +374,17 @@ public final class InputMethodManager {
public void setActive(boolean active) {
mActive = active;
- mInputConnectionWrapper.setBaseInputConnection(active
- ? mCurrentInputConnection : mNoOpInputConnection);
}
};
- final InputConnection mDummyInputConnection = new BaseInputConnection(this) {
- public boolean beginBatchEdit() {
- return false;
- }
- public boolean endBatchEdit() {
- return false;
- }
- public boolean commitText(CharSequence text, int newCursorPosition) {
- return false;
- }
- public boolean commitCompletion(CompletionInfo text) {
- return false;
- }
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- return false;
- }
- public ExtractedText getExtractedText(ExtractedTextRequest request,
- int flags) {
- return null;
- }
- public CharSequence getTextAfterCursor(int n) {
- return null;
- }
- public CharSequence getTextBeforeCursor(int n) {
- return null;
- }
- public int getCursorCapsMode(int reqModes) {
- return 0;
- }
- public boolean clearMetaKeyStates(int states) {
- return false;
- }
- public boolean performPrivateCommand(String action, Bundle data) {
- return false;
- }
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- return false;
- }
- public boolean finishComposingText() {
- return false;
- }
- };
+ final InputConnection mDummyInputConnection = new BaseInputConnection(this, true);
InputMethodManager(IInputMethodManager service, Looper looper) {
mService = service;
mMainLooper = looper;
mH = new H(looper);
- mInputConnectionWrapper = new MutableInputConnectionWrapper(mNoOpInputConnection);
- mIInputContext = new IInputConnectionWrapper(looper,
- mInputConnectionWrapper);
- setCurrentInputConnection(mDummyInputConnection);
+ mIInputContext = new ControlledInputConnectionWrapper(looper,
+ mDummyInputConnection);
if (mInstance == null) {
mInstance = this;
@@ -563,22 +489,12 @@ public final class InputMethodManager {
}
/**
- * Record the desired input connection, but only set it if mActive is true.
- */
- void setCurrentInputConnection(InputConnection connection) {
- mCurrentInputConnection = connection;
- mInputConnectionWrapper.setBaseInputConnection(mActive
- ? connection : mNoOpInputConnection);
- }
-
- /**
* Reset all of the state associated with a served view being connected
* to an input method
*/
void clearConnectionLocked() {
mCurrentTextBoxAttribute = null;
mServedInputConnection = null;
- setCurrentInputConnection(mDummyInputConnection);
}
/**
@@ -659,13 +575,20 @@ public final class InputMethodManager {
}
/**
- * Flag for {@link #showSoftInput} to indicate that the this is an implicit
+ * Flag for {@link #showSoftInput} to indicate that this is an implicit
* request to show the input window, not as the result of a direct request
* by the user. The window may not be shown in this case.
*/
public static final int SHOW_IMPLICIT = 0x0001;
/**
+ * Flag for {@link #showSoftInput} to indicate that the user has forced
+ * the input method open (such as by long-pressing menu) so it should
+ * not be closed until they explicitly do so.
+ */
+ public static final int SHOW_FORCED = 0x0002;
+
+ /**
* Explicitly request that the current input method's soft input area be
* shown to the user, if needed. Call this if the user interacts with
* your view in such a way that they have expressed they would like to
@@ -689,6 +612,14 @@ public final class InputMethodManager {
}
}
+ /** @hide */
+ public void showSoftInputUnchecked(int flags) {
+ try {
+ mService.showSoftInput(mClient, flags);
+ } catch (RemoteException e) {
+ }
+ }
+
/**
* Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
* input window should only be hidden if it was not explicitly shown
@@ -697,6 +628,13 @@ public final class InputMethodManager {
public static final int HIDE_IMPLICIT_ONLY = 0x0001;
/**
+ * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
+ * input window should normally be hidden, unless it was originally
+ * shown with {@link #SHOW_FORCED}.
+ */
+ public static final int HIDE_NOT_ALWAYS = 0x0002;
+
+ /**
* Request to hide the soft input window from the context of the window
* that is currently accepting input. This should be called as a result
* of the user doing some actually than fairly explicitly requests to
@@ -779,6 +717,8 @@ public final class InputMethodManager {
// do its stuff.
// Life is good: let's hook everything up!
EditorInfo tba = new EditorInfo();
+ tba.packageName = view.getContext().getPackageName();
+ tba.fieldId = view.getId();
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
@@ -801,22 +741,23 @@ public final class InputMethodManager {
mCurrentTextBoxAttribute = tba;
mServedConnecting = false;
mServedInputConnection = ic;
+ IInputContext servedContext;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
- setCurrentInputConnection(ic);
+ servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic);
} else {
- setCurrentInputConnection(mDummyInputConnection);
+ servedContext = null;
}
try {
if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
+ ic + " tba=" + tba + " initial=" + initial);
- InputBindResult res = mService.startInput(mClient, tba, initial,
- mCurMethod == null);
+ InputBindResult res = mService.startInput(mClient,
+ servedContext, tba, initial, mCurMethod == null);
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res != null) {
if (res.id != null) {
@@ -885,10 +826,20 @@ public final class InputMethodManager {
+ " winFocus=" + view.hasWindowFocus());
if (mServedView == view) {
ic = mServedInputConnection;
- if (view.hasWindowFocus()) {
+ // The following code would auto-hide the IME if we end up
+ // with no more views with focus. This can happen, however,
+ // whenever we go into touch mode, so it ends up hiding
+ // at times when we don't really want it to. For now it
+ // seems better to just turn it all off.
+ if (false && view.hasWindowFocus()) {
mLastServedView = view;
- mH.removeMessages(MSG_CHECK_FOCUS);
- mH.sendEmptyMessage(MSG_CHECK_FOCUS);
+ Handler vh = view.getHandler();
+ if (vh != null) {
+ // This will result in a call to checkFocus() below.
+ vh.removeMessages(ViewRoot.CHECK_FOCUS);
+ vh.sendMessage(vh.obtainMessage(ViewRoot.CHECK_FOCUS,
+ view));
+ }
}
}
}
@@ -898,8 +849,14 @@ public final class InputMethodManager {
}
}
- void checkFocus() {
+ /**
+ * @hide
+ */
+ public void checkFocus(View view) {
synchronized (mH) {
+ if (view != mLastServedView) {
+ return;
+ }
if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+ " last=" + mLastServedView);
if (mServedView == mLastServedView) {
@@ -915,7 +872,7 @@ public final class InputMethodManager {
void closeCurrentInput() {
try {
- mService.hideSoftInput(mClient, 0);
+ mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS);
} catch (RemoteException e) {
}
}
@@ -1006,6 +963,32 @@ public final class InputMethodManager {
}
/**
+ * Call {@link InputMethodSession#appPrivateCommand(String, Bundle)
+ * InputMethodSession.appPrivateCommand()} on the current Input Method.
+ * @param view Optional View that is sending the command, or null if
+ * you want to send the command regardless of the view that is attached
+ * to the input method.
+ * @param action Name of the command to be performed. This <em>must</em>
+ * be a scoped name, i.e. prefixed with a package name you own, so that
+ * different developers will not create conflicting commands.
+ * @param data Any data to include with the command.
+ */
+ public void sendAppPrivateCommand(View view, String action, Bundle data) {
+ synchronized (mH) {
+ if ((view != null && mServedView != view)
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ return;
+ }
+ try {
+ if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
+ mCurMethod.appPrivateCommand(action, data);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
+ /**
* Force switch to a new input method component. This can only be called
* from the currently active input method, as validated by the given token.
* @param token Supplies the identifying token given to an input method
@@ -1048,7 +1031,7 @@ public final class InputMethodManager {
synchronized (mH) {
if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
- if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
+ if (mCurMethod == null) {
try {
callback.finishedEvent(seq, false);
} catch (RemoteException e) {
@@ -1116,4 +1099,33 @@ public final class InputMethodManager {
}
}
}
+
+ void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ final Printer p = new PrintWriterPrinter(fout);
+ p.println("Input method client state for " + this + ":");
+
+ p.println(" mService=" + mService);
+ p.println(" mMainLooper=" + mMainLooper);
+ p.println(" mIInputContext=" + mIInputContext);
+ p.println(" mActive=" + mActive
+ + " mBindSequence=" + mBindSequence
+ + " mCurId=" + mCurId);
+ p.println(" mCurMethod=" + mCurMethod);
+ p.println(" mServedView=" + mServedView);
+ p.println(" mLastServedView=" + mLastServedView);
+ p.println(" mServedConnecting=" + mServedConnecting);
+ if (mCurrentTextBoxAttribute != null) {
+ p.println(" mCurrentTextBoxAttribute:");
+ mCurrentTextBoxAttribute.dump(p, " ");
+ } else {
+ p.println(" mCurrentTextBoxAttribute: null");
+ }
+ p.println(" mServedInputConnection=" + mServedInputConnection);
+ p.println(" mCompletions=" + mCompletions);
+ p.println(" mCursorRect=" + mCursorRect);
+ p.println(" mCursorSelStart=" + mCursorSelStart
+ + " mCursorSelEnd=" + mCursorSelEnd
+ + " mCursorCandStart=" + mCursorCandStart
+ + " mCursorCandEnd=" + mCursorCandEnd);
+ }
}