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