您的位置:首页 > 移动开发 > Android开发

android 输入法删除输入框里面字符流程分析

2014-04-29 16:33 411 查看
我们先看删除主要调用的方法,该方法在InputMethodService的继承类子类中

getCurrentInputConnection().deleteSurroundingText(1, 0);

此getCurrentInputConnection方法拿到的对象是什么呢?是InputConnectionWrapper类,进入该方法,【注意点一,后面将解释getCurrentInputConnection()为什么拿到的对象是InputConnectionWrapper】

public class InputConnectionWrapper implements InputConnection {


public boolean deleteSurroundingText(int leftLength, int rightLength) {

Log.d(TAG, "InputConnectionWrapper  deleteSurroundingText");

try {
mIInputContext.deleteSurroundingText(leftLength, rightLength);
return true;
} catch (RemoteException e) {
return false;
}
}


而上面的mIInputContext是IInputContext类对象,这个在哪实现的呢?【注意点二,后续将解释,mIInputContext是在哪初始化的】

pateo@pateo-B86N53X:/work/project/a1001eh/frameworks/base$ grep -r "IInputContext.Stub" ./

./core/java/com/android/internal/view/IInputConnectionWrapper.java:public class IInputConnectionWrapper extends IInputContext.Stub {

好吧,我们进入到IInputConnectionWrapper中,进入该方法:

public void deleteSurroundingText(int leftLength, int rightLength) {
dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
leftLength, rightLength));
}


继续看此消息的处理:

case DO_DELETE_SURROUNDING_TEXT: {
Log.w(TAG, "IInputConnectionWrapper class " +  "deleteSurroundingText DO_DELETE_SURROUNDING_TEXT msg");
InputConnection ic = mInputConnection.get();
if (ic == null || !isActive()) {
Log.w(TAG, "IInputConnectionWrapper class " +  "deleteSurroundingText on inactive InputConnection");
return;
}
ic.deleteSurroundingText(msg.arg1, msg.arg2);
return;
}


那么这个mInputConnection.get()得到的是什么呢?其实就是当前EditView的BaseInputConnection【注意点三,后面将解释,为什么这里的mInputConnection.get()得到的是BaseInputConnection对象】,进入该BaseInputConnection类的deleteSurroundingText方法

public boolean deleteSurroundingText(int leftLength, int rightLength) {
if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
+ " / " + rightLength);
Log.d("PateoInputMethod", "BaseInputConnection deleteSurroundingText ");
final Editable content = getEditable();
if (content == null) return false;

beginBatchEdit();

int a = Selection.getSelectionStart(content);
int b = Selection.getSelectionEnd(content);

if (a > b) {
int tmp = a;
a = b;
b = tmp;
}

// ignore the composing text.
int ca = getComposingSpanStart(content);
int cb = getComposingSpanEnd(content);
if (cb < ca) {
int tmp = ca;
ca = cb;
cb = tmp;
}
if (ca != -1 && cb != -1) {
if (ca < a) a = ca;
if (cb > b) b = cb;
}

int deleted = 0;

if (leftLength > 0) {
int start = a - leftLength;
if (start < 0) start = 0;
content.delete(start, a);
deleted = a - start;
}

if (rightLength > 0) {
b = b - deleted;

int end = b + rightLength;
if (end > content.length()) end = content.length();

content.delete(b, end);
}

endBatchEdit();

return true;
}


到这里,我需要解释上面的三个注意点了

第三个注意点:

【注意点三,后面将解释,为什么这里的mInputConnection.get()得到的是BaseInputConnection对象】

我们先来看下mInputConnection.get()的初始化,其在IInputConnectionWrapper类中

public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
mInputConnection = new WeakReference<InputConnection>(conn);
mMainLooper = mainLooper;
mH = new MyHandler(mMainLooper);
}


再看看IInputConnectionWrapper这个构造方法在哪被调用的,我们看到InputMethodManager中有个IInputConnectionWrapper的集成类中调用了此方法

private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
private final InputMethodManager mParentInputMethodManager;

public ControlledInputConnectionWrapper(final Looper mainLooper, final InputConnection conn,
final InputMethodManager inputMethodManager) {
super(mainLooper, conn);
mParentInputMethodManager = inputMethodManager;
}

@Override
public boolean isActive() {
return mParentInputMethodManager.mActive;
}
}


