Home | History | Annotate | Download | only in view
      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.RemoteException;
     21 import android.os.SystemClock;
     22 import android.util.Log;
     23 import android.view.KeyEvent;
     24 import android.view.inputmethod.CompletionInfo;
     25 import android.view.inputmethod.CorrectionInfo;
     26 import android.view.inputmethod.ExtractedText;
     27 import android.view.inputmethod.ExtractedTextRequest;
     28 import android.view.inputmethod.InputConnection;
     29 
     30 public 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