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.DurationMillisLong; 21 import android.annotation.MainThread; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.compat.annotation.UnsupportedAppUsage; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.os.Binder; 28 import android.os.IBinder; 29 import android.os.Message; 30 import android.os.RemoteException; 31 import android.os.ResultReceiver; 32 import android.util.Log; 33 import android.view.InputChannel; 34 import android.view.MotionEvent; 35 import android.view.inputmethod.ImeTracker; 36 import android.view.inputmethod.InputBinding; 37 import android.view.inputmethod.InputConnection; 38 import android.view.inputmethod.InputMethod; 39 import android.view.inputmethod.InputMethodSession; 40 import android.view.inputmethod.InputMethodSubtype; 41 42 import com.android.internal.inputmethod.CancellationGroup; 43 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; 44 import com.android.internal.inputmethod.IInputMethod; 45 import com.android.internal.inputmethod.IInputMethodSession; 46 import com.android.internal.inputmethod.IInputMethodSessionCallback; 47 import com.android.internal.inputmethod.IRemoteInputConnection; 48 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; 49 import com.android.internal.inputmethod.InputMethodNavButtonFlags; 50 import com.android.internal.os.HandlerCaller; 51 import com.android.internal.os.SomeArgs; 52 53 import java.io.FileDescriptor; 54 import java.io.PrintWriter; 55 import java.lang.ref.WeakReference; 56 import java.util.List; 57 import java.util.concurrent.CountDownLatch; 58 import java.util.concurrent.TimeUnit; 59 60 /** 61 * Implements the internal IInputMethod interface to convert incoming calls 62 * on to it back to calls on the public InputMethod interface, scheduling 63 * them on the main thread of the process. 64 */ 65 class IInputMethodWrapper extends IInputMethod.Stub 66 implements HandlerCaller.Callback { 67 private static final String TAG = "InputMethodWrapper"; 68 69 private static final int DO_DUMP = 1; 70 private static final int DO_INITIALIZE_INTERNAL = 10; 71 private static final int DO_SET_INPUT_CONTEXT = 20; 72 private static final int DO_UNSET_INPUT_CONTEXT = 30; 73 private static final int DO_START_INPUT = 32; 74 private static final int DO_ON_NAV_BUTTON_FLAGS_CHANGED = 35; 75 private static final int DO_CREATE_SESSION = 40; 76 private static final int DO_SET_SESSION_ENABLED = 45; 77 private static final int DO_SHOW_SOFT_INPUT = 60; 78 private static final int DO_HIDE_SOFT_INPUT = 70; 79 private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80; 80 private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90; 81 private static final int DO_CAN_START_STYLUS_HANDWRITING = 100; 82 private static final int DO_START_STYLUS_HANDWRITING = 110; 83 private static final int DO_INIT_INK_WINDOW = 120; 84 private static final int DO_FINISH_STYLUS_HANDWRITING = 130; 85 private static final int DO_UPDATE_TOOL_TYPE = 140; 86 private static final int DO_REMOVE_STYLUS_HANDWRITING_WINDOW = 150; 87 private static final int DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT = 160; 88 89 final WeakReference<InputMethodServiceInternal> mTarget; 90 final Context mContext; 91 @UnsupportedAppUsage 92 final HandlerCaller mCaller; 93 final WeakReference<InputMethod> mInputMethod; 94 final int mTargetSdkVersion; 95 96 /** 97 * This is not {@code null} only between {@link #bindInput(InputBinding)} and 98 * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if 99 * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary 100 * blocking operations. 101 * 102 * <p>This field must be set and cleared only from the binder thread(s), where the system 103 * guarantees that {@link #bindInput(InputBinding)}, 104 * {@link #startInput(IInputMethod.StartInputParams)}, and {@link #unbindInput()} are called 105 * with the same order as the original calls in 106 * {@link com.android.server.inputmethod.InputMethodManagerService}. 107 * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p> 108 */ 109 @Nullable 110 CancellationGroup mCancellationGroup = null; 111 112 // NOTE: we should have a cache of these. 113 static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback { 114 final Context mContext; 115 final InputChannel mChannel; 116 final IInputMethodSessionCallback mCb; 117 InputMethodSessionCallbackWrapper(Context context, InputChannel channel, IInputMethodSessionCallback cb)118 InputMethodSessionCallbackWrapper(Context context, InputChannel channel, 119 IInputMethodSessionCallback cb) { 120 mContext = context; 121 mChannel = channel; 122 mCb = cb; 123 } 124 125 @Override sessionCreated(InputMethodSession session)126 public void sessionCreated(InputMethodSession session) { 127 try { 128 if (session != null) { 129 IInputMethodSessionWrapper wrap = 130 new IInputMethodSessionWrapper(mContext, session, mChannel); 131 mCb.sessionCreated(wrap); 132 } else { 133 if (mChannel != null) { 134 mChannel.dispose(); 135 } 136 mCb.sessionCreated(null); 137 } 138 } catch (RemoteException e) { 139 } 140 } 141 } 142 IInputMethodWrapper(InputMethodServiceInternal imsInternal, InputMethod inputMethod)143 IInputMethodWrapper(InputMethodServiceInternal imsInternal, InputMethod inputMethod) { 144 mTarget = new WeakReference<>(imsInternal); 145 mContext = imsInternal.getContext().getApplicationContext(); 146 mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/); 147 mInputMethod = new WeakReference<>(inputMethod); 148 mTargetSdkVersion = imsInternal.getContext().getApplicationInfo().targetSdkVersion; 149 } 150 151 @MainThread 152 @Override executeMessage(Message msg)153 public void executeMessage(Message msg) { 154 final InputMethod inputMethod = mInputMethod.get(); 155 final InputMethodServiceInternal target = mTarget.get(); 156 switch (msg.what) { 157 case DO_DUMP: { 158 SomeArgs args = (SomeArgs) msg.obj; 159 if (isValid(inputMethod, target, "DO_DUMP")) { 160 final FileDescriptor fd = (FileDescriptor) args.arg1; 161 final PrintWriter fout = (PrintWriter) args.arg2; 162 final String[] dumpArgs = (String[]) args.arg3; 163 final CountDownLatch latch = (CountDownLatch) args.arg4; 164 try { 165 target.dump(fd, fout, dumpArgs); 166 } catch (RuntimeException e) { 167 fout.println("Exception: " + e); 168 } finally { 169 latch.countDown(); 170 } 171 } 172 args.recycle(); 173 return; 174 } 175 case DO_INITIALIZE_INTERNAL: 176 if (isValid(inputMethod, target, "DO_INITIALIZE_INTERNAL")) { 177 inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj); 178 } 179 return; 180 case DO_SET_INPUT_CONTEXT: { 181 if (isValid(inputMethod, target, "DO_SET_INPUT_CONTEXT")) { 182 inputMethod.bindInput((InputBinding) msg.obj); 183 } 184 return; 185 } 186 case DO_UNSET_INPUT_CONTEXT: 187 if (isValid(inputMethod, target, "DO_UNSET_INPUT_CONTEXT")) { 188 inputMethod.unbindInput(); 189 } 190 return; 191 case DO_START_INPUT: { 192 final SomeArgs args = (SomeArgs) msg.obj; 193 if (isValid(inputMethod, target, "DO_START_INPUT")) { 194 final InputConnection inputConnection = (InputConnection) args.arg1; 195 final IInputMethod.StartInputParams params = 196 (IInputMethod.StartInputParams) args.arg2; 197 inputMethod.dispatchStartInput(inputConnection, params); 198 } 199 args.recycle(); 200 return; 201 } 202 case DO_ON_NAV_BUTTON_FLAGS_CHANGED: 203 if (isValid(inputMethod, target, "DO_ON_NAV_BUTTON_FLAGS_CHANGED")) { 204 inputMethod.onNavButtonFlagsChanged(msg.arg1); 205 } 206 return; 207 case DO_CREATE_SESSION: { 208 SomeArgs args = (SomeArgs) msg.obj; 209 if (isValid(inputMethod, target, "DO_CREATE_SESSION")) { 210 inputMethod.createSession(new InputMethodSessionCallbackWrapper( 211 mContext, (InputChannel) args.arg1, 212 (IInputMethodSessionCallback) args.arg2)); 213 } 214 args.recycle(); 215 return; 216 } 217 case DO_SET_SESSION_ENABLED: 218 if (isValid(inputMethod, target, "DO_SET_SESSION_ENABLED")) { 219 inputMethod.setSessionEnabled((InputMethodSession) msg.obj, msg.arg1 != 0); 220 } 221 return; 222 case DO_SHOW_SOFT_INPUT: { 223 final SomeArgs args = (SomeArgs) msg.obj; 224 final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3; 225 if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) { 226 ImeTracker.forLogging().onProgress( 227 statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH); 228 inputMethod.showSoftInputWithToken( 229 msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken); 230 } else { 231 ImeTracker.forLogging().onFailed( 232 statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH); 233 } 234 args.recycle(); 235 return; 236 } 237 case DO_HIDE_SOFT_INPUT: { 238 final SomeArgs args = (SomeArgs) msg.obj; 239 final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3; 240 if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) { 241 ImeTracker.forLogging().onProgress( 242 statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH); 243 inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2, 244 (IBinder) args.arg1, statsToken); 245 } else { 246 ImeTracker.forLogging().onFailed( 247 statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH); 248 } 249 args.recycle(); 250 return; 251 } 252 case DO_CHANGE_INPUTMETHOD_SUBTYPE: 253 if (isValid(inputMethod, target, "DO_CHANGE_INPUTMETHOD_SUBTYPE")) { 254 inputMethod.changeInputMethodSubtype((InputMethodSubtype) msg.obj); 255 } 256 return; 257 case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: { 258 final SomeArgs args = (SomeArgs) msg.obj; 259 if (isValid(inputMethod, target, "DO_CREATE_INLINE_SUGGESTIONS_REQUEST")) { 260 inputMethod.onCreateInlineSuggestionsRequest( 261 (InlineSuggestionsRequestInfo) args.arg1, 262 (IInlineSuggestionsRequestCallback) args.arg2); 263 } 264 args.recycle(); 265 return; 266 } 267 case DO_CAN_START_STYLUS_HANDWRITING: { 268 if (isValid(inputMethod, target, "DO_CAN_START_STYLUS_HANDWRITING")) { 269 inputMethod.canStartStylusHandwriting(msg.arg1); 270 } 271 return; 272 } 273 case DO_UPDATE_TOOL_TYPE: { 274 if (isValid(inputMethod, target, "DO_UPDATE_TOOL_TYPE")) { 275 inputMethod.updateEditorToolType(msg.arg1); 276 } 277 return; 278 } 279 case DO_START_STYLUS_HANDWRITING: { 280 final SomeArgs args = (SomeArgs) msg.obj; 281 if (isValid(inputMethod, target, "DO_START_STYLUS_HANDWRITING")) { 282 inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1, 283 (List<MotionEvent>) args.arg2); 284 } 285 args.recycle(); 286 return; 287 } 288 case DO_INIT_INK_WINDOW: { 289 if (isValid(inputMethod, target, "DO_INIT_INK_WINDOW")) { 290 inputMethod.initInkWindow(); 291 } 292 return; 293 } 294 case DO_FINISH_STYLUS_HANDWRITING: { 295 if (isValid(inputMethod, target, "DO_FINISH_STYLUS_HANDWRITING")) { 296 inputMethod.finishStylusHandwriting(); 297 } 298 return; 299 } 300 case DO_REMOVE_STYLUS_HANDWRITING_WINDOW: { 301 if (isValid(inputMethod, target, "DO_REMOVE_STYLUS_HANDWRITING_WINDOW")) { 302 inputMethod.removeStylusHandwritingWindow(); 303 } 304 return; 305 } 306 case DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT: { 307 if (isValid(inputMethod, target, "DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT")) { 308 inputMethod.setStylusWindowIdleTimeoutForTest((long) msg.obj); 309 } 310 return; 311 } 312 } 313 Log.w(TAG, "Unhandled message code: " + msg.what); 314 } 315 316 @BinderThread 317 @Override dump(FileDescriptor fd, PrintWriter fout, String[] args)318 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 319 InputMethodServiceInternal target = mTarget.get(); 320 if (target == null) { 321 return; 322 } 323 if (target.getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 324 != PackageManager.PERMISSION_GRANTED) { 325 326 fout.println("Permission Denial: can't dump InputMethodManager from from pid=" 327 + Binder.getCallingPid() 328 + ", uid=" + Binder.getCallingUid()); 329 return; 330 } 331 332 CountDownLatch latch = new CountDownLatch(1); 333 mCaller.getHandler().sendMessageAtFrontOfQueue(mCaller.obtainMessageOOOO(DO_DUMP, 334 fd, fout, args, latch)); 335 try { 336 if (!latch.await(5, TimeUnit.SECONDS)) { 337 fout.println("Timeout waiting for dump"); 338 } 339 } catch (InterruptedException e) { 340 fout.println("Interrupted waiting for dump"); 341 } 342 } 343 344 @BinderThread 345 @Override initializeInternal(@onNull IInputMethod.InitParams params)346 public void initializeInternal(@NonNull IInputMethod.InitParams params) { 347 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_INITIALIZE_INTERNAL, params)); 348 } 349 350 @BinderThread 351 @Override onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb)352 public void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, 353 IInlineSuggestionsRequestCallback cb) { 354 mCaller.executeOrSendMessage( 355 mCaller.obtainMessageOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, requestInfo, cb)); 356 } 357 358 @BinderThread 359 @Override bindInput(InputBinding binding)360 public void bindInput(InputBinding binding) { 361 if (mCancellationGroup != null) { 362 Log.e(TAG, "bindInput must be paired with unbindInput."); 363 } 364 mCancellationGroup = new CancellationGroup(); 365 InputConnection ic = new RemoteInputConnection(mTarget, 366 IRemoteInputConnection.Stub.asInterface(binding.getConnectionToken()), 367 mCancellationGroup); 368 InputBinding nu = new InputBinding(ic, binding); 369 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu)); 370 } 371 372 @BinderThread 373 @Override unbindInput()374 public void unbindInput() { 375 if (mCancellationGroup != null) { 376 // Signal the flag then forget it. 377 mCancellationGroup.cancelAll(); 378 mCancellationGroup = null; 379 } else { 380 Log.e(TAG, "unbindInput must be paired with bindInput."); 381 } 382 mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT)); 383 } 384 385 @BinderThread 386 @Override startInput(@onNull IInputMethod.StartInputParams params)387 public void startInput(@NonNull IInputMethod.StartInputParams params) { 388 if (mCancellationGroup == null) { 389 Log.e(TAG, "startInput must be called after bindInput."); 390 mCancellationGroup = new CancellationGroup(); 391 } 392 393 params.editorInfo.makeCompatible(mTargetSdkVersion); 394 395 final InputConnection ic = params.remoteInputConnection == null ? null 396 : new RemoteInputConnection(mTarget, params.remoteInputConnection, 397 mCancellationGroup); 398 399 mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT, ic, params)); 400 } 401 402 @BinderThread 403 @Override onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)404 public void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { 405 mCaller.executeOrSendMessage( 406 mCaller.obtainMessageI(DO_ON_NAV_BUTTON_FLAGS_CHANGED, navButtonFlags)); 407 } 408 409 @BinderThread 410 @Override createSession(InputChannel channel, IInputMethodSessionCallback callback)411 public void createSession(InputChannel channel, IInputMethodSessionCallback callback) { 412 mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION, 413 channel, callback)); 414 } 415 416 @BinderThread 417 @Override setSessionEnabled(IInputMethodSession session, boolean enabled)418 public void setSessionEnabled(IInputMethodSession session, boolean enabled) { 419 try { 420 InputMethodSession ls = ((IInputMethodSessionWrapper) 421 session).getInternalInputMethodSession(); 422 if (ls == null) { 423 Log.w(TAG, "Session is already finished: " + session); 424 return; 425 } 426 mCaller.executeOrSendMessage(mCaller.obtainMessageIO( 427 DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls)); 428 } catch (ClassCastException e) { 429 Log.w(TAG, "Incoming session not of correct type: " + session, e); 430 } 431 } 432 433 @BinderThread 434 @Override showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver)435 public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, 436 @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { 437 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); 438 mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT, 439 flags, showInputToken, resultReceiver, statsToken)); 440 } 441 442 @BinderThread 443 @Override hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver)444 public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, 445 int flags, ResultReceiver resultReceiver) { 446 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); 447 mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT, 448 flags, hideInputToken, resultReceiver, statsToken)); 449 } 450 451 @BinderThread 452 @Override changeInputMethodSubtype(InputMethodSubtype subtype)453 public void changeInputMethodSubtype(InputMethodSubtype subtype) { 454 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE, 455 subtype)); 456 } 457 458 @BinderThread 459 @Override canStartStylusHandwriting(int requestId)460 public void canStartStylusHandwriting(int requestId) 461 throws RemoteException { 462 mCaller.executeOrSendMessage( 463 mCaller.obtainMessageI(DO_CAN_START_STYLUS_HANDWRITING, requestId)); 464 } 465 466 @BinderThread 467 @Override updateEditorToolType(int toolType)468 public void updateEditorToolType(int toolType) 469 throws RemoteException { 470 mCaller.executeOrSendMessage( 471 mCaller.obtainMessageI(DO_UPDATE_TOOL_TYPE, toolType)); 472 } 473 474 @BinderThread 475 @Override startStylusHandwriting(int requestId, @NonNull InputChannel channel, @Nullable List<MotionEvent> stylusEvents)476 public void startStylusHandwriting(int requestId, @NonNull InputChannel channel, 477 @Nullable List<MotionEvent> stylusEvents) 478 throws RemoteException { 479 mCaller.executeOrSendMessage( 480 mCaller.obtainMessageIOO(DO_START_STYLUS_HANDWRITING, requestId, channel, 481 stylusEvents)); 482 } 483 484 @BinderThread 485 @Override initInkWindow()486 public void initInkWindow() { 487 mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_INIT_INK_WINDOW)); 488 } 489 490 @BinderThread 491 @Override finishStylusHandwriting()492 public void finishStylusHandwriting() { 493 mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_STYLUS_HANDWRITING)); 494 } 495 496 @BinderThread 497 @Override removeStylusHandwritingWindow()498 public void removeStylusHandwritingWindow() { 499 mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_STYLUS_HANDWRITING_WINDOW)); 500 } 501 502 @BinderThread 503 @Override setStylusWindowIdleTimeoutForTest(@urationMillisLong long timeout)504 public void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) { 505 mCaller.executeOrSendMessage( 506 mCaller.obtainMessageO(DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT, timeout)); 507 } 508 isValid(InputMethod inputMethod, InputMethodServiceInternal target, String msg)509 private static boolean isValid(InputMethod inputMethod, InputMethodServiceInternal target, 510 String msg) { 511 if (inputMethod != null && target != null && !target.isServiceDestroyed()) { 512 return true; 513 } else { 514 Log.w(TAG, "Ignoring " + msg + ", InputMethod:" + inputMethod 515 + ", InputMethodServiceInternal:" + target); 516 return false; 517 } 518 } 519 } 520