1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.inputmethodservice;
18
19import com.android.internal.os.HandlerCaller;
20import com.android.internal.os.SomeArgs;
21import com.android.internal.view.IInputMethodSession;
22
23import android.content.Context;
24import android.graphics.Rect;
25import android.os.Bundle;
26import android.os.Looper;
27import android.os.Message;
28import android.util.Log;
29import android.util.SparseArray;
30import android.view.InputChannel;
31import android.view.InputDevice;
32import android.view.InputEvent;
33import android.view.InputEventReceiver;
34import android.view.KeyEvent;
35import android.view.MotionEvent;
36import android.view.inputmethod.CompletionInfo;
37import android.view.inputmethod.ExtractedText;
38import android.view.inputmethod.InputMethodSession;
39import android.view.inputmethod.CursorAnchorInfo;
40
41class IInputMethodSessionWrapper extends IInputMethodSession.Stub
42        implements HandlerCaller.Callback {
43    private static final String TAG = "InputMethodWrapper";
44
45    private static final int DO_FINISH_INPUT = 60;
46    private static final int DO_DISPLAY_COMPLETIONS = 65;
47    private static final int DO_UPDATE_EXTRACTED_TEXT = 67;
48    private static final int DO_UPDATE_SELECTION = 90;
49    private static final int DO_UPDATE_CURSOR = 95;
50    private static final int DO_UPDATE_CURSOR_ANCHOR_INFO = 99;
51    private static final int DO_APP_PRIVATE_COMMAND = 100;
52    private static final int DO_TOGGLE_SOFT_INPUT = 105;
53    private static final int DO_FINISH_SESSION = 110;
54    private static final int DO_VIEW_CLICKED = 115;
55
56    HandlerCaller mCaller;
57    InputMethodSession mInputMethodSession;
58    InputChannel mChannel;
59    ImeInputEventReceiver mReceiver;
60
61    public IInputMethodSessionWrapper(Context context,
62            InputMethodSession inputMethodSession, InputChannel channel) {
63        mCaller = new HandlerCaller(context, null,
64                this, true /*asyncHandler*/);
65        mInputMethodSession = inputMethodSession;
66        mChannel = channel;
67        if (channel != null) {
68            mReceiver = new ImeInputEventReceiver(channel, context.getMainLooper());
69        }
70    }
71
72    public InputMethodSession getInternalInputMethodSession() {
73        return mInputMethodSession;
74    }
75
76    @Override
77    public void executeMessage(Message msg) {
78        if (mInputMethodSession == null) {
79            // The session has been finished. Args needs to be recycled
80            // for cases below.
81            switch (msg.what) {
82                case DO_UPDATE_SELECTION:
83                case DO_APP_PRIVATE_COMMAND: {
84                    SomeArgs args = (SomeArgs)msg.obj;
85                    args.recycle();
86                }
87            }
88            return;
89        }
90
91        switch (msg.what) {
92            case DO_FINISH_INPUT:
93                mInputMethodSession.finishInput();
94                return;
95            case DO_DISPLAY_COMPLETIONS:
96                mInputMethodSession.displayCompletions((CompletionInfo[])msg.obj);
97                return;
98            case DO_UPDATE_EXTRACTED_TEXT:
99                mInputMethodSession.updateExtractedText(msg.arg1,
100                        (ExtractedText)msg.obj);
101                return;
102            case DO_UPDATE_SELECTION: {
103                SomeArgs args = (SomeArgs)msg.obj;
104                mInputMethodSession.updateSelection(args.argi1, args.argi2,
105                        args.argi3, args.argi4, args.argi5, args.argi6);
106                args.recycle();
107                return;
108            }
109            case DO_UPDATE_CURSOR: {
110                mInputMethodSession.updateCursor((Rect)msg.obj);
111                return;
112            }
113            case DO_UPDATE_CURSOR_ANCHOR_INFO: {
114                mInputMethodSession.updateCursorAnchorInfo((CursorAnchorInfo)msg.obj);
115                return;
116            }
117            case DO_APP_PRIVATE_COMMAND: {
118                SomeArgs args = (SomeArgs)msg.obj;
119                mInputMethodSession.appPrivateCommand((String)args.arg1,
120                        (Bundle)args.arg2);
121                args.recycle();
122                return;
123            }
124            case DO_TOGGLE_SOFT_INPUT: {
125                mInputMethodSession.toggleSoftInput(msg.arg1, msg.arg2);
126                return;
127            }
128            case DO_FINISH_SESSION: {
129                doFinishSession();
130                return;
131            }
132            case DO_VIEW_CLICKED: {
133                mInputMethodSession.viewClicked(msg.arg1 == 1);
134                return;
135            }
136        }
137        Log.w(TAG, "Unhandled message code: " + msg.what);
138    }
139
140    private void doFinishSession() {
141        mInputMethodSession = null;
142        if (mReceiver != null) {
143            mReceiver.dispose();
144            mReceiver = null;
145        }
146        if (mChannel != null) {
147            mChannel.dispose();
148            mChannel = null;
149        }
150    }
151
152    @Override
153    public void finishInput() {
154        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT));
155    }
156
157    @Override
158    public void displayCompletions(CompletionInfo[] completions) {
159        mCaller.executeOrSendMessage(mCaller.obtainMessageO(
160                DO_DISPLAY_COMPLETIONS, completions));
161    }
162
163    @Override
164    public void updateExtractedText(int token, ExtractedText text) {
165        mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
166                DO_UPDATE_EXTRACTED_TEXT, token, text));
167    }
168
169    @Override
170    public void updateSelection(int oldSelStart, int oldSelEnd,
171            int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
172        mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION,
173                oldSelStart, oldSelEnd, newSelStart, newSelEnd,
174                candidatesStart, candidatesEnd));
175    }
176
177    @Override
178    public void viewClicked(boolean focusChanged) {
179        mCaller.executeOrSendMessage(
180                mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
181    }
182
183    @Override
184    public void updateCursor(Rect newCursor) {
185        mCaller.executeOrSendMessage(
186                mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
187    }
188
189    @Override
190    public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
191        mCaller.executeOrSendMessage(
192                mCaller.obtainMessageO(DO_UPDATE_CURSOR_ANCHOR_INFO, cursorAnchorInfo));
193    }
194
195    @Override
196    public void appPrivateCommand(String action, Bundle data) {
197        mCaller.executeOrSendMessage(
198                mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
199    }
200
201    @Override
202    public void toggleSoftInput(int showFlags, int hideFlags) {
203        mCaller.executeOrSendMessage(
204                mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
205    }
206
207    @Override
208    public void finishSession() {
209        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION));
210    }
211
212    private final class ImeInputEventReceiver extends InputEventReceiver
213            implements InputMethodSession.EventCallback {
214        private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>();
215
216        public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
217            super(inputChannel, looper);
218        }
219
220        @Override
221        public void onInputEvent(InputEvent event) {
222            if (mInputMethodSession == null) {
223                // The session has been finished.
224                finishInputEvent(event, false);
225                return;
226            }
227
228            final int seq = event.getSequenceNumber();
229            mPendingEvents.put(seq, event);
230            if (event instanceof KeyEvent) {
231                KeyEvent keyEvent = (KeyEvent)event;
232                mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this);
233            } else {
234                MotionEvent motionEvent = (MotionEvent)event;
235                if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
236                    mInputMethodSession.dispatchTrackballEvent(seq, motionEvent, this);
237                } else {
238                    mInputMethodSession.dispatchGenericMotionEvent(seq, motionEvent, this);
239                }
240            }
241        }
242
243        @Override
244        public void finishedEvent(int seq, boolean handled) {
245            int index = mPendingEvents.indexOfKey(seq);
246            if (index >= 0) {
247                InputEvent event = mPendingEvents.valueAt(index);
248                mPendingEvents.removeAt(index);
249                finishInputEvent(event, handled);
250            }
251        }
252    }
253}
254