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.AnyThread;
20 import android.annotation.CallbackExecutor;
21 import android.annotation.IntRange;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.graphics.RectF;
25 import android.os.Bundle;
26 import android.os.CancellationSignal;
27 import android.os.Handler;
28 import android.util.Log;
29 import android.view.KeyEvent;
30 import android.view.inputmethod.CompletionInfo;
31 import android.view.inputmethod.CorrectionInfo;
32 import android.view.inputmethod.ExtractedText;
33 import android.view.inputmethod.ExtractedTextRequest;
34 import android.view.inputmethod.HandwritingGesture;
35 import android.view.inputmethod.InputConnection;
36 import android.view.inputmethod.InputContentInfo;
37 import android.view.inputmethod.PreviewableHandwritingGesture;
38 import android.view.inputmethod.SurroundingText;
39 import android.view.inputmethod.TextAttribute;
40 import android.view.inputmethod.TextBoundsInfoResult;
41 
42 import com.android.internal.inputmethod.CancellationGroup;
43 import com.android.internal.inputmethod.CompletableFutureUtil;
44 import com.android.internal.inputmethod.IRemoteInputConnection;
45 import com.android.internal.inputmethod.ImeTracing;
46 import com.android.internal.inputmethod.InputConnectionProtoDumper;
47 
48 import java.lang.ref.WeakReference;
49 import java.util.concurrent.CompletableFuture;
50 import java.util.concurrent.Executor;
51 import java.util.function.Consumer;
52 import java.util.function.IntConsumer;
53 
54 /**
55  * Takes care of remote method invocations of {@link InputConnection} in the IME side.
56  *
57  * <p>This class works as a proxy to forward API calls on {@link InputConnection} to
58  * {@link android.view.inputmethod.RemoteInputConnectionImpl} running on the IME client
59  * (editor app) process then waits replies as needed.</p>
60  *
61  * <p>See also {@link IRemoteInputConnection} for the actual {@link android.os.Binder} IPC protocols
62  * under the hood.</p>
63  */
64 final class RemoteInputConnection implements InputConnection {
65     private static final String TAG = "RemoteInputConnection";
66 
67     private static final int MAX_WAIT_TIME_MILLIS = 2000;
68 
69     @NonNull
70     private final IRemoteInputConnectionInvoker mInvoker;
71 
72     private static final class InputMethodServiceInternalHolder {
73         @NonNull
74         private final WeakReference<InputMethodServiceInternal> mServiceRef;
75 
InputMethodServiceInternalHolder( @onNull WeakReference<InputMethodServiceInternal> ims)76         private InputMethodServiceInternalHolder(
77                 @NonNull WeakReference<InputMethodServiceInternal> ims) {
78             mServiceRef = ims;
79         }
80 
81         @AnyThread
82         @Nullable
getAndWarnIfNull()83         public InputMethodServiceInternal getAndWarnIfNull() {
84             final InputMethodServiceInternal ims = mServiceRef.get();
85             if (ims == null) {
86                 Log.e(TAG, "InputMethodService is already destroyed.  InputConnection instances"
87                         + " cannot be used beyond InputMethodService lifetime.", new Throwable());
88             }
89             return ims;
90         }
91     }
92 
93     @NonNull
94     private final InputMethodServiceInternalHolder mImsInternal;
95 
96     /**
97      * Signaled when the system decided to take away IME focus from the target app.
98      *
99      * <p>This is expected to be signaled immediately when the IME process receives
100      * {@link com.android.internal.inputmethod.IInputMethod#unbindInput()}.</p>
101      */
102     @NonNull
103     private final CancellationGroup mCancellationGroup;
104 
RemoteInputConnection( @onNull WeakReference<InputMethodServiceInternal> inputMethodService, IRemoteInputConnection inputConnection, @NonNull CancellationGroup cancellationGroup)105     RemoteInputConnection(
106             @NonNull WeakReference<InputMethodServiceInternal> inputMethodService,
107             IRemoteInputConnection inputConnection, @NonNull CancellationGroup cancellationGroup) {
108         mImsInternal = new InputMethodServiceInternalHolder(inputMethodService);
109         mInvoker = IRemoteInputConnectionInvoker.create(inputConnection);
110         mCancellationGroup = cancellationGroup;
111     }
112 
113     @AnyThread
isSameConnection(@onNull IRemoteInputConnection inputConnection)114     public boolean isSameConnection(@NonNull IRemoteInputConnection inputConnection) {
115         return mInvoker.isSameConnection(inputConnection);
116     }
117 
RemoteInputConnection(@onNull RemoteInputConnection original, int sessionId)118     RemoteInputConnection(@NonNull RemoteInputConnection original, int sessionId) {
119         mImsInternal = original.mImsInternal;
120         mInvoker = original.mInvoker.cloneWithSessionId(sessionId);
121         mCancellationGroup = original.mCancellationGroup;
122     }
123 
124     /**
125      * See {@link InputConnection#getTextAfterCursor(int, int)}.
126      */
127     @Nullable
128     @AnyThread
getTextAfterCursor(@ntRangefrom = 0) int length, int flags)129     public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) {
130         if (length < 0) {
131             // TODO: Should we throw an InvalidParameterException() based on targetSdkVersion?
132             Log.e(TAG, "length=" + length + " is invalid and always results in null result.");
133         }
134         if (mCancellationGroup.isCanceled()) {
135             return null;
136         }
137 
138         final CompletableFuture<CharSequence> value = mInvoker.getTextAfterCursor(length, flags);
139         final CharSequence result = CompletableFutureUtil.getResultOrNull(
140                 value, TAG, "getTextAfterCursor()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
141 
142         final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull();
143         if (imsInternal != null && ImeTracing.getInstance().isEnabled()) {
144             final byte[] icProto = InputConnectionProtoDumper.buildGetTextAfterCursorProto(length,
145                     flags, result);
146             imsInternal.triggerServiceDump(TAG + "#getTextAfterCursor", icProto);
147         }
148 
149         return result;
150     }
151 
152     /**
153      * See {@link InputConnection#getTextBeforeCursor(int, int)}.
154      */
155     @Nullable
156     @AnyThread
getTextBeforeCursor(@ntRangefrom = 0) int length, int flags)157     public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) {
158         if (length < 0) {
159             // TODO: Should we throw an InvalidParameterException() based on targetSdkVersion?
160             Log.e(TAG, "length=" + length + " is invalid and always results in null result.");
161         }
162         if (mCancellationGroup.isCanceled()) {
163             return null;
164         }
165 
166         final CompletableFuture<CharSequence> value = mInvoker.getTextBeforeCursor(length, flags);
167         final CharSequence result = CompletableFutureUtil.getResultOrNull(
168                 value, TAG, "getTextBeforeCursor()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
169 
170         final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull();
171         if (imsInternal != null && ImeTracing.getInstance().isEnabled()) {
172             final byte[] icProto = InputConnectionProtoDumper.buildGetTextBeforeCursorProto(length,
173                     flags, result);
174             imsInternal.triggerServiceDump(TAG + "#getTextBeforeCursor", icProto);
175         }
176 
177         return result;
178     }
179 
180     @AnyThread
getSelectedText(int flags)181     public CharSequence getSelectedText(int flags) {
182         if (mCancellationGroup.isCanceled()) {
183             return null;
184         }
185 
186         final CompletableFuture<CharSequence> value = mInvoker.getSelectedText(flags);
187         final CharSequence result = CompletableFutureUtil.getResultOrNull(
188                 value, TAG, "getSelectedText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
189 
190         final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull();
191         if (imsInternal != null && ImeTracing.getInstance().isEnabled()) {
192             final byte[] icProto = InputConnectionProtoDumper.buildGetSelectedTextProto(flags,
193                     result);
194             imsInternal.triggerServiceDump(TAG + "#getSelectedText", icProto);
195         }
196 
197         return result;
198     }
199 
200     /**
201      * Get {@link SurroundingText} around the current cursor, with <var>beforeLength</var>
202      * characters of text before the cursor, <var>afterLength</var> characters of text after the
203      * cursor, and all of the selected text.
204      * @param beforeLength The expected length of the text before the cursor
205      * @param afterLength The expected length of the text after the cursor
206      * @param flags Supplies additional options controlling how the text is returned. May be either
207      *              0 or {@link #GET_TEXT_WITH_STYLES}.
208      * @return the surrounding text around the cursor position; the length of the returned text
209      * might be less than requested.  It could also be {@code null} when the editor or system could
210      * not support this protocol.
211      */
212     @AnyThread
getSurroundingText( @ntRangefrom = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags)213     public SurroundingText getSurroundingText(
214             @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) {
215         if (beforeLength < 0) {
216             // TODO: Should we throw an InvalidParameterException() based on targetSdkVersion?
217             Log.e(TAG, "beforeLength=" + beforeLength
218                     + " is invalid and always results in null result.");
219         }
220         if (afterLength < 0) {
221             // TODO: Should we throw an InvalidParameterException() based on targetSdkVersion?
222             Log.e(TAG, "afterLength=" + afterLength
223                     + " is invalid and always results in null result.");
224         }
225         if (mCancellationGroup.isCanceled()) {
226             return null;
227         }
228 
229         final CompletableFuture<SurroundingText> value = mInvoker.getSurroundingText(beforeLength,
230                 afterLength, flags);
231         final SurroundingText result = CompletableFutureUtil.getResultOrNull(
232                 value, TAG, "getSurroundingText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
233 
234         final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull();
235         if (imsInternal != null && ImeTracing.getInstance().isEnabled()) {
236             final byte[] icProto = InputConnectionProtoDumper.buildGetSurroundingTextProto(
237                     beforeLength, afterLength, flags, result);
238             imsInternal.triggerServiceDump(TAG + "#getSurroundingText", icProto);
239         }
240 
241         return result;
242     }
243 
244     @AnyThread
getCursorCapsMode(int reqModes)245     public int getCursorCapsMode(int reqModes) {
246         if (mCancellationGroup.isCanceled()) {
247             return 0;
248         }
249 
250         final CompletableFuture<Integer> value = mInvoker.getCursorCapsMode(reqModes);
251         final int result = CompletableFutureUtil.getResultOrZero(
252                 value, TAG, "getCursorCapsMode()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
253 
254         final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull();
255         if (imsInternal != null && ImeTracing.getInstance().isEnabled()) {
256             final byte[] icProto = InputConnectionProtoDumper.buildGetCursorCapsModeProto(
257                     reqModes, result);
258             imsInternal.triggerServiceDump(TAG + "#getCursorCapsMode", icProto);
259         }
260 
261         return result;
262     }
263 
264     @AnyThread
getExtractedText(ExtractedTextRequest request, int flags)265     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
266         if (mCancellationGroup.isCanceled()) {
267             return null;
268         }
269 
270         final CompletableFuture<ExtractedText> value = mInvoker.getExtractedText(request, flags);
271         final ExtractedText result = CompletableFutureUtil.getResultOrNull(
272                 value, TAG, "getExtractedText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
273 
274         final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull();
275         if (imsInternal != null && ImeTracing.getInstance().isEnabled()) {
276             final byte[] icProto = InputConnectionProtoDumper.buildGetExtractedTextProto(
277                     request, flags, result);
278             imsInternal.triggerServiceDump(TAG + "#getExtractedText", icProto);
279         }
280 
281         return result;
282     }
283 
284     @AnyThread
commitText(CharSequence text, int newCursorPosition)285     public boolean commitText(CharSequence text, int newCursorPosition) {
286         final boolean handled = mInvoker.commitText(text, newCursorPosition);
287         if (handled) {
288             notifyUserActionIfNecessary();
289         }
290         return handled;
291     }
292 
293     @AnyThread
commitText(@onNull CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)294     public boolean commitText(@NonNull CharSequence text, int newCursorPosition,
295             @Nullable TextAttribute textAttribute) {
296         final boolean handled =
297                 mInvoker.commitText(text, newCursorPosition, textAttribute);
298         if (handled) {
299             notifyUserActionIfNecessary();
300         }
301         return handled;
302     }
303 
304     @AnyThread
notifyUserActionIfNecessary()305     private void notifyUserActionIfNecessary() {
306         final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull();
307         if (imsInternal == null) {
308             return;
309         }
310         imsInternal.notifyUserActionIfNecessary();
311     }
312 
313     @AnyThread
commitCompletion(CompletionInfo text)314     public boolean commitCompletion(CompletionInfo text) {
315         return mInvoker.commitCompletion(text);
316     }
317 
318     @AnyThread
commitCorrection(CorrectionInfo correctionInfo)319     public boolean commitCorrection(CorrectionInfo correctionInfo) {
320         return mInvoker.commitCorrection(correctionInfo);
321     }
322 
323     @AnyThread
setSelection(int start, int end)324     public boolean setSelection(int start, int end) {
325         return mInvoker.setSelection(start, end);
326     }
327 
328     @AnyThread
performEditorAction(int actionCode)329     public boolean performEditorAction(int actionCode) {
330         return mInvoker.performEditorAction(actionCode);
331     }
332 
333     @AnyThread
performContextMenuAction(int id)334     public boolean performContextMenuAction(int id) {
335         return mInvoker.performContextMenuAction(id);
336     }
337 
338     @AnyThread
setComposingRegion(int start, int end)339     public boolean setComposingRegion(int start, int end) {
340         return mInvoker.setComposingRegion(start, end);
341     }
342 
343     @AnyThread
setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute)344     public boolean setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute) {
345         return mInvoker.setComposingRegion(start, end, textAttribute);
346     }
347 
348     @AnyThread
setComposingText(CharSequence text, int newCursorPosition)349     public boolean setComposingText(CharSequence text, int newCursorPosition) {
350         final boolean handled = mInvoker.setComposingText(text, newCursorPosition);
351         if (handled) {
352             notifyUserActionIfNecessary();
353         }
354         return handled;
355     }
356 
357     @AnyThread
setComposingText(CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)358     public boolean setComposingText(CharSequence text, int newCursorPosition,
359             @Nullable TextAttribute textAttribute) {
360         final boolean handled = mInvoker.setComposingText(text, newCursorPosition, textAttribute);
361         if (handled) {
362             notifyUserActionIfNecessary();
363         }
364         return handled;
365     }
366 
367     @AnyThread
finishComposingText()368     public boolean finishComposingText() {
369         return mInvoker.finishComposingText();
370     }
371 
372     @AnyThread
beginBatchEdit()373     public boolean beginBatchEdit() {
374         return mInvoker.beginBatchEdit();
375     }
376 
377     @AnyThread
endBatchEdit()378     public boolean endBatchEdit() {
379         return mInvoker.endBatchEdit();
380     }
381 
382     @AnyThread
sendKeyEvent(KeyEvent event)383     public boolean sendKeyEvent(KeyEvent event) {
384         final boolean handled = mInvoker.sendKeyEvent(event);
385         if (handled) {
386             notifyUserActionIfNecessary();
387         }
388         return handled;
389     }
390 
391     @AnyThread
clearMetaKeyStates(int states)392     public boolean clearMetaKeyStates(int states) {
393         return mInvoker.clearMetaKeyStates(states);
394     }
395 
396     @AnyThread
deleteSurroundingText(int beforeLength, int afterLength)397     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
398         return mInvoker.deleteSurroundingText(beforeLength, afterLength);
399     }
400 
401     @AnyThread
deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)402     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
403         return mInvoker.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
404     }
405 
406     @AnyThread
reportFullscreenMode(boolean enabled)407     public boolean reportFullscreenMode(boolean enabled) {
408         // Nothing should happen when called from input method.
409         return false;
410     }
411 
412     @AnyThread
performSpellCheck()413     public boolean performSpellCheck() {
414         return mInvoker.performSpellCheck();
415     }
416 
417     @AnyThread
performPrivateCommand(String action, Bundle data)418     public boolean performPrivateCommand(String action, Bundle data) {
419         return mInvoker.performPrivateCommand(action, data);
420     }
421 
422     @AnyThread
performHandwritingGesture( @onNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer)423     public void performHandwritingGesture(
424             @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
425             @Nullable IntConsumer consumer) {
426         mInvoker.performHandwritingGesture(gesture, executor, consumer);
427     }
428 
429     @AnyThread
previewHandwritingGesture( @onNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)430     public boolean previewHandwritingGesture(
431             @NonNull PreviewableHandwritingGesture gesture,
432             @Nullable CancellationSignal cancellationSignal) {
433         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
434             return false; // cancelled.
435         }
436 
437         return mInvoker.previewHandwritingGesture(gesture, cancellationSignal);
438     }
439 
440     @AnyThread
requestCursorUpdates(int cursorUpdateMode)441     public boolean requestCursorUpdates(int cursorUpdateMode) {
442         if (mCancellationGroup.isCanceled()) {
443             return false;
444         }
445 
446         final InputMethodServiceInternal ims = mImsInternal.getAndWarnIfNull();
447         if (ims == null) {
448             return false;
449         }
450 
451         final int displayId = ims.getContext().getDisplayId();
452         final CompletableFuture<Boolean> value =
453                 mInvoker.requestCursorUpdates(cursorUpdateMode, displayId);
454         return CompletableFutureUtil.getResultOrFalse(value, TAG, "requestCursorUpdates()",
455                 mCancellationGroup, MAX_WAIT_TIME_MILLIS);
456     }
457 
458     @Override
459     @AnyThread
requestCursorUpdates(@ursorUpdateMode int cursorUpdateMode, @CursorUpdateFilter int cursorUpdateFilter)460     public boolean requestCursorUpdates(@CursorUpdateMode int cursorUpdateMode,
461             @CursorUpdateFilter int cursorUpdateFilter) {
462         if (mCancellationGroup.isCanceled()) {
463             return false;
464         }
465 
466         final InputMethodServiceInternal ims = mImsInternal.getAndWarnIfNull();
467         if (ims == null) {
468             return false;
469         }
470 
471         final int displayId = ims.getContext().getDisplayId();
472         final CompletableFuture<Boolean> value =
473                 mInvoker.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter, displayId);
474         return CompletableFutureUtil.getResultOrFalse(value, TAG, "requestCursorUpdates()",
475                 mCancellationGroup, MAX_WAIT_TIME_MILLIS);
476     }
477 
478     @AnyThread
requestTextBoundsInfo( @onNull RectF bounds, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<TextBoundsInfoResult> consumer)479     public void requestTextBoundsInfo(
480             @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
481             @NonNull Consumer<TextBoundsInfoResult> consumer) {
482         mInvoker.requestTextBoundsInfo(bounds, executor, consumer);
483     }
484 
485     @AnyThread
getHandler()486     public Handler getHandler() {
487         // Nothing should happen when called from input method.
488         return null;
489     }
490 
491     @AnyThread
closeConnection()492     public void closeConnection() {
493         // Nothing should happen when called from input method.
494     }
495 
496     @AnyThread
commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)497     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
498         if (mCancellationGroup.isCanceled()) {
499             return false;
500         }
501 
502         if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
503             final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull();
504             if (imsInternal == null) {
505                 return false;
506             }
507             imsInternal.exposeContent(inputContentInfo, this);
508         }
509 
510         final CompletableFuture<Boolean> value =
511                 mInvoker.commitContent(inputContentInfo, flags, opts);
512         return CompletableFutureUtil.getResultOrFalse(
513                 value, TAG, "commitContent()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
514     }
515 
516     /**
517      * See {@link InputConnection#setImeConsumesInput(boolean)}.
518      */
519     @AnyThread
setImeConsumesInput(boolean imeConsumesInput)520     public boolean setImeConsumesInput(boolean imeConsumesInput) {
521         return mInvoker.setImeConsumesInput(imeConsumesInput);
522     }
523 
524     /** See {@link InputConnection#replaceText(int, int, CharSequence, int, TextAttribute)}. */
525     @AnyThread
replaceText( int start, int end, @NonNull CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)526     public boolean replaceText(
527             int start,
528             int end,
529             @NonNull CharSequence text,
530             int newCursorPosition,
531             @Nullable TextAttribute textAttribute) {
532         return mInvoker.replaceText(start, end, text, newCursorPosition, textAttribute);
533     }
534 
535     @AnyThread
536     @Override
toString()537     public String toString() {
538         return "RemoteInputConnection{idHash=#"
539                 + Integer.toHexString(System.identityHashCode(this)) + "}";
540     }
541 }
542