那么上面的super(mainLooper, conn)的调用处的ControlledInputConnectionWrapper构造方法又是在哪被调用的呢?在InputMethodManager类的startInputInner方法中,到这里比较清晰了点,代码如下:

boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {

final View view;
synchronized (mH) {
view = mServedView;

// Make sure we have a window token for the served view.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Starting input: view=" + view);
if (view == null) {
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "ABORT input: no served view!");
return false;
}
}

// Now we need to get an input connection from the served view.
// This is complicated in a couple ways: we can't be holding our lock
// when calling out to the view, and we need to make sure we call into
// the view on the same thread that is driving its view hierarchy.
Handler vh = view.getHandler();
if (vh == null) {
// If the view doesn't have a handler, something has changed out
// from under us, so just bail.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "ABORT input: no handler for view!");
return false;
}
if (vh.getLooper() != Looper.myLooper()) {
// The view is running on a different thread than our own, so
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Starting input: reschedule to view thread");
vh.post(new Runnable() {
public void run() {
Log.d(TAG, "InputMethodManager class startInputInner method");
startInputInner(null, 0, 0, 0);
}
});
return false;
}

// Okay we are now ready to call into the served view and have it
// 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,"InputMethodManager class " +  "Starting input: tba=" + tba + " ic=" + ic);

synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
if (mServedView != view || !mServedConnecting) {
// Something else happened, so abort.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +
"Starting input: finished by someone else (view="
+ mServedView + " conn=" + mServedConnecting + ")");
return false;
}

// If we already have a text box, then this view is already
// connected so we want to restart it.
if (mCurrentTextBoxAttribute == null) {
controlFlags |= CONTROL_START_INITIAL;
}

// Hook 'em up and let 'er rip.
mCurrentTextBoxAttribute = tba;
mServedConnecting = false;
mServedInputConnection = ic;
IInputContext servedContext;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
} else {
servedContext = null;
}

try {
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "START INPUT: " + view + " ic="
+ ic + " tba=" + tba + " controlFlags=#"
+ Integer.toHexString(controlFlags));
InputBindResult res;
if (windowGainingFocus != null) {
res = mService.windowGainedFocus(mClient, windowGainingFocus,
controlFlags, softInputMode, windowFlags,
tba, servedContext);
} else {
res = mService.startInput(mClient,
servedContext, tba, controlFlags);
}


上面的servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);中的ic是在该方法中被赋值的,看下:

InputConnection ic = view.onCreateInputConnection(tba);

追踪其view

/**
* This is the view that should currently be served by an input method,
* regardless of the state of setting that up.
*/
View mServedView;


跟踪mServedView的赋值

private boolean checkFocusNoStartInput(boolean forceNewFocus) {
// This is called a lot, so short-circuit before locking.
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
}

InputConnection ic = null;
synchronized (mH) {
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
}
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "checkFocus: view=" + mServedView
+ " next=" + mNextServedView
+ " forceNewFocus=" + forceNewFocus);

if (mNextServedView == null) {
finishInputLocked();
// In this case, we used to have a focused view on the window,
// but no longer do.  We should make sure the input method is
// no longer shown, since it serves no purpose.
closeCurrentInput();
return false;
}
if (DEBUG) Log.v(TAG,"mServedInputConnection=" + mServedInputConnection);
ic = mServedInputConnection;

mServedView = mNextServedView;
mCurrentTextBoxAttribute = null;
mCompletions = null;
mServedConnecting = true;
}

if (ic != null) {
ic.finishComposingText();
}

return true;
}


继续跟踪mNextServedView

void focusInLocked(View view) {
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "focusIn: " + view);

if (mCurRootView != view.getRootView()) {
// This is a request from a window that isn't in the window with
// IME focus, so ignore it.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Not IME target window, ignoring");
return;
}

mNextServedView = view;
scheduleCheckFocusLocked(view);
}


上面这个focusInLocked(View view) 又是被谁调用的呢?

/**
* Call this when a view receives focus.
* @hide
*/
public void focusIn(View view) {
synchronized (mH) {
focusInLocked(view);
}
}


上面的focus方法又是在哪被调用的呢?是在view中,如下:

protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
if (gainFocus) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}

