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.CancellationGroup; 41 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; 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, 174 (IInputMethodPrivilegedOperations) args.arg2, msg.arg1); 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 args.recycle(); 205 moreArgs.recycle(); 206 return; 207 } 208 case DO_CREATE_SESSION: { 209 SomeArgs args = (SomeArgs)msg.obj; 210 inputMethod.createSession(new InputMethodSessionCallbackWrapper( 211 mContext, (InputChannel)args.arg1, 212 (IInputSessionCallback)args.arg2)); 213 args.recycle(); 214 return; 215 } 216 case DO_SET_SESSION_ENABLED: 217 inputMethod.setSessionEnabled((InputMethodSession)msg.obj, 218 msg.arg1 != 0); 219 return; 220 case DO_REVOKE_SESSION: 221 inputMethod.revokeSession((InputMethodSession)msg.obj); 222 return; 223 case DO_SHOW_SOFT_INPUT: { 224 final SomeArgs args = (SomeArgs)msg.obj; 225 inputMethod.showSoftInputWithToken( 226 msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1); 227 args.recycle(); 228 return; 229 } 230 case DO_HIDE_SOFT_INPUT: { 231 final SomeArgs args = (SomeArgs) msg.obj; 232 inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2, 233 (IBinder) args.arg1); 234 args.recycle(); 235 return; 236 } 237 case DO_CHANGE_INPUTMETHOD_SUBTYPE: 238 inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj); 239 return; 240 case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: 241 final SomeArgs args = (SomeArgs) msg.obj; 242 inputMethod.onCreateInlineSuggestionsRequest( 243 (InlineSuggestionsRequestInfo) args.arg1, 244 (IInlineSuggestionsRequestCallback) args.arg2); 245 args.recycle(); 246 return; 247 248 } 249 Log.w(TAG, "Unhandled message code: " + msg.what); 250 } 251 252 @BinderThread 253 @Override dump(FileDescriptor fd, PrintWriter fout, String[] args)254 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 255 AbstractInputMethodService target = mTarget.get(); 256 if (target == null) { 257 return; 258 } 259 if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 260 != PackageManager.PERMISSION_GRANTED) { 261 262 fout.println("Permission Denial: can't dump InputMethodManager from from pid=" 263 + Binder.getCallingPid() 264 + ", uid=" + Binder.getCallingUid()); 265 return; 266 } 267 268 CountDownLatch latch = new CountDownLatch(1); 269 mCaller.getHandler().sendMessageAtFrontOfQueue(mCaller.obtainMessageOOOO(DO_DUMP, 270 fd, fout, args, latch)); 271 try { 272 if (!latch.await(5, TimeUnit.SECONDS)) { 273 fout.println("Timeout waiting for dump"); 274 } 275 } catch (InterruptedException e) { 276 fout.println("Interrupted waiting for dump"); 277 } 278 } 279 280 @BinderThread 281 @Override initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, int configChanges)282 public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, 283 int configChanges) { 284 mCaller.executeOrSendMessage( 285 mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, configChanges, token, privOps)); 286 } 287 288 @BinderThread 289 @Override onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb)290 public void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, 291 IInlineSuggestionsRequestCallback cb) { 292 mCaller.executeOrSendMessage( 293 mCaller.obtainMessageOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, requestInfo, cb)); 294 } 295 296 @BinderThread 297 @Override bindInput(InputBinding binding)298 public void bindInput(InputBinding binding) { 299 if (mCancellationGroup != null) { 300 Log.e(TAG, "bindInput must be paired with unbindInput."); 301 } 302 mCancellationGroup = new CancellationGroup(); 303 // This IInputContext is guaranteed to implement all the methods. 304 final int missingMethodFlags = 0; 305 InputConnection ic = new InputConnectionWrapper(mTarget, 306 IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags, 307 mCancellationGroup); 308 InputBinding nu = new InputBinding(ic, binding); 309 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu)); 310 } 311 312 @BinderThread 313 @Override unbindInput()314 public void unbindInput() { 315 if (mCancellationGroup != null) { 316 // Signal the flag then forget it. 317 mCancellationGroup.cancelAll(); 318 mCancellationGroup = null; 319 } else { 320 Log.e(TAG, "unbindInput must be paired with bindInput."); 321 } 322 mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT)); 323 } 324 325 @BinderThread 326 @Override startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting)327 public void startInput(IBinder startInputToken, IInputContext inputContext, 328 @InputConnectionInspector.MissingMethodFlags final int missingMethods, 329 EditorInfo attribute, boolean restarting) { 330 if (mCancellationGroup == null) { 331 Log.e(TAG, "startInput must be called after bindInput."); 332 mCancellationGroup = new CancellationGroup(); 333 } 334 SomeArgs args = SomeArgs.obtain(); 335 args.argi1 = restarting ? 1 : 0; 336 args.argi3 = missingMethods; 337 mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken, 338 inputContext, attribute, mCancellationGroup, args)); 339 } 340 341 @BinderThread 342 @Override createSession(InputChannel channel, IInputSessionCallback callback)343 public void createSession(InputChannel channel, IInputSessionCallback callback) { 344 mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION, 345 channel, callback)); 346 } 347 348 @BinderThread 349 @Override setSessionEnabled(IInputMethodSession session, boolean enabled)350 public void setSessionEnabled(IInputMethodSession session, boolean enabled) { 351 try { 352 InputMethodSession ls = ((IInputMethodSessionWrapper) 353 session).getInternalInputMethodSession(); 354 if (ls == null) { 355 Log.w(TAG, "Session is already finished: " + session); 356 return; 357 } 358 mCaller.executeOrSendMessage(mCaller.obtainMessageIO( 359 DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls)); 360 } catch (ClassCastException e) { 361 Log.w(TAG, "Incoming session not of correct type: " + session, e); 362 } 363 } 364 365 @BinderThread 366 @Override revokeSession(IInputMethodSession session)367 public void revokeSession(IInputMethodSession session) { 368 try { 369 InputMethodSession ls = ((IInputMethodSessionWrapper) 370 session).getInternalInputMethodSession(); 371 if (ls == null) { 372 Log.w(TAG, "Session is already finished: " + session); 373 return; 374 } 375 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls)); 376 } catch (ClassCastException e) { 377 Log.w(TAG, "Incoming session not of correct type: " + session, e); 378 } 379 } 380 381 @BinderThread 382 @Override showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver)383 public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) { 384 mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT, 385 flags, showInputToken, resultReceiver)); 386 } 387 388 @BinderThread 389 @Override hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver)390 public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) { 391 mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT, 392 flags, hideInputToken, resultReceiver)); 393 } 394 395 @BinderThread 396 @Override changeInputMethodSubtype(InputMethodSubtype subtype)397 public void changeInputMethodSubtype(InputMethodSubtype subtype) { 398 mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE, 399 subtype)); 400 } 401 } 402