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