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