• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.NonNull;
20 import android.inputmethodservice.AbstractInputMethodService;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.os.RemoteException;
24 import android.os.SystemClock;
25 import android.util.Log;
26 import android.view.KeyEvent;
27 import android.view.inputmethod.CompletionInfo;
28 import android.view.inputmethod.CorrectionInfo;
29 import android.view.inputmethod.ExtractedText;
30 import android.view.inputmethod.ExtractedTextRequest;
31 import android.view.inputmethod.InputConnection;
32 import android.view.inputmethod.InputConnectionInspector;
33 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
34 import android.view.inputmethod.InputContentInfo;
35 
36 import java.lang.ref.WeakReference;
37 
38 public class InputConnectionWrapper implements InputConnection {
39     private static final int MAX_WAIT_TIME_MILLIS = 2000;
40     private final IInputContext mIInputContext;
41     @NonNull
42     private final WeakReference<AbstractInputMethodService> mInputMethodService;
43 
44     @MissingMethodFlags
45     private final int mMissingMethods;
46 
47     static class InputContextCallback extends IInputContextCallback.Stub {
48         private static final String TAG = "InputConnectionWrapper.ICC";
49         public int mSeq;
50         public boolean mHaveValue;
51         public CharSequence mTextBeforeCursor;
52         public CharSequence mTextAfterCursor;
53         public CharSequence mSelectedText;
54         public ExtractedText mExtractedText;
55         public int mCursorCapsMode;
56         public boolean mRequestUpdateCursorAnchorInfoResult;
57         public boolean mCommitContentResult;
58 
59         // A 'pool' of one InputContextCallback.  Each ICW request will attempt to gain
60         // exclusive access to this object.
61         private static InputContextCallback sInstance = new InputContextCallback();
62         private static int sSequenceNumber = 1;
63 
64         /**
65          * Returns an InputContextCallback object that is guaranteed not to be in use by
66          * any other thread.  The returned object's 'have value' flag is cleared and its expected
67          * sequence number is set to a new integer.  We use a sequence number so that replies that
68          * occur after a timeout has expired are not interpreted as replies to a later request.
69          */
getInstance()70         private static InputContextCallback getInstance() {
71             synchronized (InputContextCallback.class) {
72                 // Return sInstance if it's non-null, otherwise construct a new callback
73                 InputContextCallback callback;
74                 if (sInstance != null) {
75                     callback = sInstance;
76                     sInstance = null;
77 
78                     // Reset the callback
79                     callback.mHaveValue = false;
80                 } else {
81                     callback = new InputContextCallback();
82                 }
83 
84                 // Set the sequence number
85                 callback.mSeq = sSequenceNumber++;
86                 return callback;
87             }
88         }
89 
90         /**
91          * Makes the given InputContextCallback available for use in the future.
92          */
dispose()93         private void dispose() {
94             synchronized (InputContextCallback.class) {
95                 // If sInstance is non-null, just let this object be garbage-collected
96                 if (sInstance == null) {
97                     // Allow any objects being held to be gc'ed
98                     mTextAfterCursor = null;
99                     mTextBeforeCursor = null;
100                     mExtractedText = null;
101                     sInstance = this;
102                 }
103             }
104         }
105 
setTextBeforeCursor(CharSequence textBeforeCursor, int seq)106         public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
107             synchronized (this) {
108                 if (seq == mSeq) {
109                     mTextBeforeCursor = textBeforeCursor;
110                     mHaveValue = true;
111                     notifyAll();
112                 } else {
113                     Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
114                             + ") in setTextBeforeCursor, ignoring.");
115                 }
116             }
117         }
118 
setTextAfterCursor(CharSequence textAfterCursor, int seq)119         public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
120             synchronized (this) {
121                 if (seq == mSeq) {
122                     mTextAfterCursor = textAfterCursor;
123                     mHaveValue = true;
124                     notifyAll();
125                 } else {
126                     Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
127                             + ") in setTextAfterCursor, ignoring.");
128                 }
129             }
130         }
131 
setSelectedText(CharSequence selectedText, int seq)132         public void setSelectedText(CharSequence selectedText, int seq) {
133             synchronized (this) {
134                 if (seq == mSeq) {
135                     mSelectedText = selectedText;
136                     mHaveValue = true;
137                     notifyAll();
138                 } else {
139                     Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
140                             + ") in setSelectedText, ignoring.");
141                 }
142             }
143         }
144 
setCursorCapsMode(int capsMode, int seq)145         public void setCursorCapsMode(int capsMode, int seq) {
146             synchronized (this) {
147                 if (seq == mSeq) {
148                     mCursorCapsMode = capsMode;
149                     mHaveValue = true;
150                     notifyAll();
151                 } else {
152                     Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
153                             + ") in setCursorCapsMode, ignoring.");
154                 }
155             }
156         }
157 
setExtractedText(ExtractedText extractedText, int seq)158         public void setExtractedText(ExtractedText extractedText, int seq) {
159             synchronized (this) {
160                 if (seq == mSeq) {
161                     mExtractedText = extractedText;
162                     mHaveValue = true;
163                     notifyAll();
164                 } else {
165                     Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
166                             + ") in setExtractedText, ignoring.");
167                 }
168             }
169         }
170 
setRequestUpdateCursorAnchorInfoResult(boolean result, int seq)171         public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
172             synchronized (this) {
173                 if (seq == mSeq) {
174                     mRequestUpdateCursorAnchorInfoResult = result;
175                     mHaveValue = true;
176                     notifyAll();
177                 } else {
178                     Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
179                             + ") in setCursorAnchorInfoRequestResult, ignoring.");
180                 }
181             }
182         }
183 
setCommitContentResult(boolean result, int seq)184         public void setCommitContentResult(boolean result, int seq) {
185             synchronized (this) {
186                 if (seq == mSeq) {
187                     mCommitContentResult = result;
188                     mHaveValue = true;
189                     notifyAll();
190                 } else {
191                     Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
192                             + ") in setCommitContentResult, ignoring.");
193                 }
194             }
195         }
196 
197         /**
198          * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
199          *
200          * <p>The caller must be synchronized on this callback object.
201          */
waitForResultLocked()202         void waitForResultLocked() {
203             long startTime = SystemClock.uptimeMillis();
204             long endTime = startTime + MAX_WAIT_TIME_MILLIS;
205 
206             while (!mHaveValue) {
207                 long remainingTime = endTime - SystemClock.uptimeMillis();
208                 if (remainingTime <= 0) {
209                     Log.w(TAG, "Timed out waiting on IInputContextCallback");
210                     return;
211                 }
212                 try {
213                     wait(remainingTime);
214                 } catch (InterruptedException e) {
215                 }
216             }
217         }
218     }
219 
InputConnectionWrapper( @onNull WeakReference<AbstractInputMethodService> inputMethodService, IInputContext inputContext, @MissingMethodFlags final int missingMethods)220     public InputConnectionWrapper(
221             @NonNull WeakReference<AbstractInputMethodService> inputMethodService,
222             IInputContext inputContext, @MissingMethodFlags final int missingMethods) {
223         mInputMethodService = inputMethodService;
224         mIInputContext = inputContext;
225         mMissingMethods = missingMethods;
226     }
227 
getTextAfterCursor(int length, int flags)228     public CharSequence getTextAfterCursor(int length, int flags) {
229         CharSequence value = null;
230         try {
231             InputContextCallback callback = InputContextCallback.getInstance();
232             mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
233             synchronized (callback) {
234                 callback.waitForResultLocked();
235                 if (callback.mHaveValue) {
236                     value = callback.mTextAfterCursor;
237                 }
238             }
239             callback.dispose();
240         } catch (RemoteException e) {
241             return null;
242         }
243         return value;
244     }
245 
getTextBeforeCursor(int length, int flags)246     public CharSequence getTextBeforeCursor(int length, int flags) {
247         CharSequence value = null;
248         try {
249             InputContextCallback callback = InputContextCallback.getInstance();
250             mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
251             synchronized (callback) {
252                 callback.waitForResultLocked();
253                 if (callback.mHaveValue) {
254                     value = callback.mTextBeforeCursor;
255                 }
256             }
257             callback.dispose();
258         } catch (RemoteException e) {
259             return null;
260         }
261         return value;
262     }
263 
getSelectedText(int flags)264     public CharSequence getSelectedText(int flags) {
265         if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
266             // This method is not implemented.
267             return null;
268         }
269         CharSequence value = null;
270         try {
271             InputContextCallback callback = InputContextCallback.getInstance();
272             mIInputContext.getSelectedText(flags, callback.mSeq, callback);
273             synchronized (callback) {
274                 callback.waitForResultLocked();
275                 if (callback.mHaveValue) {
276                     value = callback.mSelectedText;
277                 }
278             }
279             callback.dispose();
280         } catch (RemoteException e) {
281             return null;
282         }
283         return value;
284     }
285 
getCursorCapsMode(int reqModes)286     public int getCursorCapsMode(int reqModes) {
287         int value = 0;
288         try {
289             InputContextCallback callback = InputContextCallback.getInstance();
290             mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
291             synchronized (callback) {
292                 callback.waitForResultLocked();
293                 if (callback.mHaveValue) {
294                     value = callback.mCursorCapsMode;
295                 }
296             }
297             callback.dispose();
298         } catch (RemoteException e) {
299             return 0;
300         }
301         return value;
302     }
303 
getExtractedText(ExtractedTextRequest request, int flags)304     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
305         ExtractedText value = null;
306         try {
307             InputContextCallback callback = InputContextCallback.getInstance();
308             mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
309             synchronized (callback) {
310                 callback.waitForResultLocked();
311                 if (callback.mHaveValue) {
312                     value = callback.mExtractedText;
313                 }
314             }
315             callback.dispose();
316         } catch (RemoteException e) {
317             return null;
318         }
319         return value;
320     }
321 
commitText(CharSequence text, int newCursorPosition)322     public boolean commitText(CharSequence text, int newCursorPosition) {
323         try {
324             mIInputContext.commitText(text, newCursorPosition);
325             return true;
326         } catch (RemoteException e) {
327             return false;
328         }
329     }
330 
commitCompletion(CompletionInfo text)331     public boolean commitCompletion(CompletionInfo text) {
332         if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
333             // This method is not implemented.
334             return false;
335         }
336         try {
337             mIInputContext.commitCompletion(text);
338             return true;
339         } catch (RemoteException e) {
340             return false;
341         }
342     }
343 
commitCorrection(CorrectionInfo correctionInfo)344     public boolean commitCorrection(CorrectionInfo correctionInfo) {
345         try {
346             mIInputContext.commitCorrection(correctionInfo);
347             return true;
348         } catch (RemoteException e) {
349             return false;
350         }
351     }
352 
setSelection(int start, int end)353     public boolean setSelection(int start, int end) {
354         try {
355             mIInputContext.setSelection(start, end);
356             return true;
357         } catch (RemoteException e) {
358             return false;
359         }
360     }
361 
performEditorAction(int actionCode)362     public boolean performEditorAction(int actionCode) {
363         try {
364             mIInputContext.performEditorAction(actionCode);
365             return true;
366         } catch (RemoteException e) {
367             return false;
368         }
369     }
370 
performContextMenuAction(int id)371     public boolean performContextMenuAction(int id) {
372         try {
373             mIInputContext.performContextMenuAction(id);
374             return true;
375         } catch (RemoteException e) {
376             return false;
377         }
378     }
379 
setComposingRegion(int start, int end)380     public boolean setComposingRegion(int start, int end) {
381         if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
382             // This method is not implemented.
383             return false;
384         }
385         try {
386             mIInputContext.setComposingRegion(start, end);
387             return true;
388         } catch (RemoteException e) {
389             return false;
390         }
391     }
392 
setComposingText(CharSequence text, int newCursorPosition)393     public boolean setComposingText(CharSequence text, int newCursorPosition) {
394         try {
395             mIInputContext.setComposingText(text, newCursorPosition);
396             return true;
397         } catch (RemoteException e) {
398             return false;
399         }
400     }
401 
finishComposingText()402     public boolean finishComposingText() {
403         try {
404             mIInputContext.finishComposingText();
405             return true;
406         } catch (RemoteException e) {
407             return false;
408         }
409     }
410 
beginBatchEdit()411     public boolean beginBatchEdit() {
412         try {
413             mIInputContext.beginBatchEdit();
414             return true;
415         } catch (RemoteException e) {
416             return false;
417         }
418     }
419 
endBatchEdit()420     public boolean endBatchEdit() {
421         try {
422             mIInputContext.endBatchEdit();
423             return true;
424         } catch (RemoteException e) {
425             return false;
426         }
427     }
428 
sendKeyEvent(KeyEvent event)429     public boolean sendKeyEvent(KeyEvent event) {
430         try {
431             mIInputContext.sendKeyEvent(event);
432             return true;
433         } catch (RemoteException e) {
434             return false;
435         }
436     }
437 
clearMetaKeyStates(int states)438     public boolean clearMetaKeyStates(int states) {
439         try {
440             mIInputContext.clearMetaKeyStates(states);
441             return true;
442         } catch (RemoteException e) {
443             return false;
444         }
445     }
446 
deleteSurroundingText(int beforeLength, int afterLength)447     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
448         try {
449             mIInputContext.deleteSurroundingText(beforeLength, afterLength);
450             return true;
451         } catch (RemoteException e) {
452             return false;
453         }
454     }
455 
deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)456     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
457         if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
458             // This method is not implemented.
459             return false;
460         }
461         try {
462             mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
463             return true;
464         } catch (RemoteException e) {
465             return false;
466         }
467     }
468 
reportFullscreenMode(boolean enabled)469     public boolean reportFullscreenMode(boolean enabled) {
470         try {
471             mIInputContext.reportFullscreenMode(enabled);
472             return true;
473         } catch (RemoteException e) {
474             return false;
475         }
476     }
477 
performPrivateCommand(String action, Bundle data)478     public boolean performPrivateCommand(String action, Bundle data) {
479         try {
480             mIInputContext.performPrivateCommand(action, data);
481             return true;
482         } catch (RemoteException e) {
483             return false;
484         }
485     }
486 
requestCursorUpdates(int cursorUpdateMode)487     public boolean requestCursorUpdates(int cursorUpdateMode) {
488         boolean result = false;
489         if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
490             // This method is not implemented.
491             return false;
492         }
493         try {
494             InputContextCallback callback = InputContextCallback.getInstance();
495             mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
496             synchronized (callback) {
497                 callback.waitForResultLocked();
498                 if (callback.mHaveValue) {
499                     result = callback.mRequestUpdateCursorAnchorInfoResult;
500                 }
501             }
502             callback.dispose();
503         } catch (RemoteException e) {
504             return false;
505         }
506         return result;
507     }
508 
getHandler()509     public Handler getHandler() {
510         // Nothing should happen when called from input method.
511         return null;
512     }
513 
closeConnection()514     public void closeConnection() {
515         // Nothing should happen when called from input method.
516     }
517 
commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)518     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
519         boolean result = false;
520         if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
521             // This method is not implemented.
522             return false;
523         }
524         try {
525             if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
526                 final AbstractInputMethodService inputMethodService = mInputMethodService.get();
527                 if (inputMethodService == null) {
528                     // This basically should not happen, because it's the the caller of this method.
529                     return false;
530                 }
531                 inputMethodService.exposeContent(inputContentInfo, this);
532             }
533 
534             InputContextCallback callback = InputContextCallback.getInstance();
535             mIInputContext.commitContent(inputContentInfo, flags, opts, callback.mSeq, callback);
536             synchronized (callback) {
537                 callback.waitForResultLocked();
538                 if (callback.mHaveValue) {
539                     result = callback.mCommitContentResult;
540                 }
541             }
542             callback.dispose();
543         } catch (RemoteException e) {
544             return false;
545         }
546         return result;
547     }
548 
isMethodMissing(@issingMethodFlags final int methodFlag)549     private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
550         return (mMissingMethods & methodFlag) == methodFlag;
551     }
552 
553     @Override
toString()554     public String toString() {
555         return "InputConnectionWrapper{idHash=#"
556                 + Integer.toHexString(System.identityHashCode(this))
557                 + " mMissingMethods="
558                 + InputConnectionInspector.getMissingMethodFlagsAsString(mMissingMethods) + "}";
559     }
560 }
561