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