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