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