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.annotation.BinderThread;
20 import android.annotation.MainThread;
21 import android.annotation.Nullable;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.os.Binder;
26 import android.os.IBinder;
27 import android.os.Message;
28 import android.os.RemoteException;
29 import android.os.ResultReceiver;
30 import android.util.Log;
31 import android.view.InputChannel;
32 import android.view.inputmethod.EditorInfo;
33 import android.view.inputmethod.InputBinding;
34 import android.view.inputmethod.InputConnection;
35 import android.view.inputmethod.InputConnectionInspector;
36 import android.view.inputmethod.InputMethod;
37 import android.view.inputmethod.InputMethodSession;
38 import android.view.inputmethod.InputMethodSubtype;
39 
40 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
41 import com.android.internal.inputmethod.CancellationGroup;
42 import com.android.internal.os.HandlerCaller;
43 import com.android.internal.os.SomeArgs;
44 import com.android.internal.view.IInlineSuggestionsRequestCallback;
45 import com.android.internal.view.IInputContext;
46 import com.android.internal.view.IInputMethod;
47 import com.android.internal.view.IInputMethodSession;
48 import com.android.internal.view.IInputSessionCallback;
49 import com.android.internal.view.InlineSuggestionsRequestInfo;
50 import com.android.internal.view.InputConnectionWrapper;
51 
52 import java.io.FileDescriptor;
53 import java.io.PrintWriter;
54 import java.lang.ref.WeakReference;
55 import java.util.concurrent.CountDownLatch;
56 import java.util.concurrent.TimeUnit;
57 
58 /**
59  * Implements the internal IInputMethod interface to convert incoming calls
60  * on to it back to calls on the public InputMethod interface, scheduling
61  * them on the main thread of the process.
62  */
63 class IInputMethodWrapper extends IInputMethod.Stub
64         implements HandlerCaller.Callback {
65     private static final String TAG = "InputMethodWrapper";
66 
67     private static final int DO_DUMP = 1;
68     private static final int DO_INITIALIZE_INTERNAL = 10;
69     private static final int DO_SET_INPUT_CONTEXT = 20;
70     private static final int DO_UNSET_INPUT_CONTEXT = 30;
71     private static final int DO_START_INPUT = 32;
72     private static final int DO_CREATE_SESSION = 40;
73     private static final int DO_SET_SESSION_ENABLED = 45;
74     private static final int DO_REVOKE_SESSION = 50;
75     private static final int DO_SHOW_SOFT_INPUT = 60;
76     private static final int DO_HIDE_SOFT_INPUT = 70;
77     private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
78     private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;
79 
80     final WeakReference<AbstractInputMethodService> mTarget;
81     final Context mContext;
82     @UnsupportedAppUsage
83     final HandlerCaller mCaller;
84     final WeakReference<InputMethod> mInputMethod;
85     final int mTargetSdkVersion;
86 
87     /**
88      * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
89      * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been
90      * called or not, mainly to avoid unnecessary blocking operations.
91      *
92      * <p>This field must be set and cleared only from the binder thread(s), where the system
93      * guarantees that {@link #bindInput(InputBinding)},
94      * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and
95      * {@link #unbindInput()} are called with the same order as the original calls
96      * in {@link com.android.server.inputmethod.InputMethodManagerService}.
97      * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
98      */
99     @Nullable
100     CancellationGroup mCancellationGroup = null;
101 
102     // NOTE: we should have a cache of these.
103     static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
104         final Context mContext;
105         final InputChannel mChannel;
106         final IInputSessionCallback mCb;
107 
InputMethodSessionCallbackWrapper(Context context, InputChannel channel, IInputSessionCallback cb)108         InputMethodSessionCallbackWrapper(Context context, InputChannel channel,
109                 IInputSessionCallback cb) {
110             mContext = context;
111             mChannel = channel;
112             mCb = cb;
113         }
114 
115         @Override
sessionCreated(InputMethodSession session)116         public void sessionCreated(InputMethodSession session) {
117             try {
118                 if (session != null) {
119                     IInputMethodSessionWrapper wrap =
120                             new IInputMethodSessionWrapper(mContext, session, mChannel);
121                     mCb.sessionCreated(wrap);
122                 } else {
123                     if (mChannel != null) {
124                         mChannel.dispose();
125                     }
126                     mCb.sessionCreated(null);
127                 }
128             } catch (RemoteException e) {
129             }
130         }
131     }
132 
IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod)133     public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) {
134         mTarget = new WeakReference<>(context);
135         mContext = context.getApplicationContext();
136         mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
137         mInputMethod = new WeakReference<>(inputMethod);
138         mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
139     }
140 
141     @MainThread
142     @Override
executeMessage(Message msg)143     public void executeMessage(Message msg) {
144         InputMethod inputMethod = mInputMethod.get();
145         // Need a valid reference to the inputMethod for everything except a dump.
146         if (inputMethod == null && msg.what != DO_DUMP) {
147             Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
148             return;
149         }
150 
151         switch (msg.what) {
152             case DO_DUMP: {
153                 AbstractInputMethodService target = mTarget.get();
154                 if (target == null) {
155                     return;
156                 }
157                 SomeArgs args = (SomeArgs)msg.obj;
158                 try {
159                     target.dump((FileDescriptor)args.arg1,
160                             (PrintWriter)args.arg2, (String[])args.arg3);
161                 } catch (RuntimeException e) {
162                     ((PrintWriter)args.arg2).println("Exception: " + e);
163                 }
164                 synchronized (args.arg4) {
165                     ((CountDownLatch)args.arg4).countDown();
166                 }
167                 args.recycle();
168                 return;
169             }
170             case DO_INITIALIZE_INTERNAL: {
171                 SomeArgs args = (SomeArgs) msg.obj;
172                 try {
173                     inputMethod.initializeInternal((IBinder) args.arg1, msg.arg1,
174                             (IInputMethodPrivilegedOperations) args.arg2);
175                 } finally {
176                     args.recycle();
177                 }
178                 return;
179             }
180             case DO_SET_INPUT_CONTEXT: {
181                 inputMethod.bindInput((InputBinding)msg.obj);
182                 return;
183             }
184             case DO_UNSET_INPUT_CONTEXT:
185                 inputMethod.unbindInput();
186                 return;
187             case DO_START_INPUT: {
188                 final SomeArgs args = (SomeArgs) msg.obj;
189                 final IBinder startInputToken = (IBinder) args.arg1;
190                 final IInputContext inputContext = (IInputContext) args.arg2;
191                 final EditorInfo info = (EditorInfo) args.arg3;
192                 final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
193                 SomeArgs moreArgs = (SomeArgs) args.arg5;
194                 final InputConnection ic = inputContext != null
195                         ? new InputConnectionWrapper(
196                                 mTarget, inputContext, moreArgs.argi3, cancellationGroup)
197                         : null;
198                 info.makeCompatible(mTargetSdkVersion);
199                 inputMethod.dispatchStartInputWithToken(
200                         ic,
201                         info,
202                         moreArgs.argi1 == 1 /* restarting */,
203                         startInputToken,
204                         moreArgs.argi2 == 1 /* shouldPreRenderIme */);
205                 args.recycle();
206                 moreArgs.recycle();
207                 return;
208             }
209             case DO_CREATE_SESSION: {
210                 SomeArgs args = (SomeArgs)msg.obj;
211                 inputMethod.createSession(new InputMethodSessionCallbackWrapper(
212                         mContext, (InputChannel)args.arg1,
213                         (IInputSessionCallback)args.arg2));
214                 args.recycle();
215                 return;
216             }
217             case DO_SET_SESSION_ENABLED:
218                 inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
219                         msg.arg1 != 0);
220                 return;
221             case DO_REVOKE_SESSION:
222                 inputMethod.revokeSession((InputMethodSession)msg.obj);
223                 return;
224             case DO_SHOW_SOFT_INPUT: {
225                 final SomeArgs args = (SomeArgs)msg.obj;
226                 inputMethod.showSoftInputWithToken(
227                         msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
228                 args.recycle();
229                 return;
230             }
231             case DO_HIDE_SOFT_INPUT: {
232                 final SomeArgs args = (SomeArgs) msg.obj;
233                 inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
234                         (IBinder) args.arg1);
235                 args.recycle();
236                 return;
237             }
238             case DO_CHANGE_INPUTMETHOD_SUBTYPE:
239                 inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
240                 return;
241             case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
242                 final SomeArgs args = (SomeArgs) msg.obj;
243                 inputMethod.onCreateInlineSuggestionsRequest(
244                         (InlineSuggestionsRequestInfo) args.arg1,
245                         (IInlineSuggestionsRequestCallback) args.arg2);
246                 args.recycle();
247                 return;
248 
249         }
250         Log.w(TAG, "Unhandled message code: " + msg.what);
251     }
252 
253     @BinderThread
254     @Override
dump(FileDescriptor fd, PrintWriter fout, String[] args)255     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
256         AbstractInputMethodService target = mTarget.get();
257         if (target == null) {
258             return;
259         }
260         if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
261                 != PackageManager.PERMISSION_GRANTED) {
262 
263             fout.println("Permission Denial: can't dump InputMethodManager from from pid="
264                     + Binder.getCallingPid()
265                     + ", uid=" + Binder.getCallingUid());
266             return;
267         }
268 
269         CountDownLatch latch = new CountDownLatch(1);
270         mCaller.executeOrSendMessage(mCaller.obtainMessageOOOO(DO_DUMP,
271                 fd, fout, args, latch));
272         try {
273             if (!latch.await(5, TimeUnit.SECONDS)) {
274                 fout.println("Timeout waiting for dump");
275             }
276         } catch (InterruptedException e) {
277             fout.println("Interrupted waiting for dump");
278         }
279     }
280 
281     @BinderThread
282     @Override
initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps)283     public void initializeInternal(IBinder token, int displayId,
284             IInputMethodPrivilegedOperations privOps) {
285         mCaller.executeOrSendMessage(
286                 mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps));
287     }
288 
289     @BinderThread
290     @Override
onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb)291     public void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
292             IInlineSuggestionsRequestCallback cb) {
293         mCaller.executeOrSendMessage(
294                 mCaller.obtainMessageOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, requestInfo, cb));
295     }
296 
297     @BinderThread
298     @Override
bindInput(InputBinding binding)299     public void bindInput(InputBinding binding) {
300         if (mCancellationGroup != null) {
301             Log.e(TAG, "bindInput must be paired with unbindInput.");
302         }
303         mCancellationGroup = new CancellationGroup();
304         // This IInputContext is guaranteed to implement all the methods.
305         final int missingMethodFlags = 0;
306         InputConnection ic = new InputConnectionWrapper(mTarget,
307                 IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
308                 mCancellationGroup);
309         InputBinding nu = new InputBinding(ic, binding);
310         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
311     }
312 
313     @BinderThread
314     @Override
unbindInput()315     public void unbindInput() {
316         if (mCancellationGroup != null) {
317             // Signal the flag then forget it.
318             mCancellationGroup.cancelAll();
319             mCancellationGroup = null;
320         } else {
321             Log.e(TAG, "unbindInput must be paired with bindInput.");
322         }
323         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
324     }
325 
326     @BinderThread
327     @Override
startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme)328     public void startInput(IBinder startInputToken, IInputContext inputContext,
329             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
330             EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
331         if (mCancellationGroup == null) {
332             Log.e(TAG, "startInput must be called after bindInput.");
333             mCancellationGroup = new CancellationGroup();
334         }
335         SomeArgs args = SomeArgs.obtain();
336         args.argi1 = restarting ? 1 : 0;
337         args.argi2 = shouldPreRenderIme ? 1 : 0;
338         args.argi3 = missingMethods;
339         mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken,
340                 inputContext, attribute, mCancellationGroup, args));
341     }
342 
343     @BinderThread
344     @Override
createSession(InputChannel channel, IInputSessionCallback callback)345     public void createSession(InputChannel channel, IInputSessionCallback callback) {
346         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
347                 channel, callback));
348     }
349 
350     @BinderThread
351     @Override
setSessionEnabled(IInputMethodSession session, boolean enabled)352     public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
353         try {
354             InputMethodSession ls = ((IInputMethodSessionWrapper)
355                     session).getInternalInputMethodSession();
356             if (ls == null) {
357                 Log.w(TAG, "Session is already finished: " + session);
358                 return;
359             }
360             mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
361                     DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls));
362         } catch (ClassCastException e) {
363             Log.w(TAG, "Incoming session not of correct type: " + session, e);
364         }
365     }
366 
367     @BinderThread
368     @Override
revokeSession(IInputMethodSession session)369     public void revokeSession(IInputMethodSession session) {
370         try {
371             InputMethodSession ls = ((IInputMethodSessionWrapper)
372                     session).getInternalInputMethodSession();
373             if (ls == null) {
374                 Log.w(TAG, "Session is already finished: " + session);
375                 return;
376             }
377             mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls));
378         } catch (ClassCastException e) {
379             Log.w(TAG, "Incoming session not of correct type: " + session, e);
380         }
381     }
382 
383     @BinderThread
384     @Override
showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver)385     public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
386         mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
387                 flags, showInputToken, resultReceiver));
388     }
389 
390     @BinderThread
391     @Override
hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver)392     public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
393         mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT,
394                 flags, hideInputToken, resultReceiver));
395     }
396 
397     @BinderThread
398     @Override
changeInputMethodSubtype(InputMethodSubtype subtype)399     public void changeInputMethodSubtype(InputMethodSubtype subtype) {
400         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
401                 subtype));
402     }
403 }
404