• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.view.inputmethod.Flags.verifyKeyEvent;
20 
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.Context;
23 import android.graphics.Rect;
24 import android.hardware.input.InputManager;
25 import android.os.Bundle;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.SystemClock;
29 import android.util.Log;
30 import android.util.SparseArray;
31 import android.view.InputChannel;
32 import android.view.InputDevice;
33 import android.view.InputEvent;
34 import android.view.InputEventReceiver;
35 import android.view.KeyEvent;
36 import android.view.MotionEvent;
37 import android.view.inputmethod.CompletionInfo;
38 import android.view.inputmethod.CursorAnchorInfo;
39 import android.view.inputmethod.EditorInfo;
40 import android.view.inputmethod.ExtractedText;
41 import android.view.inputmethod.InputMethodSession;
42 
43 import com.android.internal.inputmethod.IInputMethodSession;
44 import com.android.internal.inputmethod.IRemoteInputConnection;
45 import com.android.internal.os.HandlerCaller;
46 import com.android.internal.os.SomeArgs;
47 
48 import java.util.Objects;
49 
50 class IInputMethodSessionWrapper extends IInputMethodSession.Stub
51         implements HandlerCaller.Callback {
52     private static final String TAG = "InputMethodWrapper";
53 
54     private static final int DO_DISPLAY_COMPLETIONS = 65;
55     private static final int DO_UPDATE_EXTRACTED_TEXT = 67;
56     private static final int DO_UPDATE_SELECTION = 90;
57     private static final int DO_UPDATE_CURSOR = 95;
58     private static final int DO_UPDATE_CURSOR_ANCHOR_INFO = 99;
59     private static final int DO_APP_PRIVATE_COMMAND = 100;
60     private static final int DO_FINISH_SESSION = 110;
61     private static final int DO_VIEW_CLICKED = 115;
62     private static final int DO_REMOVE_IME_SURFACE = 130;
63     private static final int DO_FINISH_INPUT = 140;
64     private static final int DO_INVALIDATE_INPUT = 150;
65     private final Context mContext;
66 
67 
68     @UnsupportedAppUsage
69     HandlerCaller mCaller;
70     InputMethodSession mInputMethodSession;
71     InputChannel mChannel;
72     ImeInputEventReceiver mReceiver;
73 
IInputMethodSessionWrapper(Context context, InputMethodSession inputMethodSession, InputChannel channel)74     public IInputMethodSessionWrapper(Context context,
75             InputMethodSession inputMethodSession, InputChannel channel) {
76         mContext = context;
77         mCaller = new HandlerCaller(context, null,
78                 this, true /*asyncHandler*/);
79         mInputMethodSession = inputMethodSession;
80         mChannel = channel;
81         if (channel != null) {
82             mReceiver = new ImeInputEventReceiver(channel, context.getMainLooper());
83         }
84     }
85 
getInternalInputMethodSession()86     public InputMethodSession getInternalInputMethodSession() {
87         return mInputMethodSession;
88     }
89 
90     @Override
executeMessage(Message msg)91     public void executeMessage(Message msg) {
92         if (mInputMethodSession == null) {
93             // The session has been finished. Args needs to be recycled
94             // for cases below.
95             switch (msg.what) {
96                 case DO_UPDATE_SELECTION:
97                 case DO_APP_PRIVATE_COMMAND: {
98                     SomeArgs args = (SomeArgs)msg.obj;
99                     args.recycle();
100                 }
101             }
102             return;
103         }
104 
105         switch (msg.what) {
106             case DO_DISPLAY_COMPLETIONS:
107                 mInputMethodSession.displayCompletions((CompletionInfo[])msg.obj);
108                 return;
109             case DO_UPDATE_EXTRACTED_TEXT:
110                 mInputMethodSession.updateExtractedText(msg.arg1,
111                         (ExtractedText)msg.obj);
112                 return;
113             case DO_UPDATE_SELECTION: {
114                 SomeArgs args = (SomeArgs)msg.obj;
115                 mInputMethodSession.updateSelection(args.argi1, args.argi2,
116                         args.argi3, args.argi4, args.argi5, args.argi6);
117                 args.recycle();
118                 return;
119             }
120             case DO_UPDATE_CURSOR: {
121                 mInputMethodSession.updateCursor((Rect)msg.obj);
122                 return;
123             }
124             case DO_UPDATE_CURSOR_ANCHOR_INFO: {
125                 mInputMethodSession.updateCursorAnchorInfo((CursorAnchorInfo)msg.obj);
126                 return;
127             }
128             case DO_APP_PRIVATE_COMMAND: {
129                 SomeArgs args = (SomeArgs)msg.obj;
130                 mInputMethodSession.appPrivateCommand((String)args.arg1,
131                         (Bundle)args.arg2);
132                 args.recycle();
133                 return;
134             }
135             case DO_FINISH_SESSION: {
136                 doFinishSession();
137                 return;
138             }
139             case DO_VIEW_CLICKED: {
140                 mInputMethodSession.viewClicked(msg.arg1 == 1);
141                 return;
142             }
143             case DO_REMOVE_IME_SURFACE: {
144                 mInputMethodSession.removeImeSurface();
145                 return;
146             }
147             case DO_FINISH_INPUT: {
148                 mInputMethodSession.finishInput();
149                 return;
150             }
151             case DO_INVALIDATE_INPUT: {
152                 final SomeArgs args = (SomeArgs) msg.obj;
153                 try {
154                     mInputMethodSession.invalidateInputInternal((EditorInfo) args.arg1,
155                             (IRemoteInputConnection) args.arg2, msg.arg1);
156                 } finally {
157                     args.recycle();
158                 }
159                 return;
160             }
161         }
162         Log.w(TAG, "Unhandled message code: " + msg.what);
163     }
164 
doFinishSession()165     private void doFinishSession() {
166         mInputMethodSession = null;
167         if (mReceiver != null) {
168             mReceiver.dispose();
169             mReceiver = null;
170         }
171         if (mChannel != null) {
172             mChannel.dispose();
173             mChannel = null;
174         }
175     }
176 
177     @Override
displayCompletions(CompletionInfo[] completions)178     public void displayCompletions(CompletionInfo[] completions) {
179         mCaller.executeOrSendMessage(mCaller.obtainMessageO(
180                 DO_DISPLAY_COMPLETIONS, completions));
181     }
182 
183     @Override
updateExtractedText(int token, ExtractedText text)184     public void updateExtractedText(int token, ExtractedText text) {
185         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
186                 DO_UPDATE_EXTRACTED_TEXT, token, text));
187     }
188 
189     @Override
updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)190     public void updateSelection(int oldSelStart, int oldSelEnd,
191             int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
192         mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION,
193                 oldSelStart, oldSelEnd, newSelStart, newSelEnd,
194                 candidatesStart, candidatesEnd));
195     }
196 
197     @Override
viewClicked(boolean focusChanged)198     public void viewClicked(boolean focusChanged) {
199         mCaller.executeOrSendMessage(
200                 mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
201     }
202 
203     @Override
removeImeSurface()204     public void removeImeSurface() {
205         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_IME_SURFACE));
206     }
207 
208     @Override
updateCursor(Rect newCursor)209     public void updateCursor(Rect newCursor) {
210         mCaller.executeOrSendMessage(
211                 mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
212     }
213 
214     @Override
updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo)215     public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
216         mCaller.executeOrSendMessage(
217                 mCaller.obtainMessageO(DO_UPDATE_CURSOR_ANCHOR_INFO, cursorAnchorInfo));
218     }
219 
220     @Override
appPrivateCommand(String action, Bundle data)221     public void appPrivateCommand(String action, Bundle data) {
222         mCaller.executeOrSendMessage(
223                 mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
224     }
225 
226     @Override
finishSession()227     public void finishSession() {
228         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION));
229     }
230 
231     @Override
invalidateInput(EditorInfo editorInfo, IRemoteInputConnection inputConnection, int sessionId)232     public void invalidateInput(EditorInfo editorInfo, IRemoteInputConnection inputConnection,
233             int sessionId) {
234         mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(
235                 DO_INVALIDATE_INPUT, sessionId, editorInfo, inputConnection));
236     }
237 
238     @Override
finishInput()239     public void finishInput() {
240         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT));
241     }
242     private final class ImeInputEventReceiver extends InputEventReceiver
243             implements InputMethodSession.EventCallback {
244         // Time after which a KeyEvent is invalid
245         private static final long KEY_EVENT_ALLOW_PERIOD_MS = 100L;
246         private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>();
247 
ImeInputEventReceiver(InputChannel inputChannel, Looper looper)248         public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
249             super(inputChannel, looper);
250         }
251 
252         @Override
onInputEvent(InputEvent event)253         public void onInputEvent(InputEvent event) {
254             if (mInputMethodSession == null) {
255                 // The session has been finished.
256                 finishInputEvent(event, false);
257                 return;
258             }
259 
260             if (event instanceof KeyEvent keyEvent && needsVerification(keyEvent)) {
261                 // any KeyEvent with modifiers (e.g. Ctrl/Alt/Fn) must be verified that
262                 // they originated from system.
263                 InputManager im = mContext.getSystemService(InputManager.class);
264                 Objects.requireNonNull(im);
265                 final long age = SystemClock.uptimeMillis() - keyEvent.getEventTime();
266                 if (age >= KEY_EVENT_ALLOW_PERIOD_MS && im.verifyInputEvent(keyEvent) == null) {
267                     Log.w(TAG, "Unverified or Invalid KeyEvent injected into IME. Dropping "
268                             + keyEvent);
269                     finishInputEvent(event, false /* handled */);
270                     return;
271                 }
272             }
273 
274             final int seq = event.getSequenceNumber();
275             mPendingEvents.put(seq, event);
276             if (event instanceof KeyEvent keyEvent) {
277                 mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this);
278             } else {
279                 MotionEvent motionEvent = (MotionEvent)event;
280                 if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
281                     mInputMethodSession.dispatchTrackballEvent(seq, motionEvent, this);
282                 } else {
283                     mInputMethodSession.dispatchGenericMotionEvent(seq, motionEvent, this);
284                 }
285             }
286         }
287 
288         @Override
finishedEvent(int seq, boolean handled)289         public void finishedEvent(int seq, boolean handled) {
290             int index = mPendingEvents.indexOfKey(seq);
291             if (index >= 0) {
292                 InputEvent event = mPendingEvents.valueAt(index);
293                 mPendingEvents.removeAt(index);
294                 finishInputEvent(event, handled);
295             }
296         }
297 
hasKeyModifiers(KeyEvent event)298         private boolean hasKeyModifiers(KeyEvent event) {
299             if (event.hasNoModifiers()) {
300                 return false;
301             }
302             return event.isCtrlPressed()
303                     || event.isAltPressed()
304                     || event.isFunctionPressed()
305                     || event.isMetaPressed();
306         }
307 
needsVerification(KeyEvent event)308         private boolean needsVerification(KeyEvent event) {
309             //TODO(b/331730488): Handle a11y events as well.
310             return verifyKeyEvent()
311                     && (hasKeyModifiers(event)
312                             || mInputMethodSession.onShouldVerifyKeyEvent(event));
313         }
314     }
315 }
316