2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.inputmethodservice;
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;
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;
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;
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.
55 class IInputMethodWrapper extends IInputMethod.Stub
56 implements HandlerCaller.Callback {
57 private static final String TAG = "InputMethodWrapper";
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_RESTART_INPUT = 34;
65 private static final int DO_CREATE_SESSION = 40;
66 private static final int DO_SET_SESSION_ENABLED = 45;
67 private static final int DO_REVOKE_SESSION = 50;
68 private static final int DO_SHOW_SOFT_INPUT = 60;
69 private static final int DO_HIDE_SOFT_INPUT = 70;
70 private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
72 final WeakReference<AbstractInputMethodService> mTarget;
73 final Context mContext;
74 final HandlerCaller mCaller;
75 final WeakReference<InputMethod> mInputMethod;
76 final int mTargetSdkVersion;
78 static class Notifier {
82 // NOTE: we should have a cache of these.
83 static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
84 final Context mContext;
85 final InputChannel mChannel;
86 final IInputSessionCallback mCb;
88 InputMethodSessionCallbackWrapper(Context context, InputChannel channel,
89 IInputSessionCallback cb) {
96 public void sessionCreated(InputMethodSession session) {
98 if (session != null) {
99 IInputMethodSessionWrapper wrap =
100 new IInputMethodSessionWrapper(mContext, session, mChannel);
101 mCb.sessionCreated(wrap);
103 if (mChannel != null) {
106 mCb.sessionCreated(null);
108 } catch (RemoteException e) {
113 public IInputMethodWrapper(AbstractInputMethodService context,
114 InputMethod inputMethod) {
115 mTarget = new WeakReference<AbstractInputMethodService>(context);
116 mContext = context.getApplicationContext();
117 mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
118 mInputMethod = new WeakReference<InputMethod>(inputMethod);
119 mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
122 public InputMethod getInternalInputMethod() {
123 return mInputMethod.get();
127 public void executeMessage(Message msg) {
128 InputMethod inputMethod = mInputMethod.get();
129 // Need a valid reference to the inputMethod for everything except a dump.
130 if (inputMethod == null && msg.what != DO_DUMP) {
131 Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
137 AbstractInputMethodService target = mTarget.get();
138 if (target == null) {
141 SomeArgs args = (SomeArgs)msg.obj;
143 target.dump((FileDescriptor)args.arg1,
144 (PrintWriter)args.arg2, (String[])args.arg3);
145 } catch (RuntimeException e) {
146 ((PrintWriter)args.arg2).println("Exception: " + e);
148 synchronized (args.arg4) {
149 ((CountDownLatch)args.arg4).countDown();
155 case DO_ATTACH_TOKEN: {
156 inputMethod.attachToken((IBinder)msg.obj);
159 case DO_SET_INPUT_CONTEXT: {
160 inputMethod.bindInput((InputBinding)msg.obj);
163 case DO_UNSET_INPUT_CONTEXT:
164 inputMethod.unbindInput();
166 case DO_START_INPUT: {
167 SomeArgs args = (SomeArgs)msg.obj;
168 int missingMethods = msg.arg1;
169 IInputContext inputContext = (IInputContext)args.arg1;
170 InputConnection ic = inputContext != null
171 ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
172 EditorInfo info = (EditorInfo)args.arg2;
173 info.makeCompatible(mTargetSdkVersion);
174 inputMethod.startInput(ic, info);
178 case DO_RESTART_INPUT: {
179 SomeArgs args = (SomeArgs)msg.obj;
180 int missingMethods = msg.arg1;
181 IInputContext inputContext = (IInputContext)args.arg1;
182 InputConnection ic = inputContext != null
183 ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
184 EditorInfo info = (EditorInfo)args.arg2;
185 info.makeCompatible(mTargetSdkVersion);
186 inputMethod.restartInput(ic, info);
190 case DO_CREATE_SESSION: {
191 SomeArgs args = (SomeArgs)msg.obj;
192 inputMethod.createSession(new InputMethodSessionCallbackWrapper(
193 mContext, (InputChannel)args.arg1,
194 (IInputSessionCallback)args.arg2));
198 case DO_SET_SESSION_ENABLED:
199 inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
202 case DO_REVOKE_SESSION:
203 inputMethod.revokeSession((InputMethodSession)msg.obj);
205 case DO_SHOW_SOFT_INPUT:
206 inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
208 case DO_HIDE_SOFT_INPUT:
209 inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
211 case DO_CHANGE_INPUTMETHOD_SUBTYPE:
212 inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
215 Log.w(TAG, "Unhandled message code: " + msg.what);
219 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
220 AbstractInputMethodService target = mTarget.get();
221 if (target == null) {
224 if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
225 != PackageManager.PERMISSION_GRANTED) {
227 fout.println("Permission Denial: can't dump InputMethodManager from from pid="
228 + Binder.getCallingPid()
229 + ", uid=" + Binder.getCallingUid());
233 CountDownLatch latch = new CountDownLatch(1);
234 mCaller.executeOrSendMessage(mCaller.obtainMessageOOOO(DO_DUMP,
235 fd, fout, args, latch));
237 if (!latch.await(5, TimeUnit.SECONDS)) {
238 fout.println("Timeout waiting for dump");
240 } catch (InterruptedException e) {
241 fout.println("Interrupted waiting for dump");
246 public void attachToken(IBinder token) {
247 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
251 public void bindInput(InputBinding binding) {
252 // This IInputContext is guaranteed to implement all the methods.
253 final int missingMethodFlags = 0;
254 InputConnection ic = new InputConnectionWrapper(mTarget,
255 IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
256 InputBinding nu = new InputBinding(ic, binding);
257 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
261 public void unbindInput() {
262 mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
266 public void startInput(IInputContext inputContext,
267 @InputConnectionInspector.MissingMethodFlags final int missingMethods,
268 EditorInfo attribute) {
269 mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_START_INPUT,
270 missingMethods, inputContext, attribute));
274 public void restartInput(IInputContext inputContext,
275 @InputConnectionInspector.MissingMethodFlags final int missingMethods,
276 EditorInfo attribute) {
277 mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_RESTART_INPUT,
278 missingMethods, inputContext, attribute));
282 public void createSession(InputChannel channel, IInputSessionCallback callback) {
283 mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
288 public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
290 InputMethodSession ls = ((IInputMethodSessionWrapper)
291 session).getInternalInputMethodSession();
293 Log.w(TAG, "Session is already finished: " + session);
296 mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
297 DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls));
298 } catch (ClassCastException e) {
299 Log.w(TAG, "Incoming session not of correct type: " + session, e);
304 public void revokeSession(IInputMethodSession session) {
306 InputMethodSession ls = ((IInputMethodSessionWrapper)
307 session).getInternalInputMethodSession();
309 Log.w(TAG, "Session is already finished: " + session);
312 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls));
313 } catch (ClassCastException e) {
314 Log.w(TAG, "Incoming session not of correct type: " + session, e);
319 public void showSoftInput(int flags, ResultReceiver resultReceiver) {
320 mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
321 flags, resultReceiver));
325 public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
326 mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
327 flags, resultReceiver));
331 public void changeInputMethodSubtype(InputMethodSubtype subtype) {
332 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,