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 com.android.internal.view;
18
19import android.os.Bundle;
20import android.os.Handler;
21import android.os.Looper;
22import android.os.Message;
23import android.os.RemoteException;
24import android.util.Log;
25import android.view.KeyEvent;
26import android.view.inputmethod.CompletionInfo;
27import android.view.inputmethod.CorrectionInfo;
28import android.view.inputmethod.ExtractedTextRequest;
29import android.view.inputmethod.InputConnection;
30
31import java.lang.ref.WeakReference;
32
33public class IInputConnectionWrapper extends IInputContext.Stub {
34    static final String TAG = "IInputConnectionWrapper";
35
36    private static final int DO_GET_TEXT_AFTER_CURSOR = 10;
37    private static final int DO_GET_TEXT_BEFORE_CURSOR = 20;
38    private static final int DO_GET_SELECTED_TEXT = 25;
39    private static final int DO_GET_CURSOR_CAPS_MODE = 30;
40    private static final int DO_GET_EXTRACTED_TEXT = 40;
41    private static final int DO_COMMIT_TEXT = 50;
42    private static final int DO_COMMIT_COMPLETION = 55;
43    private static final int DO_COMMIT_CORRECTION = 56;
44    private static final int DO_SET_SELECTION = 57;
45    private static final int DO_PERFORM_EDITOR_ACTION = 58;
46    private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59;
47    private static final int DO_SET_COMPOSING_TEXT = 60;
48    private static final int DO_SET_COMPOSING_REGION = 63;
49    private static final int DO_FINISH_COMPOSING_TEXT = 65;
50    private static final int DO_SEND_KEY_EVENT = 70;
51    private static final int DO_DELETE_SURROUNDING_TEXT = 80;
52    private static final int DO_BEGIN_BATCH_EDIT = 90;
53    private static final int DO_END_BATCH_EDIT = 95;
54    private static final int DO_REPORT_FULLSCREEN_MODE = 100;
55    private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
56    private static final int DO_CLEAR_META_KEY_STATES = 130;
57    private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
58
59    private WeakReference<InputConnection> mInputConnection;
60
61    private Looper mMainLooper;
62    private Handler mH;
63
64    static class SomeArgs {
65        Object arg1;
66        Object arg2;
67        IInputContextCallback callback;
68        int seq;
69    }
70
71    class MyHandler extends Handler {
72        MyHandler(Looper looper) {
73            super(looper);
74        }
75
76        @Override
77        public void handleMessage(Message msg) {
78            executeMessage(msg);
79        }
80    }
81
82    public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
83        mInputConnection = new WeakReference<InputConnection>(conn);
84        mMainLooper = mainLooper;
85        mH = new MyHandler(mMainLooper);
86    }
87
88    public boolean isActive() {
89        return true;
90    }
91
92    public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
93        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
94    }
95
96    public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
97        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
98    }
99
100    public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
101        dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
102    }
103
104    public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
105        dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
106    }
107
108    public void getExtractedText(ExtractedTextRequest request,
109            int flags, int seq, IInputContextCallback callback) {
110        dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
111                request, seq, callback));
112    }
113
114    public void commitText(CharSequence text, int newCursorPosition) {
115        dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
116    }
117
118    public void commitCompletion(CompletionInfo text) {
119        dispatchMessage(obtainMessageO(DO_COMMIT_COMPLETION, text));
120    }
121
122    public void commitCorrection(CorrectionInfo info) {
123        dispatchMessage(obtainMessageO(DO_COMMIT_CORRECTION, info));
124    }
125
126    public void setSelection(int start, int end) {
127        dispatchMessage(obtainMessageII(DO_SET_SELECTION, start, end));
128    }
129
130    public void performEditorAction(int id) {
131        dispatchMessage(obtainMessageII(DO_PERFORM_EDITOR_ACTION, id, 0));
132    }
133
134    public void performContextMenuAction(int id) {
135        dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0));
136    }
137
138    public void setComposingRegion(int start, int end) {
139        dispatchMessage(obtainMessageII(DO_SET_COMPOSING_REGION, start, end));
140    }
141
142    public void setComposingText(CharSequence text, int newCursorPosition) {
143        dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text));
144    }
145
146    public void finishComposingText() {
147        dispatchMessage(obtainMessage(DO_FINISH_COMPOSING_TEXT));
148    }
149
150    public void sendKeyEvent(KeyEvent event) {
151        dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event));
152    }
153
154    public void clearMetaKeyStates(int states) {
155        dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0));
156    }
157
158    public void deleteSurroundingText(int leftLength, int rightLength) {
159        dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
160            leftLength, rightLength));
161    }
162
163    public void beginBatchEdit() {
164        dispatchMessage(obtainMessage(DO_BEGIN_BATCH_EDIT));
165    }
166
167    public void endBatchEdit() {
168        dispatchMessage(obtainMessage(DO_END_BATCH_EDIT));
169    }
170
171    public void reportFullscreenMode(boolean enabled) {
172        dispatchMessage(obtainMessageII(DO_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0));
173    }
174
175    public void performPrivateCommand(String action, Bundle data) {
176        dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
177    }
178
179    public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
180            IInputContextCallback callback) {
181        dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
182                seq, callback));
183    }
184
185    void dispatchMessage(Message msg) {
186        // If we are calling this from the main thread, then we can call
187        // right through.  Otherwise, we need to send the message to the
188        // main thread.
189        if (Looper.myLooper() == mMainLooper) {
190            executeMessage(msg);
191            msg.recycle();
192            return;
193        }
194
195        mH.sendMessage(msg);
196    }
197
198    void executeMessage(Message msg) {
199        switch (msg.what) {
200            case DO_GET_TEXT_AFTER_CURSOR: {
201                SomeArgs args = (SomeArgs)msg.obj;
202                try {
203                    InputConnection ic = mInputConnection.get();
204                    if (ic == null || !isActive()) {
205                        Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
206                        args.callback.setTextAfterCursor(null, args.seq);
207                        return;
208                    }
209                    args.callback.setTextAfterCursor(ic.getTextAfterCursor(
210                            msg.arg1, msg.arg2), args.seq);
211                } catch (RemoteException e) {
212                    Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
213                }
214                return;
215            }
216            case DO_GET_TEXT_BEFORE_CURSOR: {
217                SomeArgs args = (SomeArgs)msg.obj;
218                try {
219                    InputConnection ic = mInputConnection.get();
220                    if (ic == null || !isActive()) {
221                        Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
222                        args.callback.setTextBeforeCursor(null, args.seq);
223                        return;
224                    }
225                    args.callback.setTextBeforeCursor(ic.getTextBeforeCursor(
226                            msg.arg1, msg.arg2), args.seq);
227                } catch (RemoteException e) {
228                    Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
229                }
230                return;
231            }
232            case DO_GET_SELECTED_TEXT: {
233                SomeArgs args = (SomeArgs)msg.obj;
234                try {
235                    InputConnection ic = mInputConnection.get();
236                    if (ic == null || !isActive()) {
237                        Log.w(TAG, "getSelectedText on inactive InputConnection");
238                        args.callback.setSelectedText(null, args.seq);
239                        return;
240                    }
241                    args.callback.setSelectedText(ic.getSelectedText(
242                            msg.arg1), args.seq);
243                } catch (RemoteException e) {
244                    Log.w(TAG, "Got RemoteException calling setSelectedText", e);
245                }
246                return;
247            }
248            case DO_GET_CURSOR_CAPS_MODE: {
249                SomeArgs args = (SomeArgs)msg.obj;
250                try {
251                    InputConnection ic = mInputConnection.get();
252                    if (ic == null || !isActive()) {
253                        Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
254                        args.callback.setCursorCapsMode(0, args.seq);
255                        return;
256                    }
257                    args.callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
258                            args.seq);
259                } catch (RemoteException e) {
260                    Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
261                }
262                return;
263            }
264            case DO_GET_EXTRACTED_TEXT: {
265                SomeArgs args = (SomeArgs)msg.obj;
266                try {
267                    InputConnection ic = mInputConnection.get();
268                    if (ic == null || !isActive()) {
269                        Log.w(TAG, "getExtractedText on inactive InputConnection");
270                        args.callback.setExtractedText(null, args.seq);
271                        return;
272                    }
273                    args.callback.setExtractedText(ic.getExtractedText(
274                            (ExtractedTextRequest)args.arg1, msg.arg1), args.seq);
275                } catch (RemoteException e) {
276                    Log.w(TAG, "Got RemoteException calling setExtractedText", e);
277                }
278                return;
279            }
280            case DO_COMMIT_TEXT: {
281                InputConnection ic = mInputConnection.get();
282                if (ic == null || !isActive()) {
283                    Log.w(TAG, "commitText on inactive InputConnection");
284                    return;
285                }
286                ic.commitText((CharSequence)msg.obj, msg.arg1);
287                return;
288            }
289            case DO_SET_SELECTION: {
290                InputConnection ic = mInputConnection.get();
291                if (ic == null || !isActive()) {
292                    Log.w(TAG, "setSelection on inactive InputConnection");
293                    return;
294                }
295                ic.setSelection(msg.arg1, msg.arg2);
296                return;
297            }
298            case DO_PERFORM_EDITOR_ACTION: {
299                InputConnection ic = mInputConnection.get();
300                if (ic == null || !isActive()) {
301                    Log.w(TAG, "performEditorAction on inactive InputConnection");
302                    return;
303                }
304                ic.performEditorAction(msg.arg1);
305                return;
306            }
307            case DO_PERFORM_CONTEXT_MENU_ACTION: {
308                InputConnection ic = mInputConnection.get();
309                if (ic == null || !isActive()) {
310                    Log.w(TAG, "performContextMenuAction on inactive InputConnection");
311                    return;
312                }
313                ic.performContextMenuAction(msg.arg1);
314                return;
315            }
316            case DO_COMMIT_COMPLETION: {
317                InputConnection ic = mInputConnection.get();
318                if (ic == null || !isActive()) {
319                    Log.w(TAG, "commitCompletion on inactive InputConnection");
320                    return;
321                }
322                ic.commitCompletion((CompletionInfo)msg.obj);
323                return;
324            }
325            case DO_COMMIT_CORRECTION: {
326                InputConnection ic = mInputConnection.get();
327                if (ic == null || !isActive()) {
328                    Log.w(TAG, "commitCorrection on inactive InputConnection");
329                    return;
330                }
331                ic.commitCorrection((CorrectionInfo)msg.obj);
332                return;
333            }
334            case DO_SET_COMPOSING_TEXT: {
335                InputConnection ic = mInputConnection.get();
336                if (ic == null || !isActive()) {
337                    Log.w(TAG, "setComposingText on inactive InputConnection");
338                    return;
339                }
340                ic.setComposingText((CharSequence)msg.obj, msg.arg1);
341                return;
342            }
343            case DO_SET_COMPOSING_REGION: {
344                InputConnection ic = mInputConnection.get();
345                if (ic == null || !isActive()) {
346                    Log.w(TAG, "setComposingRegion on inactive InputConnection");
347                    return;
348                }
349                ic.setComposingRegion(msg.arg1, msg.arg2);
350                return;
351            }
352            case DO_FINISH_COMPOSING_TEXT: {
353                InputConnection ic = mInputConnection.get();
354                // Note we do NOT check isActive() here, because this is safe
355                // for an IME to call at any time, and we need to allow it
356                // through to clean up our state after the IME has switched to
357                // another client.
358                if (ic == null) {
359                    Log.w(TAG, "finishComposingText on inactive InputConnection");
360                    return;
361                }
362                ic.finishComposingText();
363                return;
364            }
365            case DO_SEND_KEY_EVENT: {
366                InputConnection ic = mInputConnection.get();
367                if (ic == null || !isActive()) {
368                    Log.w(TAG, "sendKeyEvent on inactive InputConnection");
369                    return;
370                }
371                ic.sendKeyEvent((KeyEvent)msg.obj);
372                return;
373            }
374            case DO_CLEAR_META_KEY_STATES: {
375                InputConnection ic = mInputConnection.get();
376                if (ic == null || !isActive()) {
377                    Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
378                    return;
379                }
380                ic.clearMetaKeyStates(msg.arg1);
381                return;
382            }
383            case DO_DELETE_SURROUNDING_TEXT: {
384                InputConnection ic = mInputConnection.get();
385                if (ic == null || !isActive()) {
386                    Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
387                    return;
388                }
389                ic.deleteSurroundingText(msg.arg1, msg.arg2);
390                return;
391            }
392            case DO_BEGIN_BATCH_EDIT: {
393                InputConnection ic = mInputConnection.get();
394                if (ic == null || !isActive()) {
395                    Log.w(TAG, "beginBatchEdit on inactive InputConnection");
396                    return;
397                }
398                ic.beginBatchEdit();
399                return;
400            }
401            case DO_END_BATCH_EDIT: {
402                InputConnection ic = mInputConnection.get();
403                if (ic == null || !isActive()) {
404                    Log.w(TAG, "endBatchEdit on inactive InputConnection");
405                    return;
406                }
407                ic.endBatchEdit();
408                return;
409            }
410            case DO_REPORT_FULLSCREEN_MODE: {
411                InputConnection ic = mInputConnection.get();
412                if (ic == null || !isActive()) {
413                    Log.w(TAG, "showStatusIcon on inactive InputConnection");
414                    return;
415                }
416                ic.reportFullscreenMode(msg.arg1 == 1);
417                return;
418            }
419            case DO_PERFORM_PRIVATE_COMMAND: {
420                InputConnection ic = mInputConnection.get();
421                if (ic == null || !isActive()) {
422                    Log.w(TAG, "performPrivateCommand on inactive InputConnection");
423                    return;
424                }
425                SomeArgs args = (SomeArgs)msg.obj;
426                ic.performPrivateCommand((String)args.arg1,
427                        (Bundle)args.arg2);
428                return;
429            }
430            case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: {
431                SomeArgs args = (SomeArgs)msg.obj;
432                try {
433                    InputConnection ic = mInputConnection.get();
434                    if (ic == null || !isActive()) {
435                        Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
436                        args.callback.setRequestUpdateCursorAnchorInfoResult(false, args.seq);
437                        return;
438                    }
439                    args.callback.setRequestUpdateCursorAnchorInfoResult(
440                            ic.requestCursorUpdates(msg.arg1), args.seq);
441                } catch (RemoteException e) {
442                    Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
443                }
444                return;
445            }
446        }
447        Log.w(TAG, "Unhandled message code: " + msg.what);
448    }
449
450    Message obtainMessage(int what) {
451        return mH.obtainMessage(what);
452    }
453
454    Message obtainMessageII(int what, int arg1, int arg2) {
455        return mH.obtainMessage(what, arg1, arg2);
456    }
457
458    Message obtainMessageO(int what, Object arg1) {
459        return mH.obtainMessage(what, 0, 0, arg1);
460    }
461
462    Message obtainMessageISC(int what, int arg1, int seq, IInputContextCallback callback) {
463        SomeArgs args = new SomeArgs();
464        args.callback = callback;
465        args.seq = seq;
466        return mH.obtainMessage(what, arg1, 0, args);
467    }
468
469    Message obtainMessageIISC(int what, int arg1, int arg2, int seq, IInputContextCallback callback) {
470        SomeArgs args = new SomeArgs();
471        args.callback = callback;
472        args.seq = seq;
473        return mH.obtainMessage(what, arg1, arg2, args);
474    }
475
476    Message obtainMessageOSC(int what, Object arg1, int seq, IInputContextCallback callback) {
477        SomeArgs args = new SomeArgs();
478        args.arg1 = arg1;
479        args.callback = callback;
480        args.seq = seq;
481        return mH.obtainMessage(what, 0, 0, args);
482    }
483
484    Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq,
485            IInputContextCallback callback) {
486        SomeArgs args = new SomeArgs();
487        args.arg1 = arg2;
488        args.callback = callback;
489        args.seq = seq;
490        return mH.obtainMessage(what, arg1, 0, args);
491    }
492
493    Message obtainMessageIO(int what, int arg1, Object arg2) {
494        return mH.obtainMessage(what, arg1, 0, arg2);
495    }
496
497    Message obtainMessageOO(int what, Object arg1, Object arg2) {
498        SomeArgs args = new SomeArgs();
499        args.arg1 = arg1;
500        args.arg2 = arg2;
501        return mH.obtainMessage(what, 0, 0, args);
502    }
503}
504