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