InputConnectionWrapper.java revision da996f390e17e16f2dfa60e972e7ebc4f868f37e
1package com.android.internal.view;
2
3import com.android.internal.view.IInputContext;
4
5import android.os.Bundle;
6import android.os.RemoteException;
7import android.os.SystemClock;
8import android.util.Log;
9import android.view.KeyEvent;
10import android.view.inputmethod.CompletionInfo;
11import android.view.inputmethod.ExtractedText;
12import android.view.inputmethod.ExtractedTextRequest;
13import android.view.inputmethod.InputConnection;
14
15public class InputConnectionWrapper implements InputConnection {
16    private static final int MAX_WAIT_TIME_MILLIS = 2000;
17    private final IInputContext mIInputContext;
18
19    static class InputContextCallback extends IInputContextCallback.Stub {
20        private static final String TAG = "InputConnectionWrapper.ICC";
21        public int mSeq;
22        public boolean mHaveValue;
23        public CharSequence mTextBeforeCursor;
24        public CharSequence mTextAfterCursor;
25        public ExtractedText mExtractedText;
26        public int mCursorCapsMode;
27
28        // A 'pool' of one InputContextCallback.  Each ICW request will attempt to gain
29        // exclusive access to this object.
30        private static InputContextCallback sInstance = new InputContextCallback();
31        private static int sSequenceNumber = 1;
32
33        /**
34         * Returns an InputContextCallback object that is guaranteed not to be in use by
35         * any other thread.  The returned object's 'have value' flag is cleared and its expected
36         * sequence number is set to a new integer.  We use a sequence number so that replies that
37         * occur after a timeout has expired are not interpreted as replies to a later request.
38         */
39        private static InputContextCallback getInstance() {
40            synchronized (InputContextCallback.class) {
41                // Return sInstance if it's non-null, otherwise construct a new callback
42                InputContextCallback callback;
43                if (sInstance != null) {
44                    callback = sInstance;
45                    sInstance = null;
46
47                    // Reset the callback
48                    callback.mHaveValue = false;
49                } else {
50                    callback = new InputContextCallback();
51                }
52
53                // Set the sequence number
54                callback.mSeq = sSequenceNumber++;
55                return callback;
56            }
57        }
58
59        /**
60         * Makes the given InputContextCallback available for use in the future.
61         */
62        private void dispose() {
63            synchronized (InputContextCallback.class) {
64                // If sInstance is non-null, just let this object be garbage-collected
65                if (sInstance == null) {
66                    // Allow any objects being held to be gc'ed
67                    mTextAfterCursor = null;
68                    mTextBeforeCursor = null;
69                    mExtractedText = null;
70                    sInstance = this;
71                }
72            }
73        }
74
75        public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
76            synchronized (this) {
77                if (seq == mSeq) {
78                    mTextBeforeCursor = textBeforeCursor;
79                    mHaveValue = true;
80                    notifyAll();
81                } else {
82                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
83                            + ") in setTextBeforeCursor, ignoring.");
84                }
85            }
86        }
87
88        public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
89            synchronized (this) {
90                if (seq == mSeq) {
91                    mTextAfterCursor = textAfterCursor;
92                    mHaveValue = true;
93                    notifyAll();
94                } else {
95                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
96                            + ") in setTextAfterCursor, ignoring.");
97                }
98            }
99        }
100
101        public void setCursorCapsMode(int capsMode, int seq) {
102            synchronized (this) {
103                if (seq == mSeq) {
104                    mCursorCapsMode = capsMode;
105                    mHaveValue = true;
106                    notifyAll();
107                } else {
108                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
109                            + ") in setCursorCapsMode, ignoring.");
110                }
111            }
112        }
113
114        public void setExtractedText(ExtractedText extractedText, int seq) {
115            synchronized (this) {
116                if (seq == mSeq) {
117                    mExtractedText = extractedText;
118                    mHaveValue = true;
119                    notifyAll();
120                } else {
121                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
122                            + ") in setExtractedText, ignoring.");
123                }
124            }
125        }
126
127        /**
128         * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
129         *
130         * <p>The caller must be synchronized on this callback object.
131         */
132        void waitForResultLocked() {
133            long startTime = SystemClock.uptimeMillis();
134            long endTime = startTime + MAX_WAIT_TIME_MILLIS;
135
136            while (!mHaveValue) {
137                long remainingTime = endTime - SystemClock.uptimeMillis();
138                if (remainingTime <= 0) {
139                    Log.w(TAG, "Timed out waiting on IInputContextCallback");
140                    return;
141                }
142                try {
143                    wait(remainingTime);
144                } catch (InterruptedException e) {
145                }
146            }
147        }
148    }
149
150    public InputConnectionWrapper(IInputContext inputContext) {
151        mIInputContext = inputContext;
152    }
153
154    public CharSequence getTextAfterCursor(int length, int flags) {
155        CharSequence value = null;
156        try {
157            InputContextCallback callback = InputContextCallback.getInstance();
158            mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
159            synchronized (callback) {
160                callback.waitForResultLocked();
161                if (callback.mHaveValue) {
162                    value = callback.mTextAfterCursor;
163                }
164            }
165            callback.dispose();
166        } catch (RemoteException e) {
167            return null;
168        }
169        return value;
170    }
171
172    public CharSequence getTextBeforeCursor(int length, int flags) {
173        CharSequence value = null;
174        try {
175            InputContextCallback callback = InputContextCallback.getInstance();
176            mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
177            synchronized (callback) {
178                callback.waitForResultLocked();
179                if (callback.mHaveValue) {
180                    value = callback.mTextBeforeCursor;
181                }
182            }
183            callback.dispose();
184        } catch (RemoteException e) {
185            return null;
186        }
187        return value;
188    }
189
190    public int getCursorCapsMode(int reqModes) {
191        int value = 0;
192        try {
193            InputContextCallback callback = InputContextCallback.getInstance();
194            mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
195            synchronized (callback) {
196                callback.waitForResultLocked();
197                if (callback.mHaveValue) {
198                    value = callback.mCursorCapsMode;
199                }
200            }
201            callback.dispose();
202        } catch (RemoteException e) {
203            return 0;
204        }
205        return value;
206    }
207
208    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
209        ExtractedText value = null;
210        try {
211            InputContextCallback callback = InputContextCallback.getInstance();
212            mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
213            synchronized (callback) {
214                callback.waitForResultLocked();
215                if (callback.mHaveValue) {
216                    value = callback.mExtractedText;
217                }
218            }
219            callback.dispose();
220        } catch (RemoteException e) {
221            return null;
222        }
223        return value;
224    }
225
226    public boolean commitText(CharSequence text, int newCursorPosition) {
227        try {
228            mIInputContext.commitText(text, newCursorPosition);
229            return true;
230        } catch (RemoteException e) {
231            return false;
232        }
233    }
234
235    public boolean commitCompletion(CompletionInfo text) {
236        try {
237            mIInputContext.commitCompletion(text);
238            return true;
239        } catch (RemoteException e) {
240            return false;
241        }
242    }
243
244    public boolean setSelection(int start, int end) {
245        try {
246            mIInputContext.setSelection(start, end);
247            return true;
248        } catch (RemoteException e) {
249            return false;
250        }
251    }
252
253    public boolean performContextMenuAction(int id) {
254        try {
255            mIInputContext.performContextMenuAction(id);
256            return true;
257        } catch (RemoteException e) {
258            return false;
259        }
260    }
261
262    public boolean setComposingText(CharSequence text, int newCursorPosition) {
263        try {
264            mIInputContext.setComposingText(text, newCursorPosition);
265            return true;
266        } catch (RemoteException e) {
267            return false;
268        }
269    }
270
271    public boolean finishComposingText() {
272        try {
273            mIInputContext.finishComposingText();
274            return true;
275        } catch (RemoteException e) {
276            return false;
277        }
278    }
279
280    public boolean beginBatchEdit() {
281        try {
282            mIInputContext.beginBatchEdit();
283            return true;
284        } catch (RemoteException e) {
285            return false;
286        }
287    }
288
289    public boolean endBatchEdit() {
290        try {
291            mIInputContext.endBatchEdit();
292            return true;
293        } catch (RemoteException e) {
294            return false;
295        }
296    }
297
298    public boolean sendKeyEvent(KeyEvent event) {
299        try {
300            mIInputContext.sendKeyEvent(event);
301            return true;
302        } catch (RemoteException e) {
303            return false;
304        }
305    }
306
307    public boolean clearMetaKeyStates(int states) {
308        try {
309            mIInputContext.clearMetaKeyStates(states);
310            return true;
311        } catch (RemoteException e) {
312            return false;
313        }
314    }
315
316    public boolean deleteSurroundingText(int leftLength, int rightLength) {
317        try {
318            mIInputContext.deleteSurroundingText(leftLength, rightLength);
319            return true;
320        } catch (RemoteException e) {
321            return false;
322        }
323    }
324
325    public boolean reportFullscreenMode(boolean enabled) {
326        try {
327            mIInputContext.reportFullscreenMode(enabled);
328            return true;
329        } catch (RemoteException e) {
330            return false;
331        }
332    }
333
334    public boolean performPrivateCommand(String action, Bundle data) {
335        try {
336            mIInputContext.performPrivateCommand(action, data);
337            return true;
338        } catch (RemoteException e) {
339            return false;
340        }
341    }
342}
343