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 
17 package android.inputmethodservice;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.Context;
21 import android.graphics.Rect;
22 import android.os.Bundle;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.util.Log;
26 import android.util.SparseArray;
27 import android.view.InputChannel;
28 import android.view.InputDevice;
29 import android.view.InputEvent;
30 import android.view.InputEventReceiver;
31 import android.view.KeyEvent;
32 import android.view.MotionEvent;
33 import android.view.inputmethod.CompletionInfo;
34 import android.view.inputmethod.CursorAnchorInfo;
35 import android.view.inputmethod.ExtractedText;
36 import android.view.inputmethod.InputMethodSession;
37 
38 import com.android.internal.os.HandlerCaller;
39 import com.android.internal.os.SomeArgs;
40 import com.android.internal.view.IInputMethodSession;
41 
42 class IInputMethodSessionWrapper extends IInputMethodSession.Stub
43         implements HandlerCaller.Callback {
44     private static final String TAG = "InputMethodWrapper";
45 
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     private static final int DO_NOTIFY_IME_HIDDEN = 120;
56 
57     @UnsupportedAppUsage
58     HandlerCaller mCaller;
59     InputMethodSession mInputMethodSession;
60     InputChannel mChannel;
61     ImeInputEventReceiver mReceiver;
62 
IInputMethodSessionWrapper(Context context, InputMethodSession inputMethodSession, InputChannel channel)63     public IInputMethodSessionWrapper(Context context,
64             InputMethodSession inputMethodSession, InputChannel channel) {
65         mCaller = new HandlerCaller(context, null,
66                 this, true /*asyncHandler*/);
67         mInputMethodSession = inputMethodSession;
68         mChannel = channel;
69         if (channel != null) {
70             mReceiver = new ImeInputEventReceiver(channel, context.getMainLooper());
71         }
72     }
73 
getInternalInputMethodSession()74     public InputMethodSession getInternalInputMethodSession() {
75         return mInputMethodSession;
76     }
77 
78     @Override
executeMessage(Message msg)79     public void executeMessage(Message msg) {
80         if (mInputMethodSession == null) {
81             // The session has been finished. Args needs to be recycled
82             // for cases below.
83             switch (msg.what) {
84                 case DO_UPDATE_SELECTION:
85                 case DO_APP_PRIVATE_COMMAND: {
86                     SomeArgs args = (SomeArgs)msg.obj;
87                     args.recycle();
88                 }
89             }
90             return;
91         }
92 
93         switch (msg.what) {
94             case DO_DISPLAY_COMPLETIONS:
95                 mInputMethodSession.displayCompletions((CompletionInfo[])msg.obj);
96                 return;
97             case DO_UPDATE_EXTRACTED_TEXT:
98                 mInputMethodSession.updateExtractedText(msg.arg1,
99                         (ExtractedText)msg.obj);
100                 return;
101             case DO_UPDATE_SELECTION: {
102                 SomeArgs args = (SomeArgs)msg.obj;
103                 mInputMethodSession.updateSelection(args.argi1, args.argi2,
104                         args.argi3, args.argi4, args.argi5, args.argi6);
105                 args.recycle();
106                 return;
107             }
108             case DO_UPDATE_CURSOR: {
109                 mInputMethodSession.updateCursor((Rect)msg.obj);
110                 return;
111             }
112             case DO_UPDATE_CURSOR_ANCHOR_INFO: {
113                 mInputMethodSession.updateCursorAnchorInfo((CursorAnchorInfo)msg.obj);
114                 return;
115             }
116             case DO_APP_PRIVATE_COMMAND: {
117                 SomeArgs args = (SomeArgs)msg.obj;
118                 mInputMethodSession.appPrivateCommand((String)args.arg1,
119                         (Bundle)args.arg2);
120                 args.recycle();
121                 return;
122             }
123             case DO_TOGGLE_SOFT_INPUT: {
124                 mInputMethodSession.toggleSoftInput(msg.arg1, msg.arg2);
125                 return;
126             }
127             case DO_FINISH_SESSION: {
128                 doFinishSession();
129                 return;
130             }
131             case DO_VIEW_CLICKED: {
132                 mInputMethodSession.viewClicked(msg.arg1 == 1);
133                 return;
134             }
135             case DO_NOTIFY_IME_HIDDEN: {
136                 mInputMethodSession.notifyImeHidden();
137                 return;
138             }
139         }
140         Log.w(TAG, "Unhandled message code: " + msg.what);
141     }
142 
doFinishSession()143     private void doFinishSession() {
144         mInputMethodSession = null;
145         if (mReceiver != null) {
146             mReceiver.dispose();
147             mReceiver = null;
148         }
149         if (mChannel != null) {
150             mChannel.dispose();
151             mChannel = null;
152         }
153     }
154 
155     @Override
displayCompletions(CompletionInfo[] completions)156     public void displayCompletions(CompletionInfo[] completions) {
157         mCaller.executeOrSendMessage(mCaller.obtainMessageO(
158                 DO_DISPLAY_COMPLETIONS, completions));
159     }
160 
161     @Override
updateExtractedText(int token, ExtractedText text)162     public void updateExtractedText(int token, ExtractedText text) {
163         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
164                 DO_UPDATE_EXTRACTED_TEXT, token, text));
165     }
166 
167     @Override
updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)168     public void updateSelection(int oldSelStart, int oldSelEnd,
169             int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
170         mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION,
171                 oldSelStart, oldSelEnd, newSelStart, newSelEnd,
172                 candidatesStart, candidatesEnd));
173     }
174 
175     @Override
viewClicked(boolean focusChanged)176     public void viewClicked(boolean focusChanged) {
177         mCaller.executeOrSendMessage(
178                 mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
179     }
180 
181     @Override
notifyImeHidden()182     public void notifyImeHidden() {
183         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_IME_HIDDEN));
184     }
185 
186     @Override
updateCursor(Rect newCursor)187     public void updateCursor(Rect newCursor) {
188         mCaller.executeOrSendMessage(
189                 mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
190     }
191 
192     @Override
updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo)193     public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
194         mCaller.executeOrSendMessage(
195                 mCaller.obtainMessageO(DO_UPDATE_CURSOR_ANCHOR_INFO, cursorAnchorInfo));
196     }
197 
198     @Override
appPrivateCommand(String action, Bundle data)199     public void appPrivateCommand(String action, Bundle data) {
200         mCaller.executeOrSendMessage(
201                 mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
202     }
203 
204     @Override
toggleSoftInput(int showFlags, int hideFlags)205     public void toggleSoftInput(int showFlags, int hideFlags) {
206         mCaller.executeOrSendMessage(
207                 mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
208     }
209 
210     @Override
finishSession()211     public void finishSession() {
212         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION));
213     }
214 
215     private final class ImeInputEventReceiver extends InputEventReceiver
216             implements InputMethodSession.EventCallback {
217         private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>();
218 
ImeInputEventReceiver(InputChannel inputChannel, Looper looper)219         public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
220             super(inputChannel, looper);
221         }
222 
223         @Override
onInputEvent(InputEvent event)224         public void onInputEvent(InputEvent event) {
225             if (mInputMethodSession == null) {
226                 // The session has been finished.
227                 finishInputEvent(event, false);
228                 return;
229             }
230 
231             final int seq = event.getSequenceNumber();
232             mPendingEvents.put(seq, event);
233             if (event instanceof KeyEvent) {
234                 KeyEvent keyEvent = (KeyEvent)event;
235                 mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this);
236             } else {
237                 MotionEvent motionEvent = (MotionEvent)event;
238                 if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
239                     mInputMethodSession.dispatchTrackballEvent(seq, motionEvent, this);
240                 } else {
241                     mInputMethodSession.dispatchGenericMotionEvent(seq, motionEvent, this);
242                 }
243             }
244         }
245 
246         @Override
finishedEvent(int seq, boolean handled)247         public void finishedEvent(int seq, boolean handled) {
248             int index = mPendingEvents.indexOfKey(seq);
249             if (index >= 0) {
250                 InputEvent event = mPendingEvents.valueAt(index);
251                 mPendingEvents.removeAt(index);
252                 finishInputEvent(event, handled);
253             }
254         }
255     }
256 }
257