|  | /* | 
|  | * Copyright (C) 2008 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package android.inputmethodservice; | 
|  |  | 
|  | import android.compat.annotation.UnsupportedAppUsage; | 
|  | import android.content.Context; | 
|  | import android.graphics.Rect; | 
|  | import android.os.Bundle; | 
|  | import android.os.Looper; | 
|  | import android.os.Message; | 
|  | import android.util.Log; | 
|  | import android.util.SparseArray; | 
|  | import android.view.InputChannel; | 
|  | import android.view.InputDevice; | 
|  | import android.view.InputEvent; | 
|  | import android.view.InputEventReceiver; | 
|  | import android.view.KeyEvent; | 
|  | import android.view.MotionEvent; | 
|  | import android.view.inputmethod.CompletionInfo; | 
|  | import android.view.inputmethod.CursorAnchorInfo; | 
|  | import android.view.inputmethod.ExtractedText; | 
|  | import android.view.inputmethod.InputMethodSession; | 
|  |  | 
|  | import com.android.internal.os.HandlerCaller; | 
|  | import com.android.internal.os.SomeArgs; | 
|  | import com.android.internal.view.IInputMethodSession; | 
|  |  | 
|  | class IInputMethodSessionWrapper extends IInputMethodSession.Stub | 
|  | implements HandlerCaller.Callback { | 
|  | private static final String TAG = "InputMethodWrapper"; | 
|  |  | 
|  | private static final int DO_DISPLAY_COMPLETIONS = 65; | 
|  | private static final int DO_UPDATE_EXTRACTED_TEXT = 67; | 
|  | private static final int DO_UPDATE_SELECTION = 90; | 
|  | private static final int DO_UPDATE_CURSOR = 95; | 
|  | private static final int DO_UPDATE_CURSOR_ANCHOR_INFO = 99; | 
|  | private static final int DO_APP_PRIVATE_COMMAND = 100; | 
|  | private static final int DO_TOGGLE_SOFT_INPUT = 105; | 
|  | private static final int DO_FINISH_SESSION = 110; | 
|  | private static final int DO_VIEW_CLICKED = 115; | 
|  | private static final int DO_NOTIFY_IME_HIDDEN = 120; | 
|  | private static final int DO_REMOVE_IME_SURFACE = 130; | 
|  |  | 
|  | @UnsupportedAppUsage | 
|  | HandlerCaller mCaller; | 
|  | InputMethodSession mInputMethodSession; | 
|  | InputChannel mChannel; | 
|  | ImeInputEventReceiver mReceiver; | 
|  |  | 
|  | public IInputMethodSessionWrapper(Context context, | 
|  | InputMethodSession inputMethodSession, InputChannel channel) { | 
|  | mCaller = new HandlerCaller(context, null, | 
|  | this, true /*asyncHandler*/); | 
|  | mInputMethodSession = inputMethodSession; | 
|  | mChannel = channel; | 
|  | if (channel != null) { | 
|  | mReceiver = new ImeInputEventReceiver(channel, context.getMainLooper()); | 
|  | } | 
|  | } | 
|  |  | 
|  | public InputMethodSession getInternalInputMethodSession() { | 
|  | return mInputMethodSession; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void executeMessage(Message msg) { | 
|  | if (mInputMethodSession == null) { | 
|  | // The session has been finished. Args needs to be recycled | 
|  | // for cases below. | 
|  | switch (msg.what) { | 
|  | case DO_UPDATE_SELECTION: | 
|  | case DO_APP_PRIVATE_COMMAND: { | 
|  | SomeArgs args = (SomeArgs)msg.obj; | 
|  | args.recycle(); | 
|  | } | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | switch (msg.what) { | 
|  | case DO_DISPLAY_COMPLETIONS: | 
|  | mInputMethodSession.displayCompletions((CompletionInfo[])msg.obj); | 
|  | return; | 
|  | case DO_UPDATE_EXTRACTED_TEXT: | 
|  | mInputMethodSession.updateExtractedText(msg.arg1, | 
|  | (ExtractedText)msg.obj); | 
|  | return; | 
|  | case DO_UPDATE_SELECTION: { | 
|  | SomeArgs args = (SomeArgs)msg.obj; | 
|  | mInputMethodSession.updateSelection(args.argi1, args.argi2, | 
|  | args.argi3, args.argi4, args.argi5, args.argi6); | 
|  | args.recycle(); | 
|  | return; | 
|  | } | 
|  | case DO_UPDATE_CURSOR: { | 
|  | mInputMethodSession.updateCursor((Rect)msg.obj); | 
|  | return; | 
|  | } | 
|  | case DO_UPDATE_CURSOR_ANCHOR_INFO: { | 
|  | mInputMethodSession.updateCursorAnchorInfo((CursorAnchorInfo)msg.obj); | 
|  | return; | 
|  | } | 
|  | case DO_APP_PRIVATE_COMMAND: { | 
|  | SomeArgs args = (SomeArgs)msg.obj; | 
|  | mInputMethodSession.appPrivateCommand((String)args.arg1, | 
|  | (Bundle)args.arg2); | 
|  | args.recycle(); | 
|  | return; | 
|  | } | 
|  | case DO_TOGGLE_SOFT_INPUT: { | 
|  | mInputMethodSession.toggleSoftInput(msg.arg1, msg.arg2); | 
|  | return; | 
|  | } | 
|  | case DO_FINISH_SESSION: { | 
|  | doFinishSession(); | 
|  | return; | 
|  | } | 
|  | case DO_VIEW_CLICKED: { | 
|  | mInputMethodSession.viewClicked(msg.arg1 == 1); | 
|  | return; | 
|  | } | 
|  | case DO_NOTIFY_IME_HIDDEN: { | 
|  | mInputMethodSession.notifyImeHidden(); | 
|  | return; | 
|  | } | 
|  | case DO_REMOVE_IME_SURFACE: { | 
|  | mInputMethodSession.removeImeSurface(); | 
|  | return; | 
|  | } | 
|  | } | 
|  | Log.w(TAG, "Unhandled message code: " + msg.what); | 
|  | } | 
|  |  | 
|  | private void doFinishSession() { | 
|  | mInputMethodSession = null; | 
|  | if (mReceiver != null) { | 
|  | mReceiver.dispose(); | 
|  | mReceiver = null; | 
|  | } | 
|  | if (mChannel != null) { | 
|  | mChannel.dispose(); | 
|  | mChannel = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void displayCompletions(CompletionInfo[] completions) { | 
|  | mCaller.executeOrSendMessage(mCaller.obtainMessageO( | 
|  | DO_DISPLAY_COMPLETIONS, completions)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void updateExtractedText(int token, ExtractedText text) { | 
|  | mCaller.executeOrSendMessage(mCaller.obtainMessageIO( | 
|  | DO_UPDATE_EXTRACTED_TEXT, token, text)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void updateSelection(int oldSelStart, int oldSelEnd, | 
|  | int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { | 
|  | mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION, | 
|  | oldSelStart, oldSelEnd, newSelStart, newSelEnd, | 
|  | candidatesStart, candidatesEnd)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void viewClicked(boolean focusChanged) { | 
|  | mCaller.executeOrSendMessage( | 
|  | mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void notifyImeHidden() { | 
|  | mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_IME_HIDDEN)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void removeImeSurface() { | 
|  | mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_IME_SURFACE)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void updateCursor(Rect newCursor) { | 
|  | mCaller.executeOrSendMessage( | 
|  | mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { | 
|  | mCaller.executeOrSendMessage( | 
|  | mCaller.obtainMessageO(DO_UPDATE_CURSOR_ANCHOR_INFO, cursorAnchorInfo)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void appPrivateCommand(String action, Bundle data) { | 
|  | mCaller.executeOrSendMessage( | 
|  | mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void toggleSoftInput(int showFlags, int hideFlags) { | 
|  | mCaller.executeOrSendMessage( | 
|  | mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finishSession() { | 
|  | mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION)); | 
|  | } | 
|  |  | 
|  | private final class ImeInputEventReceiver extends InputEventReceiver | 
|  | implements InputMethodSession.EventCallback { | 
|  | private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>(); | 
|  |  | 
|  | public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) { | 
|  | super(inputChannel, looper); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onInputEvent(InputEvent event) { | 
|  | if (mInputMethodSession == null) { | 
|  | // The session has been finished. | 
|  | finishInputEvent(event, false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | final int seq = event.getSequenceNumber(); | 
|  | mPendingEvents.put(seq, event); | 
|  | if (event instanceof KeyEvent) { | 
|  | KeyEvent keyEvent = (KeyEvent)event; | 
|  | mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this); | 
|  | } else { | 
|  | MotionEvent motionEvent = (MotionEvent)event; | 
|  | if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) { | 
|  | mInputMethodSession.dispatchTrackballEvent(seq, motionEvent, this); | 
|  | } else { | 
|  | mInputMethodSession.dispatchGenericMotionEvent(seq, motionEvent, this); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finishedEvent(int seq, boolean handled) { | 
|  | int index = mPendingEvents.indexOfKey(seq); | 
|  | if (index >= 0) { | 
|  | InputEvent event = mPendingEvents.valueAt(index); | 
|  | mPendingEvents.removeAt(index); | 
|  | finishInputEvent(event, handled); | 
|  | } | 
|  | } | 
|  | } | 
|  | } |