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