OSDN Git Service

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