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