InputMethodManager imm = InputMethodManager.peekInstance();
if (!gainFocus) {
if (isPressed()) {
setPressed(false);
}
if (imm != null && mAttachInfo != null
&& mAttachInfo.mHasWindowFocus) {
Log.d(TAG, "View class onFocusChanged method InputMethodManager focusOut");
imm.focusOut(this);
}
onFocusLost();
} else if (imm != null && mAttachInfo != null
&& mAttachInfo.mHasWindowFocus) {
Log.d(TAG, "View class onFocusChanged method InputMethodManager focusIn");
imm.focusIn(this);
}


而这个view是什么呢?而这个this就可能是view的继承对象比如TextView,或继承TextView的EditText,而上面的InputConnection ic = view.onCreateInputConnection(tba);实际就是调用了它的子类TextView中的onCreateInputConnection方法:

@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {

Log.d(TAG, "TextView class onCreateInputConnection method start");

if (onCheckIsTextEditor() && isEnabled()) {
if (mInputMethodState == null) {
mInputMethodState = new InputMethodState();
}
outAttrs.inputType = mInputType;
if (mInputContentType != null) {
outAttrs.imeOptions = mInputContentType.imeOptions;
outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
outAttrs.actionLabel = mInputContentType.imeActionLabel;
outAttrs.actionId = mInputContentType.imeActionId;
outAttrs.extras = mInputContentType.extras;
} else {
outAttrs.imeOptions = EditorInfo.IME_NULL;
}
//            if (focusSearch(FOCUS_DOWN) != null) {
//                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
//            }
//            if (focusSearch(FOCUS_UP) != null) {
//                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
//            }
//            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
//                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
//                if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
//                    // An action has not been set, but the enter key will move to
//                    // the next focus, so set the action to that.
//                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
//                } else {
//                    // An action has not been set, and there is no focus to move
//                    // to, so let's just supply a "done" action.
//                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
//                }
//                if (!shouldAdvanceFocusOnEnter()) {
//                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
//                }
//            }
//            if (isMultilineInputType(outAttrs.inputType)) {
//                // Multi-line text editors should always show an enter key.
//                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
//            }
outAttrs.hintText = mHint;
if (mText instanceof Editable) {
Log.d(TAG, "TextView class onCreateInputConnection return EditableInputConnection");
InputConnection ic = new EditableInputConnection(this);
outAttrs.initialSelStart = getSelectionStart();
outAttrs.initialSelEnd = getSelectionEnd();
outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
return ic;
}
}
return null;
}


上面的注意下:public class EditableInputConnection extends BaseInputConnection 现在你明白了吧:mInputConnection.get()得到的就是BaseInputConnection对象

第一个注意点

注意点一,后面将解释getCurrentInputConnection()为什么拿到的对象是InputConnectionWrapper

当光标进入输入框的时候,会走入初始化DO_START_INPUT消息,在这里进行了InputConnectionWrapper对象的生成

case DO_START_INPUT: {
Log.w(TAG, "==================================================>IInputMethodWrapper class " + " DO_START_INPUT msg");
HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext) : null;
EditorInfo info = (EditorInfo)args.arg2;
info.makeCompatible(mTargetSdkVersion);
inputMethod.startInput(ic, info);
return;
}


在这里进行了new InputConnectionWrapper(inputContext)其inputContext又是哪来的呢?

public void startInput(IInputContext inputContext, EditorInfo attribute) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
inputContext, attribute));
}


最终跟踪到InputMethodManagerService类中

case MSG_START_INPUT:
Slog.d(TAG,"InputMethodManagerService class MSG_START_INPUT msg ");
args = (HandlerCaller.SomeArgs)msg.obj;
try {
SessionState session = (SessionState)args.arg1;
setEnabledSessionInMainThread(session);
session.method.startInput((IInputContext)args.arg2,
(EditorInfo)args.arg3);
} catch (RemoteException e) {
}
return true;

InputBindResult attachNewInputLocked(boolean initial) {
if (DEBUG) Slog.v(TAG, "InputMethodManagerService class" +  "attachNewInputLocked method coming...,mBoundToMethod=" + mBoundToMethod + " ,initial=" + initial);
if (!mBoundToMethod) {
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
mBoundToMethod = true;
}
final SessionState session = mCurClient.curSession;
if (initial) {
executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
MSG_START_INPUT, session, mCurInputContext, mCurAttribute));

InputBindResult startInputUncheckedLocked(ClientState cs,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
}

