1 /* 2 * Copyright (C) 2021 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.view.inputmethod; 18 19 import android.annotation.AnyThread; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.graphics.Rect; 23 import android.os.Binder; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.RemoteException; 28 import android.util.Log; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.inputmethod.IInputMethodSession; 32 import com.android.internal.inputmethod.IRemoteInputConnection; 33 34 /** 35 * This class wrap the {@link IInputMethodSession} object from {@link InputMethodManager}. 36 * Using current {@link IInputMethodSession} object to communicate with 37 * {@link android.inputmethodservice.InputMethodService}. 38 */ 39 final class IInputMethodSessionInvoker { 40 41 private static final String TAG = "InputMethodSessionWrapper"; 42 43 /** 44 * The actual instance of the method to make calls on it. 45 */ 46 @NonNull 47 private final IInputMethodSession mSession; 48 49 /** 50 * An optional {@link Handler} to dispatch {@link IInputMethodSession} method invocations to 51 * a background thread to emulate async (one-way) {@link Binder} call. 52 * 53 * {@code null} if {@code Binder.isProxy(mSession)} is {@code true}. 54 */ 55 @Nullable 56 private final Handler mCustomHandler; 57 58 private static final Object sAsyncBinderEmulationHandlerLock = new Object(); 59 60 @GuardedBy("sAsyncBinderEmulationHandlerLock") 61 @Nullable 62 private static Handler sAsyncBinderEmulationHandler; 63 IInputMethodSessionInvoker(@onNull IInputMethodSession inputMethodSession, @Nullable Handler customHandler)64 private IInputMethodSessionInvoker(@NonNull IInputMethodSession inputMethodSession, 65 @Nullable Handler customHandler) { 66 mSession = inputMethodSession; 67 mCustomHandler = customHandler; 68 } 69 70 /** 71 * Create a {@link IInputMethodSessionInvoker} instance if applicability. 72 * 73 * @param inputMethodSession {@link IInputMethodSession} object to be wrapped. 74 * @return an instance of {@link IInputMethodSessionInvoker} if {@code inputMethodSession} is 75 * not {@code null}. {@code null} otherwise. 76 */ 77 @Nullable createOrNull( @onNull IInputMethodSession inputMethodSession)78 public static IInputMethodSessionInvoker createOrNull( 79 @NonNull IInputMethodSession inputMethodSession) { 80 81 final Handler customHandler; 82 if (inputMethodSession != null && !Binder.isProxy(inputMethodSession)) { 83 synchronized (sAsyncBinderEmulationHandlerLock) { 84 if (sAsyncBinderEmulationHandler == null) { 85 final HandlerThread thread = new HandlerThread("IMM.binder-emu"); 86 thread.start(); 87 // Use an async handler instead of Handler#getThreadHandler(). 88 sAsyncBinderEmulationHandler = Handler.createAsync(thread.getLooper()); 89 } 90 customHandler = sAsyncBinderEmulationHandler; 91 } 92 } else { 93 customHandler = null; 94 } 95 96 return inputMethodSession != null 97 ? new IInputMethodSessionInvoker(inputMethodSession, customHandler) : null; 98 } 99 100 @AnyThread finishInput()101 void finishInput() { 102 if (mCustomHandler == null) { 103 finishInputInternal(); 104 } else { 105 mCustomHandler.post(this::finishInputInternal); 106 } 107 } 108 109 @AnyThread finishInputInternal()110 private void finishInputInternal() { 111 try { 112 mSession.finishInput(); 113 } catch (RemoteException e) { 114 Log.w(TAG, "IME died", e); 115 } 116 } 117 118 @AnyThread updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo)119 void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { 120 if (mCustomHandler == null) { 121 updateCursorAnchorInfoInternal(cursorAnchorInfo); 122 } else { 123 mCustomHandler.post(() -> updateCursorAnchorInfoInternal(cursorAnchorInfo)); 124 } 125 } 126 127 @AnyThread updateCursorAnchorInfoInternal(CursorAnchorInfo cursorAnchorInfo)128 private void updateCursorAnchorInfoInternal(CursorAnchorInfo cursorAnchorInfo) { 129 try { 130 mSession.updateCursorAnchorInfo(cursorAnchorInfo); 131 } catch (RemoteException e) { 132 Log.w(TAG, "IME died", e); 133 } 134 } 135 136 @AnyThread displayCompletions(CompletionInfo[] completions)137 void displayCompletions(CompletionInfo[] completions) { 138 if (mCustomHandler == null) { 139 displayCompletionsInternal(completions); 140 } else { 141 mCustomHandler.post(() -> displayCompletionsInternal(completions)); 142 } 143 } 144 145 @AnyThread displayCompletionsInternal(CompletionInfo[] completions)146 void displayCompletionsInternal(CompletionInfo[] completions) { 147 try { 148 mSession.displayCompletions(completions); 149 } catch (RemoteException e) { 150 Log.w(TAG, "IME died", e); 151 } 152 } 153 154 @AnyThread updateExtractedText(int token, ExtractedText text)155 void updateExtractedText(int token, ExtractedText text) { 156 if (mCustomHandler == null) { 157 updateExtractedTextInternal(token, text); 158 } else { 159 mCustomHandler.post(() -> updateExtractedTextInternal(token, text)); 160 } 161 } 162 163 @AnyThread updateExtractedTextInternal(int token, ExtractedText text)164 private void updateExtractedTextInternal(int token, ExtractedText text) { 165 try { 166 mSession.updateExtractedText(token, text); 167 } catch (RemoteException e) { 168 Log.w(TAG, "IME died", e); 169 } 170 } 171 172 @AnyThread appPrivateCommand(String action, Bundle data)173 void appPrivateCommand(String action, Bundle data) { 174 if (mCustomHandler == null) { 175 appPrivateCommandInternal(action, data); 176 } else { 177 mCustomHandler.post(() -> appPrivateCommandInternal(action, data)); 178 } 179 } 180 181 @AnyThread appPrivateCommandInternal(String action, Bundle data)182 private void appPrivateCommandInternal(String action, Bundle data) { 183 try { 184 mSession.appPrivateCommand(action, data); 185 } catch (RemoteException e) { 186 Log.w(TAG, "IME died", e); 187 } 188 } 189 190 @AnyThread viewClicked(boolean focusChanged)191 void viewClicked(boolean focusChanged) { 192 if (mCustomHandler == null) { 193 viewClickedInternal(focusChanged); 194 } else { 195 mCustomHandler.post(() -> viewClickedInternal(focusChanged)); 196 } 197 } 198 199 @AnyThread viewClickedInternal(boolean focusChanged)200 private void viewClickedInternal(boolean focusChanged) { 201 try { 202 mSession.viewClicked(focusChanged); 203 } catch (RemoteException e) { 204 Log.w(TAG, "IME died", e); 205 } 206 } 207 208 @AnyThread updateCursor(Rect newCursor)209 void updateCursor(Rect newCursor) { 210 if (mCustomHandler == null) { 211 updateCursorInternal(newCursor); 212 } else { 213 mCustomHandler.post(() -> updateCursorInternal(newCursor)); 214 } 215 } 216 217 @AnyThread updateCursorInternal(Rect newCursor)218 private void updateCursorInternal(Rect newCursor) { 219 try { 220 mSession.updateCursor(newCursor); 221 } catch (RemoteException e) { 222 Log.w(TAG, "IME died", e); 223 } 224 } 225 226 @AnyThread updateSelection(int oldSelStart, int oldSelEnd, int selStart, int selEnd, int candidatesStart, int candidatesEnd)227 void updateSelection(int oldSelStart, int oldSelEnd, int selStart, int selEnd, 228 int candidatesStart, int candidatesEnd) { 229 if (mCustomHandler == null) { 230 updateSelectionInternal( 231 oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); 232 } else { 233 mCustomHandler.post(() -> updateSelectionInternal( 234 oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd)); 235 } 236 } 237 238 @AnyThread updateSelectionInternal(int oldSelStart, int oldSelEnd, int selStart, int selEnd, int candidatesStart, int candidatesEnd)239 private void updateSelectionInternal(int oldSelStart, int oldSelEnd, int selStart, int selEnd, 240 int candidatesStart, int candidatesEnd) { 241 try { 242 mSession.updateSelection( 243 oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); 244 } catch (RemoteException e) { 245 Log.w(TAG, "IME died", e); 246 } 247 } 248 249 @AnyThread invalidateInput(EditorInfo editorInfo, IRemoteInputConnection inputConnection, int sessionId)250 void invalidateInput(EditorInfo editorInfo, IRemoteInputConnection inputConnection, 251 int sessionId) { 252 if (mCustomHandler == null) { 253 invalidateInputInternal(editorInfo, inputConnection, sessionId); 254 } else { 255 mCustomHandler.post(() -> invalidateInputInternal(editorInfo, inputConnection, 256 sessionId)); 257 } 258 } 259 260 @AnyThread invalidateInputInternal(EditorInfo editorInfo, IRemoteInputConnection inputConnection, int sessionId)261 private void invalidateInputInternal(EditorInfo editorInfo, 262 IRemoteInputConnection inputConnection, int sessionId) { 263 try { 264 mSession.invalidateInput(editorInfo, inputConnection, sessionId); 265 } catch (RemoteException e) { 266 Log.w(TAG, "IME died", e); 267 } 268 } 269 270 /** 271 * @return {@link IInputMethodSession#toString()} as a debug string. 272 */ 273 @AnyThread 274 @NonNull 275 @Override toString()276 public String toString() { 277 return mSession.toString(); 278 } 279 } 280