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