1 /*
2  * Copyright (C) 2022 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 com.android.server.inputmethod;
18 
19 import android.annotation.AnyThread;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.os.Binder;
23 import android.os.DeadObjectException;
24 import android.os.IBinder;
25 import android.os.RemoteException;
26 import android.os.ResultReceiver;
27 import android.util.Slog;
28 import android.view.InputChannel;
29 import android.view.MotionEvent;
30 import android.view.inputmethod.CursorAnchorInfo;
31 import android.view.inputmethod.EditorInfo;
32 import android.view.inputmethod.ImeTracker;
33 import android.view.inputmethod.InputBinding;
34 import android.view.inputmethod.InputMethod;
35 import android.view.inputmethod.InputMethodSubtype;
36 import android.window.ImeOnBackInvokedDispatcher;
37 
38 import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
39 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
40 import com.android.internal.inputmethod.IInputMethod;
41 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
42 import com.android.internal.inputmethod.IInputMethodSession;
43 import com.android.internal.inputmethod.IInputMethodSessionCallback;
44 import com.android.internal.inputmethod.IRemoteInputConnection;
45 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
46 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
47 
48 import java.util.List;
49 
50 /**
51  * A wrapper class to invoke IPCs defined in {@link IInputMethod}.
52  */
53 final class IInputMethodInvoker {
54     private static final String TAG = InputMethodManagerService.TAG;
55     private static final boolean DEBUG = InputMethodManagerService.DEBUG;
56 
57     @AnyThread
58     @Nullable
create(@ullable IInputMethod inputMethod)59     static IInputMethodInvoker create(@Nullable IInputMethod inputMethod) {
60         if (inputMethod == null) {
61             return null;
62         }
63         if (!Binder.isProxy(inputMethod)) {
64             // IInputMethodInvoker must be used only within the system_server and InputMethodService
65             // must not be running in the system_server.  Therefore, "inputMethod" must be a Proxy.
66             throw new UnsupportedOperationException(inputMethod + " must have been a BinderProxy.");
67         }
68         return new IInputMethodInvoker(inputMethod);
69     }
70 
71     /**
72      * A simplified version of {@link android.os.Debug#getCaller()}.
73      *
74      * @return method name of the caller.
75      */
76     @AnyThread
getCallerMethodName()77     private static String getCallerMethodName() {
78         final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
79         if (callStack.length <= 4) {
80             return "<bottom of call stack>";
81         }
82         return callStack[4].getMethodName();
83     }
84 
85     @AnyThread
logRemoteException(@onNull RemoteException e)86     private static void logRemoteException(@NonNull RemoteException e) {
87         if (DEBUG || !(e instanceof DeadObjectException)) {
88             Slog.w(TAG, "IPC failed at IInputMethodInvoker#" + getCallerMethodName(), e);
89         }
90     }
91 
92     @AnyThread
getBinderIdentityHashCode(@ullable IInputMethodInvoker obj)93     static int getBinderIdentityHashCode(@Nullable IInputMethodInvoker obj) {
94         if (obj == null) {
95             return 0;
96         }
97 
98         return System.identityHashCode(obj.mTarget);
99     }
100 
101     @NonNull
102     private final IInputMethod mTarget;
103 
IInputMethodInvoker(@onNull IInputMethod target)104     private IInputMethodInvoker(@NonNull IInputMethod target) {
105         mTarget = target;
106     }
107 
108     @AnyThread
109     @NonNull
asBinder()110     IBinder asBinder() {
111         return mTarget.asBinder();
112     }
113 
114     @AnyThread
initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations, @InputMethodNavButtonFlags int navigationBarFlags)115     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
116             @InputMethodNavButtonFlags int navigationBarFlags) {
117         final IInputMethod.InitParams params = new IInputMethod.InitParams();
118         params.token = token;
119         params.privilegedOperations = privilegedOperations;
120         params.navigationBarFlags = navigationBarFlags;
121         try {
122             mTarget.initializeInternal(params);
123         } catch (RemoteException e) {
124             logRemoteException(e);
125         }
126     }
127 
128     @AnyThread
onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb)129     void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
130             IInlineSuggestionsRequestCallback cb) {
131         try {
132             mTarget.onCreateInlineSuggestionsRequest(requestInfo, cb);
133         } catch (RemoteException e) {
134             logRemoteException(e);
135         }
136     }
137 
138     @AnyThread
bindInput(InputBinding binding)139     void bindInput(InputBinding binding) {
140         try {
141             mTarget.bindInput(binding);
142         } catch (RemoteException e) {
143             logRemoteException(e);
144         }
145     }
146 
147     @AnyThread
unbindInput()148     void unbindInput() {
149         try {
150             mTarget.unbindInput();
151         } catch (RemoteException e) {
152             logRemoteException(e);
153         }
154     }
155 
156     @AnyThread
startInput(IBinder startInputToken, IRemoteInputConnection remoteInputConnection, EditorInfo editorInfo, boolean restarting, @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher)157     void startInput(IBinder startInputToken, IRemoteInputConnection remoteInputConnection,
158             EditorInfo editorInfo, boolean restarting,
159             @InputMethodNavButtonFlags int navButtonFlags,
160             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
161         final IInputMethod.StartInputParams params = new IInputMethod.StartInputParams();
162         params.startInputToken = startInputToken;
163         params.remoteInputConnection = remoteInputConnection;
164         params.editorInfo = editorInfo;
165         params.restarting = restarting;
166         params.navigationBarFlags = navButtonFlags;
167         params.imeDispatcher = imeDispatcher;
168         try {
169             mTarget.startInput(params);
170         } catch (RemoteException e) {
171             logRemoteException(e);
172         }
173     }
174 
175     @AnyThread
onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)176     void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
177         try {
178             mTarget.onNavButtonFlagsChanged(navButtonFlags);
179         } catch (RemoteException e) {
180             logRemoteException(e);
181         }
182     }
183 
184     @AnyThread
createSession(InputChannel channel, IInputMethodSessionCallback callback)185     void createSession(InputChannel channel, IInputMethodSessionCallback callback) {
186         try {
187             mTarget.createSession(channel, callback);
188         } catch (RemoteException e) {
189             logRemoteException(e);
190         }
191     }
192 
193     @AnyThread
setSessionEnabled(IInputMethodSession session, boolean enabled)194     void setSessionEnabled(IInputMethodSession session, boolean enabled) {
195         try {
196             mTarget.setSessionEnabled(session, enabled);
197         } catch (RemoteException e) {
198             logRemoteException(e);
199         }
200     }
201 
202     // TODO(b/192412909): Convert this back to void method
203     @AnyThread
showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver)204     boolean showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
205             @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
206         try {
207             mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
208         } catch (RemoteException e) {
209             logRemoteException(e);
210             return false;
211         }
212         return true;
213     }
214 
215     // TODO(b/192412909): Convert this back to void method
216     @AnyThread
hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver)217     boolean hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
218             int flags, ResultReceiver resultReceiver) {
219         try {
220             mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver);
221         } catch (RemoteException e) {
222             logRemoteException(e);
223             return false;
224         }
225         return true;
226     }
227 
228     @AnyThread
updateEditorToolType(@otionEvent.ToolType int toolType)229     void updateEditorToolType(@MotionEvent.ToolType int toolType) {
230         try {
231             mTarget.updateEditorToolType(toolType);
232         } catch (RemoteException e) {
233             logRemoteException(e);
234         }
235     }
236 
237     @AnyThread
changeInputMethodSubtype(InputMethodSubtype subtype)238     void changeInputMethodSubtype(InputMethodSubtype subtype) {
239         try {
240             mTarget.changeInputMethodSubtype(subtype);
241         } catch (RemoteException e) {
242             logRemoteException(e);
243         }
244     }
245 
246     @AnyThread
canStartStylusHandwriting(int requestId, IConnectionlessHandwritingCallback connectionlessCallback, CursorAnchorInfo cursorAnchorInfo, boolean isConnectionlessForDelegation)247     void canStartStylusHandwriting(int requestId,
248             IConnectionlessHandwritingCallback connectionlessCallback,
249             CursorAnchorInfo cursorAnchorInfo, boolean isConnectionlessForDelegation) {
250         try {
251             mTarget.canStartStylusHandwriting(requestId, connectionlessCallback, cursorAnchorInfo,
252                     isConnectionlessForDelegation);
253         } catch (RemoteException e) {
254             logRemoteException(e);
255         }
256     }
257 
258     @AnyThread
startStylusHandwriting(int requestId, InputChannel channel, List<MotionEvent> events)259     boolean startStylusHandwriting(int requestId, InputChannel channel, List<MotionEvent> events) {
260         try {
261             mTarget.startStylusHandwriting(requestId, channel, events);
262         } catch (RemoteException e) {
263             logRemoteException(e);
264             return false;
265         }
266         return true;
267     }
268 
269     @AnyThread
commitHandwritingDelegationTextIfAvailable()270     void commitHandwritingDelegationTextIfAvailable() {
271         try {
272             mTarget.commitHandwritingDelegationTextIfAvailable();
273         } catch (RemoteException e) {
274             logRemoteException(e);
275         }
276     }
277 
278     @AnyThread
discardHandwritingDelegationText()279     void discardHandwritingDelegationText() {
280         try {
281             mTarget.discardHandwritingDelegationText();
282         } catch (RemoteException e) {
283             logRemoteException(e);
284         }
285     }
286 
287     @AnyThread
initInkWindow()288     void initInkWindow() {
289         try {
290             mTarget.initInkWindow();
291         } catch (RemoteException e) {
292             logRemoteException(e);
293         }
294     }
295 
296     @AnyThread
finishStylusHandwriting()297     void finishStylusHandwriting() {
298         try {
299             mTarget.finishStylusHandwriting();
300         } catch (RemoteException e) {
301             logRemoteException(e);
302         }
303     }
304 
305     @AnyThread
removeStylusHandwritingWindow()306     void removeStylusHandwritingWindow() {
307         try {
308             mTarget.removeStylusHandwritingWindow();
309         } catch (RemoteException e) {
310             logRemoteException(e);
311         }
312     }
313 
314     @AnyThread
setStylusWindowIdleTimeoutForTest(long timeout)315     void setStylusWindowIdleTimeoutForTest(long timeout) {
316         try {
317             mTarget.setStylusWindowIdleTimeoutForTest(timeout);
318         } catch (RemoteException e) {
319             logRemoteException(e);
320         }
321     }
322 }
323