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