if (mCurClient != cs) {
// If the client is changing, we need to switch over to the new
// one.
unbindCurrentClientLocked();
if (DEBUG) Slog.v(TAG, "InputMethodManagerService class" +  "switching to client: client = "
+ cs.client.asBinder());

// If the screen is on, inform the new client it is active
if (mScreenOn) {
try {
cs.client.setActive(mScreenOn);
} catch (RemoteException e) {
Slog.w(TAG, "InputMethodManagerService class" +  "Got RemoteException sending setActive notification to pid "
+ cs.pid + " uid " + cs.uid);
}
}
}

// Bump up the sequence for this client and attach it.
mCurSeq++;
if (mCurSeq <= 0) mCurSeq = 1;
mCurClient = cs;
mCurInputContext = inputContext;

InputBindResult startInputLocked(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {

@Override
public InputBindResult startInput(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
synchronized (mMethodMap) {
final long ident = Binder.clearCallingIdentity();
try {
return startInputLocked(client, inputContext, attribute, controlFlags);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}

boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {

final View view;
synchronized (mH) {
view = mServedView;

// Make sure we have a window token for the served view.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Starting input: view=" + view);
if (view == null) {
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "ABORT input: no served view!");
return false;
}
}

// Now we need to get an input connection from the served view.
// This is complicated in a couple ways: we can't be holding our lock
// when calling out to the view, and we need to make sure we call into
// the view on the same thread that is driving its view hierarchy.
Handler vh = view.getHandler();
if (vh == null) {
// If the view doesn't have a handler, something has changed out
// from under us, so just bail.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "ABORT input: no handler for view!");
return false;
}
if (vh.getLooper() != Looper.myLooper()) {
// The view is running on a different thread than our own, so
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Starting input: reschedule to view thread");
vh.post(new Runnable() {
public void run() {
Log.d(TAG, "InputMethodManager class startInputInner method");
startInputInner(null, 0, 0, 0);
}
});
return false;
}

// Okay we are now ready to call into the served view and have it
// 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,"InputMethodManager class " +  "Starting input: tba=" + tba + " ic=" + ic);

synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
if (mServedView != view || !mServedConnecting) {
// Something else happened, so abort.
if (DEBUG) Log.v(TAG,"InputMethodManager class " +
"Starting input: finished by someone else (view="
+ mServedView + " conn=" + mServedConnecting + ")");
return false;
}

// If we already have a text box, then this view is already
// connected so we want to restart it.
if (mCurrentTextBoxAttribute == null) {
controlFlags |= CONTROL_START_INITIAL;
}

// Hook 'em up and let 'er rip.
mCurrentTextBoxAttribute = tba;
mServedConnecting = false;
mServedInputConnection = ic;
IInputContext servedContext;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
} else {
servedContext = null;
}

try {
if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "START INPUT: " + view + " ic="
+ ic + " tba=" + tba + " controlFlags=#"
+ Integer.toHexString(controlFlags));
InputBindResult res;
if (windowGainingFocus != null) {
res = mService.windowGainedFocus(mClient, windowGainingFocus,
controlFlags, softInputMode, windowFlags,
tba, servedContext);
} else {
res = mService.startInput(mClient,
servedContext, tba, controlFlags);
主要:

InputConnection ic = view.onCreateInputConnection(tba);

上面还有一句

servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);


即把mServedInputConnection = ic放入了mInputConnection = new WeakReference<InputConnection>(conn);中方便后续get

 private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
private final InputMethodManager mParentInputMethodManager;

public ControlledInputConnectionWrapper(final Looper mainLooper, final InputConnection conn,
final InputMethodManager inputMethodManager) {
super(mainLooper, conn);
mParentInputMethodManager = inputMethodManager;
}

@Override
public boolean isActive() {
return mParentInputMethodManager.mActive;
}
}

public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
mInputConnection = new WeakReference<InputConnection>(conn);
mMainLooper = mainLooper;
mH = new MyHandler(mMainLooper);
}


上面基本说清楚了,但是如果我们再扩展下有关IInputContextCallback.Stub,则会看到如下一些内容

public CharSequence getSelectedText(int flags) {
CharSequence value = null;
try {
InputContextCallback callback = InputContextCallback.getInstance();
mIInputContext.getSelectedText(flags, callback.mSeq, callback);
synchronized (callback) {
callback.waitForResultLocked();
if (callback.mHaveValue) {
value = callback.mSelectedText;
}
}
callback.dispose();
} catch (RemoteException e) {
return null;
}
return value;
}

我们看到callback传入了mIInputContext.getSelectedText方法,看下下面
pateo@pateo-B86N53X:/work/project/a1001eh/frameworks/base$ grep -r "IInputContext.Stub" ./

./core/java/com/android/internal/view/IInputConnectionWrapper.java:public class IInputConnectionWrapper extends IInputContext.Stub {

public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
}
case DO_GET_SELECTED_TEXT: {
SomeArgs args = (SomeArgs)msg.obj;
try {
InputConnection ic = mInputConnection.get();
if (ic == null || !isActive()) {
Log.w(TAG, "IInputConnectionWrapper class " +  "getSelectedText on inactive InputConnection");
args.callback.setSelectedText(null, args.seq);
return;
}
args.callback.setSelectedText(ic.getSelectedText(
msg.arg1), args.seq);
} catch (RemoteException e) {
Log.w(TAG, "IInputConnectionWrapper class " +  "Got RemoteException calling setSelectedText", e);
}
return;
}


再上面看到了又回调了setSelectedText方法
args.callback.setSelectedText(null, args.seq);
args.callback.setSelectedText(ic.getSelectedText(
msg.arg1), args.seq);进入back这个类,看看有哪些方法:
static class InputContextCallback extends IInputContextCallback.Stub {

public int mSeq;
public boolean mHaveValue;
public CharSequence mTextBeforeCursor;
public CharSequence mTextAfterCursor;
public CharSequence mSelectedText;
public ExtractedText mExtractedText;
public int mCursorCapsMode;

// A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
// exclusive access to this object.
private static InputContextCallback sInstance = new InputContextCallback();
private static int sSequenceNumber = 1;

/**
* Returns an InputContextCallback object that is guaranteed not to be in use by
* any other thread. The returned object's 'have value' flag is cleared and its expected
* sequence number is set to a new integer. We use a sequence number so that replies that
* occur after a timeout has expired are not interpreted as replies to a later request.
*/
private static InputContextCallback getInstance() {
synchronized (InputContextCallback.class) {
// Return sInstance if it's non-null, otherwise construct a new callback
InputContextCallback callback;
if (sInstance != null) {
callback = sInstance;
sInstance = null;

// Reset the callback
callback.mHaveValue = false;
} else {
callback = new InputContextCallback();
}

// Set the sequence number
callback.mSeq = sSequenceNumber++;
return callback;
}
}

/**
* Makes the given InputContextCallback available for use in the future.
*/
private void dispose() {
synchronized (InputContextCallback.class) {
// If sInstance is non-null, just let this object be garbage-collected
if (sInstance == null) {
// Allow any objects being held to be gc'ed
mTextAfterCursor = null;
mTextBeforeCursor = null;
mExtractedText = null;
sInstance = this;
}
}
}

public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
synchronized (this) {
if (seq == mSeq) {
mTextBeforeCursor = textBeforeCursor;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setTextBeforeCursor, ignoring.");
}
}
}

public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
synchronized (this) {
if (seq == mSeq) {
mTextAfterCursor = textAfterCursor;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setTextAfterCursor, ignoring.");
}
}
}

public void setSelectedText(CharSequence selectedText, int seq) {
synchronized (this) {
if (seq == mSeq) {
mSelectedText = selectedText;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setSelectedText, ignoring.");
}
}
}

public void setCursorCapsMode(int capsMode, int seq) {
synchronized (this) {
if (seq == mSeq) {
mCursorCapsMode = capsMode;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setCursorCapsMode, ignoring.");
}
}
}

public void setExtractedText(ExtractedText extractedText, int seq) {
synchronized (this) {
if (seq == mSeq) {
mExtractedText = extractedText;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setExtractedText, ignoring.");
}
}
}

/**
* Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
*
* <p>The caller must be synchronized on this callback object.
*/
void waitForResultLocked() {
long startTime = SystemClock.uptimeMillis();
long endTime = startTime + MAX_WAIT_TIME_MILLIS;

while (!mHaveValue) {
long remainingTime = endTime - SystemClock.uptimeMillis();
if (remainingTime <= 0) {
Log.w(TAG, "Timed out waiting on IInputContextCallback");
return;
}
try {
wait(remainingTime);
} catch (InterruptedException e) {
}
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: