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 com.android.internal.annotations.GuardedBy;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
27import android.os.RemoteException;
28import android.util.Log;
29import android.view.KeyEvent;
30import android.view.inputmethod.CompletionInfo;
31import android.view.inputmethod.CorrectionInfo;
32import android.view.inputmethod.ExtractedTextRequest;
33import android.view.inputmethod.InputConnection;
34import android.view.inputmethod.InputConnectionInspector;
35import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
36
37public abstract class IInputConnectionWrapper extends IInputContext.Stub {
38    static final String TAG = "IInputConnectionWrapper";
39
40    private static final int DO_GET_TEXT_AFTER_CURSOR = 10;
41    private static final int DO_GET_TEXT_BEFORE_CURSOR = 20;
42    private static final int DO_GET_SELECTED_TEXT = 25;
43    private static final int DO_GET_CURSOR_CAPS_MODE = 30;
44    private static final int DO_GET_EXTRACTED_TEXT = 40;
45    private static final int DO_COMMIT_TEXT = 50;
46    private static final int DO_COMMIT_COMPLETION = 55;
47    private static final int DO_COMMIT_CORRECTION = 56;
48    private static final int DO_SET_SELECTION = 57;
49    private static final int DO_PERFORM_EDITOR_ACTION = 58;
50    private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59;
51    private static final int DO_SET_COMPOSING_TEXT = 60;
52    private static final int DO_SET_COMPOSING_REGION = 63;
53    private static final int DO_FINISH_COMPOSING_TEXT = 65;
54    private static final int DO_SEND_KEY_EVENT = 70;
55    private static final int DO_DELETE_SURROUNDING_TEXT = 80;
56    private static final int DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 81;
57    private static final int DO_BEGIN_BATCH_EDIT = 90;
58    private static final int DO_END_BATCH_EDIT = 95;
59    private static final int DO_REPORT_FULLSCREEN_MODE = 100;
60    private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
61    private static final int DO_CLEAR_META_KEY_STATES = 130;
62    private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
63    private static final int DO_CLOSE_CONNECTION = 150;
64
65    @GuardedBy("mLock")
66    @Nullable
67    private InputConnection mInputConnection;
68
69    private Looper mMainLooper;
70    private Handler mH;
71    private Object mLock = new Object();
72    @GuardedBy("mLock")
73    private boolean mFinished = false;
74    @GuardedBy("mLock")
75    private String mInputMethodId;
76
77    static class SomeArgs {
78        Object arg1;
79        Object arg2;
80        IInputContextCallback callback;
81        int seq;
82    }
83
84    class MyHandler extends Handler {
85        MyHandler(Looper looper) {
86            super(looper);
87        }
88
89        @Override
90        public void handleMessage(Message msg) {
91            executeMessage(msg);
92        }
93    }
94
95    public IInputConnectionWrapper(Looper mainLooper, @NonNull InputConnection inputConnection) {
96        mInputConnection = inputConnection;
97        mMainLooper = mainLooper;
98        mH = new MyHandler(mMainLooper);
99    }
100
101    @Nullable
102    public InputConnection getInputConnection() {
103        synchronized (mLock) {
104            return mInputConnection;
105        }
106    }
107
108    protected boolean isFinished() {
109        synchronized (mLock) {
110            return mFinished;
111        }
112    }
113
114    public String getInputMethodId() {
115        synchronized (mLock) {
116            return mInputMethodId;
117        }
118    }
119
120    public void setInputMethodId(final String inputMethodId) {
121        synchronized (mLock) {
122            mInputMethodId = inputMethodId;
123        }
124    }
125
126    abstract protected boolean isActive();
127
128    /**
129     * Called when the user took some actions that should be taken into consideration to update the
130     * LRU list for input method rotation.
131     */
132    abstract protected void onUserAction();
133
134    /**
135     * Called when the input method started or stopped full-screen mode.
136     * @param enabled {@code true} if the input method starts full-screen mode.
137     * @param calledInBackground {@code true} if this input connection is in a state when incoming
138     * events are usually ignored.
139     */
140    abstract protected void onReportFullscreenMode(boolean enabled, boolean calledInBackground);
141
142    public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
143        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
144    }
145
146    public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
147        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
148    }
149
150    public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
151        dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
152    }
153
154    public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
155        dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
156    }
157
158    public void getExtractedText(ExtractedTextRequest request,
159            int flags, int seq, IInputContextCallback callback) {
160        dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
161                request, seq, callback));
162    }
163
164    public void commitText(CharSequence text, int newCursorPosition) {
165        dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
166    }
167
168    public void commitCompletion(CompletionInfo text) {
169        dispatchMessage(obtainMessageO(DO_COMMIT_COMPLETION, text));
170    }
171
172    public void commitCorrection(CorrectionInfo info) {
173        dispatchMessage(obtainMessageO(DO_COMMIT_CORRECTION, info));
174    }
175
176    public void setSelection(int start, int end) {
177        dispatchMessage(obtainMessageII(DO_SET_SELECTION, start, end));
178    }
179
180    public void performEditorAction(int id) {
181        dispatchMessage(obtainMessageII(DO_PERFORM_EDITOR_ACTION, id, 0));
182    }
183
184    public void performContextMenuAction(int id) {
185        dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0));
186    }
187
188    public void setComposingRegion(int start, int end) {
189        dispatchMessage(obtainMessageII(DO_SET_COMPOSING_REGION, start, end));
190    }
191
192    public void setComposingText(CharSequence text, int newCursorPosition) {
193        dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text));
194    }
195
196    public void finishComposingText() {
197        dispatchMessage(obtainMessage(DO_FINISH_COMPOSING_TEXT));
198    }
199
200    public void sendKeyEvent(KeyEvent event) {
201        dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event));
202    }
203
204    public void clearMetaKeyStates(int states) {
205        dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0));
206    }
207
208    public void deleteSurroundingText(int beforeLength, int afterLength) {
209        dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
210                beforeLength, afterLength));
211    }
212
213    public void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
214        dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
215                beforeLength, afterLength));
216    }
217
218    public void beginBatchEdit() {
219        dispatchMessage(obtainMessage(DO_BEGIN_BATCH_EDIT));
220    }
221
222    public void endBatchEdit() {
223        dispatchMessage(obtainMessage(DO_END_BATCH_EDIT));
224    }
225
226    public void reportFullscreenMode(boolean enabled) {
227        dispatchMessage(obtainMessageII(DO_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0));
228    }
229
230    public void performPrivateCommand(String action, Bundle data) {
231        dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
232    }
233
234    public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
235            IInputContextCallback callback) {
236        dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
237                seq, callback));
238    }
239
240    public void closeConnection() {
241        dispatchMessage(obtainMessage(DO_CLOSE_CONNECTION));
242    }
243
244    void dispatchMessage(Message msg) {
245        // If we are calling this from the main thread, then we can call
246        // right through.  Otherwise, we need to send the message to the
247        // main thread.
248        if (Looper.myLooper() == mMainLooper) {
249            executeMessage(msg);
250            msg.recycle();
251            return;
252        }
253
254        mH.sendMessage(msg);
255    }
256
257    void executeMessage(Message msg) {
258        switch (msg.what) {
259            case DO_GET_TEXT_AFTER_CURSOR: {
260                SomeArgs args = (SomeArgs)msg.obj;
261                try {
262                    InputConnection ic = getInputConnection();
263                    if (ic == null || !isActive()) {
264                        Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
265                        args.callback.setTextAfterCursor(null, args.seq);
266                        return;
267                    }
268                    args.callback.setTextAfterCursor(ic.getTextAfterCursor(
269                            msg.arg1, msg.arg2), args.seq);
270                } catch (RemoteException e) {
271                    Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
272                }
273                return;
274            }
275            case DO_GET_TEXT_BEFORE_CURSOR: {
276                SomeArgs args = (SomeArgs)msg.obj;
277                try {
278                    InputConnection ic = getInputConnection();
279                    if (ic == null || !isActive()) {
280                        Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
281                        args.callback.setTextBeforeCursor(null, args.seq);
282                        return;
283                    }
284                    args.callback.setTextBeforeCursor(ic.getTextBeforeCursor(
285                            msg.arg1, msg.arg2), args.seq);
286                } catch (RemoteException e) {
287                    Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
288                }
289                return;
290            }
291            case DO_GET_SELECTED_TEXT: {
292                SomeArgs args = (SomeArgs)msg.obj;
293                try {
294                    InputConnection ic = getInputConnection();
295                    if (ic == null || !isActive()) {
296                        Log.w(TAG, "getSelectedText on inactive InputConnection");
297                        args.callback.setSelectedText(null, args.seq);
298                        return;
299                    }
300                    args.callback.setSelectedText(ic.getSelectedText(
301                            msg.arg1), args.seq);
302                } catch (RemoteException e) {
303                    Log.w(TAG, "Got RemoteException calling setSelectedText", e);
304                }
305                return;
306            }
307            case DO_GET_CURSOR_CAPS_MODE: {
308                SomeArgs args = (SomeArgs)msg.obj;
309                try {
310                    InputConnection ic = getInputConnection();
311                    if (ic == null || !isActive()) {
312                        Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
313                        args.callback.setCursorCapsMode(0, args.seq);
314                        return;
315                    }
316                    args.callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
317                            args.seq);
318                } catch (RemoteException e) {
319                    Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
320                }
321                return;
322            }
323            case DO_GET_EXTRACTED_TEXT: {
324                SomeArgs args = (SomeArgs)msg.obj;
325                try {
326                    InputConnection ic = getInputConnection();
327                    if (ic == null || !isActive()) {
328                        Log.w(TAG, "getExtractedText on inactive InputConnection");
329                        args.callback.setExtractedText(null, args.seq);
330                        return;
331                    }
332                    args.callback.setExtractedText(ic.getExtractedText(
333                            (ExtractedTextRequest)args.arg1, msg.arg1), args.seq);
334                } catch (RemoteException e) {
335                    Log.w(TAG, "Got RemoteException calling setExtractedText", e);
336                }
337                return;
338            }
339            case DO_COMMIT_TEXT: {
340                InputConnection ic = getInputConnection();
341                if (ic == null || !isActive()) {
342                    Log.w(TAG, "commitText on inactive InputConnection");
343                    return;
344                }
345                ic.commitText((CharSequence)msg.obj, msg.arg1);
346                onUserAction();
347                return;
348            }
349            case DO_SET_SELECTION: {
350                InputConnection ic = getInputConnection();
351                if (ic == null || !isActive()) {
352                    Log.w(TAG, "setSelection on inactive InputConnection");
353                    return;
354                }
355                ic.setSelection(msg.arg1, msg.arg2);
356                return;
357            }
358            case DO_PERFORM_EDITOR_ACTION: {
359                InputConnection ic = getInputConnection();
360                if (ic == null || !isActive()) {
361                    Log.w(TAG, "performEditorAction on inactive InputConnection");
362                    return;
363                }
364                ic.performEditorAction(msg.arg1);
365                return;
366            }
367            case DO_PERFORM_CONTEXT_MENU_ACTION: {
368                InputConnection ic = getInputConnection();
369                if (ic == null || !isActive()) {
370                    Log.w(TAG, "performContextMenuAction on inactive InputConnection");
371                    return;
372                }
373                ic.performContextMenuAction(msg.arg1);
374                return;
375            }
376            case DO_COMMIT_COMPLETION: {
377                InputConnection ic = getInputConnection();
378                if (ic == null || !isActive()) {
379                    Log.w(TAG, "commitCompletion on inactive InputConnection");
380                    return;
381                }
382                ic.commitCompletion((CompletionInfo)msg.obj);
383                return;
384            }
385            case DO_COMMIT_CORRECTION: {
386                InputConnection ic = getInputConnection();
387                if (ic == null || !isActive()) {
388                    Log.w(TAG, "commitCorrection on inactive InputConnection");
389                    return;
390                }
391                ic.commitCorrection((CorrectionInfo)msg.obj);
392                return;
393            }
394            case DO_SET_COMPOSING_TEXT: {
395                InputConnection ic = getInputConnection();
396                if (ic == null || !isActive()) {
397                    Log.w(TAG, "setComposingText on inactive InputConnection");
398                    return;
399                }
400                ic.setComposingText((CharSequence)msg.obj, msg.arg1);
401                onUserAction();
402                return;
403            }
404            case DO_SET_COMPOSING_REGION: {
405                InputConnection ic = getInputConnection();
406                if (ic == null || !isActive()) {
407                    Log.w(TAG, "setComposingRegion on inactive InputConnection");
408                    return;
409                }
410                ic.setComposingRegion(msg.arg1, msg.arg2);
411                return;
412            }
413            case DO_FINISH_COMPOSING_TEXT: {
414                InputConnection ic = getInputConnection();
415                // Note we do NOT check isActive() here, because this is safe
416                // for an IME to call at any time, and we need to allow it
417                // through to clean up our state after the IME has switched to
418                // another client.
419                if (ic == null) {
420                    Log.w(TAG, "finishComposingText on inactive InputConnection");
421                    return;
422                }
423                ic.finishComposingText();
424                return;
425            }
426            case DO_SEND_KEY_EVENT: {
427                InputConnection ic = getInputConnection();
428                if (ic == null || !isActive()) {
429                    Log.w(TAG, "sendKeyEvent on inactive InputConnection");
430                    return;
431                }
432                ic.sendKeyEvent((KeyEvent)msg.obj);
433                onUserAction();
434                return;
435            }
436            case DO_CLEAR_META_KEY_STATES: {
437                InputConnection ic = getInputConnection();
438                if (ic == null || !isActive()) {
439                    Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
440                    return;
441                }
442                ic.clearMetaKeyStates(msg.arg1);
443                return;
444            }
445            case DO_DELETE_SURROUNDING_TEXT: {
446                InputConnection ic = getInputConnection();
447                if (ic == null || !isActive()) {
448                    Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
449                    return;
450                }
451                ic.deleteSurroundingText(msg.arg1, msg.arg2);
452                return;
453            }
454            case DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: {
455                InputConnection ic = getInputConnection();
456                if (ic == null || !isActive()) {
457                    Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection");
458                    return;
459                }
460                ic.deleteSurroundingTextInCodePoints(msg.arg1, msg.arg2);
461                return;
462            }
463            case DO_BEGIN_BATCH_EDIT: {
464                InputConnection ic = getInputConnection();
465                if (ic == null || !isActive()) {
466                    Log.w(TAG, "beginBatchEdit on inactive InputConnection");
467                    return;
468                }
469                ic.beginBatchEdit();
470                return;
471            }
472            case DO_END_BATCH_EDIT: {
473                InputConnection ic = getInputConnection();
474                if (ic == null || !isActive()) {
475                    Log.w(TAG, "endBatchEdit on inactive InputConnection");
476                    return;
477                }
478                ic.endBatchEdit();
479                return;
480            }
481            case DO_REPORT_FULLSCREEN_MODE: {
482                InputConnection ic = getInputConnection();
483                boolean isBackground = false;
484                if (ic == null || !isActive()) {
485                    Log.w(TAG, "reportFullscreenMode on inexistent InputConnection");
486                    isBackground = true;
487                }
488                final boolean enabled = msg.arg1 == 1;
489                if (!isBackground) {
490                    ic.reportFullscreenMode(enabled);
491                }
492                // Due to the nature of asynchronous event handling, currently InputMethodService
493                // has relied on the fact that #reportFullscreenMode() can be handled even when the
494                // InputConnection is inactive.  We have to notify this event to InputMethodManager.
495                onReportFullscreenMode(enabled, isBackground);
496                return;
497            }
498            case DO_PERFORM_PRIVATE_COMMAND: {
499                InputConnection ic = getInputConnection();
500                if (ic == null || !isActive()) {
501                    Log.w(TAG, "performPrivateCommand on inactive InputConnection");
502                    return;
503                }
504                SomeArgs args = (SomeArgs)msg.obj;
505                ic.performPrivateCommand((String)args.arg1,
506                        (Bundle)args.arg2);
507                return;
508            }
509            case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: {
510                SomeArgs args = (SomeArgs)msg.obj;
511                try {
512                    InputConnection ic = getInputConnection();
513                    if (ic == null || !isActive()) {
514                        Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
515                        args.callback.setRequestUpdateCursorAnchorInfoResult(false, args.seq);
516                        return;
517                    }
518                    args.callback.setRequestUpdateCursorAnchorInfoResult(
519                            ic.requestCursorUpdates(msg.arg1), args.seq);
520                } catch (RemoteException e) {
521                    Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
522                }
523                return;
524            }
525            case DO_CLOSE_CONNECTION: {
526                // Note that we do not need to worry about race condition here, because 1) mFinished
527                // is updated only inside this block, and 2) the code here is running on a Handler
528                // hence we assume multiple DO_CLOSE_CONNECTION messages will not be handled at the
529                // same time.
530                if (isFinished()) {
531                    return;
532                }
533                try {
534                    InputConnection ic = getInputConnection();
535                    // Note we do NOT check isActive() here, because this is safe
536                    // for an IME to call at any time, and we need to allow it
537                    // through to clean up our state after the IME has switched to
538                    // another client.
539                    if (ic == null) {
540                        return;
541                    }
542                    @MissingMethodFlags
543                    final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic);
544                    if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) {
545                        ic.closeConnection();
546                    }
547                } finally {
548                    synchronized (mLock) {
549                        mInputConnection = null;
550                        mFinished = true;
551                    }
552                }
553                return;
554            }
555        }
556        Log.w(TAG, "Unhandled message code: " + msg.what);
557    }
558
559    Message obtainMessage(int what) {
560        return mH.obtainMessage(what);
561    }
562
563    Message obtainMessageII(int what, int arg1, int arg2) {
564        return mH.obtainMessage(what, arg1, arg2);
565    }
566
567    Message obtainMessageO(int what, Object arg1) {
568        return mH.obtainMessage(what, 0, 0, arg1);
569    }
570
571    Message obtainMessageISC(int what, int arg1, int seq, IInputContextCallback callback) {
572        SomeArgs args = new SomeArgs();
573        args.callback = callback;
574        args.seq = seq;
575        return mH.obtainMessage(what, arg1, 0, args);
576    }
577
578    Message obtainMessageIISC(int what, int arg1, int arg2, int seq, IInputContextCallback callback) {
579        SomeArgs args = new SomeArgs();
580        args.callback = callback;
581        args.seq = seq;
582        return mH.obtainMessage(what, arg1, arg2, args);
583    }
584
585    Message obtainMessageOSC(int what, Object arg1, int seq, IInputContextCallback callback) {
586        SomeArgs args = new SomeArgs();
587        args.arg1 = arg1;
588        args.callback = callback;
589        args.seq = seq;
590        return mH.obtainMessage(what, 0, 0, args);
591    }
592
593    Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq,
594            IInputContextCallback callback) {
595        SomeArgs args = new SomeArgs();
596        args.arg1 = arg2;
597        args.callback = callback;
598        args.seq = seq;
599        return mH.obtainMessage(what, arg1, 0, args);
600    }
601
602    Message obtainMessageIO(int what, int arg1, Object arg2) {
603        return mH.obtainMessage(what, arg1, 0, arg2);
604    }
605
606    Message obtainMessageOO(int what, Object arg1, Object arg2) {
607        SomeArgs args = new SomeArgs();
608        args.arg1 = arg1;
609        args.arg2 = arg2;
610        return mH.obtainMessage(what, 0, 0, args);
611    }
612}
613