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