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 com.android.internal.os.HandlerCaller;
20 import com.android.internal.os.SomeArgs;
21 import com.android.internal.view.IInputMethodSession;
22 
23 import android.content.Context;
24 import android.graphics.Rect;
25 import android.os.Bundle;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.util.Log;
29 import android.util.SparseArray;
30 import android.view.InputChannel;
31 import android.view.InputDevice;
32 import android.view.InputEvent;
33 import android.view.InputEventReceiver;
34 import android.view.KeyEvent;
35 import android.view.MotionEvent;
36 import android.view.inputmethod.CompletionInfo;
37 import android.view.inputmethod.ExtractedText;
38 import android.view.inputmethod.InputMethodSession;
39 import android.view.inputmethod.CursorAnchorInfo;
40 
41 class 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 
IInputMethodSessionWrapper(Context context, InputMethodSession inputMethodSession, InputChannel channel)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 
getInternalInputMethodSession()72     public InputMethodSession getInternalInputMethodSession() {
73         return mInputMethodSession;
74     }
75 
76     @Override
executeMessage(Message msg)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 
doFinishSession()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
finishInput()153     public void finishInput() {
154         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT));
155     }
156 
157     @Override
displayCompletions(CompletionInfo[] completions)158     public void displayCompletions(CompletionInfo[] completions) {
159         mCaller.executeOrSendMessage(mCaller.obtainMessageO(
160                 DO_DISPLAY_COMPLETIONS, completions));
161     }
162 
163     @Override
updateExtractedText(int token, ExtractedText text)164     public void updateExtractedText(int token, ExtractedText text) {
165         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
166                 DO_UPDATE_EXTRACTED_TEXT, token, text));
167     }
168 
169     @Override
updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)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
viewClicked(boolean focusChanged)178     public void viewClicked(boolean focusChanged) {
179         mCaller.executeOrSendMessage(
180                 mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
181     }
182 
183     @Override
updateCursor(Rect newCursor)184     public void updateCursor(Rect newCursor) {
185         mCaller.executeOrSendMessage(
186                 mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
187     }
188 
189     @Override
updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo)190     public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
191         mCaller.executeOrSendMessage(
192                 mCaller.obtainMessageO(DO_UPDATE_CURSOR_ANCHOR_INFO, cursorAnchorInfo));
193     }
194 
195     @Override
appPrivateCommand(String action, Bundle data)196     public void appPrivateCommand(String action, Bundle data) {
197         mCaller.executeOrSendMessage(
198                 mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
199     }
200 
201     @Override
toggleSoftInput(int showFlags, int hideFlags)202     public void toggleSoftInput(int showFlags, int hideFlags) {
203         mCaller.executeOrSendMessage(
204                 mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
205     }
206 
207     @Override
finishSession()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 
ImeInputEventReceiver(InputChannel inputChannel, Looper looper)216         public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
217             super(inputChannel, looper);
218         }
219 
220         @Override
onInputEvent(InputEvent event)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
finishedEvent(int seq, boolean handled)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