Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2007-2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.internal.widget;
     18 
     19 import android.os.Bundle;
     20 import android.text.Editable;
     21 import android.text.Spanned;
     22 import android.text.method.KeyListener;
     23 import android.text.style.SuggestionSpan;
     24 import android.util.Log;
     25 import android.view.inputmethod.BaseInputConnection;
     26 import android.view.inputmethod.CompletionInfo;
     27 import android.view.inputmethod.CorrectionInfo;
     28 import android.view.inputmethod.ExtractedText;
     29 import android.view.inputmethod.ExtractedTextRequest;
     30 import android.view.inputmethod.InputConnection;
     31 import android.widget.TextView;
     32 
     33 public class EditableInputConnection extends BaseInputConnection {
     34     private static final boolean DEBUG = false;
     35     private static final String TAG = "EditableInputConnection";
     36 
     37     private final TextView mTextView;
     38 
     39     // Keeps track of nested begin/end batch edit to ensure this connection always has a
     40     // balanced impact on its associated TextView.
     41     // A negative value means that this connection has been finished by the InputMethodManager.
     42     private int mBatchEditNesting;
     43 
     44     public EditableInputConnection(TextView textview) {
     45         super(textview, true);
     46         mTextView = textview;
     47     }
     48 
     49     @Override
     50     public Editable getEditable() {
     51         TextView tv = mTextView;
     52         if (tv != null) {
     53             return tv.getEditableText();
     54         }
     55         return null;
     56     }
     57 
     58     @Override
     59     public boolean beginBatchEdit() {
     60         synchronized(this) {
     61             if (mBatchEditNesting >= 0) {
     62                 mTextView.beginBatchEdit();
     63                 mBatchEditNesting++;
     64                 return true;
     65             }
     66         }
     67         return false;
     68     }
     69 
     70     @Override
     71     public boolean endBatchEdit() {
     72         synchronized(this) {
     73             if (mBatchEditNesting > 0) {
     74                 // When the connection is reset by the InputMethodManager and reportFinish
     75                 // is called, some endBatchEdit calls may still be asynchronously received from the
     76                 // IME. Do not take these into account, thus ensuring that this IC's final
     77                 // contribution to mTextView's nested batch edit count is zero.
     78                 mTextView.endBatchEdit();
     79                 mBatchEditNesting--;
     80                 return true;
     81             }
     82         }
     83         return false;
     84     }
     85 
     86     @Override
     87     protected void reportFinish() {
     88         super.reportFinish();
     89 
     90         synchronized(this) {
     91             while (mBatchEditNesting > 0) {
     92                 endBatchEdit();
     93             }
     94             // Will prevent any further calls to begin or endBatchEdit
     95             mBatchEditNesting = -1;
     96         }
     97     }
     98 
     99     @Override
    100     public boolean clearMetaKeyStates(int states) {
    101         final Editable content = getEditable();
    102         if (content == null) return false;
    103         KeyListener kl = mTextView.getKeyListener();
    104         if (kl != null) {
    105             try {
    106                 kl.clearMetaKeyState(mTextView, content, states);
    107             } catch (AbstractMethodError e) {
    108                 // This is an old listener that doesn't implement the
    109                 // new method.
    110             }
    111         }
    112         return true;
    113     }
    114 
    115     @Override
    116     public boolean commitCompletion(CompletionInfo text) {
    117         if (DEBUG) Log.v(TAG, "commitCompletion " + text);
    118         mTextView.beginBatchEdit();
    119         mTextView.onCommitCompletion(text);
    120         mTextView.endBatchEdit();
    121         return true;
    122     }
    123 
    124     /**
    125      * Calls the {@link TextView#onCommitCorrection} method of the associated TextView.
    126      */
    127     @Override
    128     public boolean commitCorrection(CorrectionInfo correctionInfo) {
    129         if (DEBUG) Log.v(TAG, "commitCorrection" + correctionInfo);
    130         mTextView.beginBatchEdit();
    131         mTextView.onCommitCorrection(correctionInfo);
    132         mTextView.endBatchEdit();
    133         return true;
    134     }
    135 
    136     @Override
    137     public boolean performEditorAction(int actionCode) {
    138         if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
    139         mTextView.onEditorAction(actionCode);
    140         return true;
    141     }
    142 
    143     @Override
    144     public boolean performContextMenuAction(int id) {
    145         if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
    146         mTextView.beginBatchEdit();
    147         mTextView.onTextContextMenuItem(id);
    148         mTextView.endBatchEdit();
    149         return true;
    150     }
    151 
    152     @Override
    153     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
    154         if (mTextView != null) {
    155             ExtractedText et = new ExtractedText();
    156             if (mTextView.extractText(request, et)) {
    157                 if ((flags&GET_EXTRACTED_TEXT_MONITOR) != 0) {
    158                     mTextView.setExtracting(request);
    159                 }
    160                 return et;
    161             }
    162         }
    163         return null;
    164     }
    165 
    166     @Override
    167     public boolean performPrivateCommand(String action, Bundle data) {
    168         mTextView.onPrivateIMECommand(action, data);
    169         return true;
    170     }
    171 
    172     @Override
    173     public boolean commitText(CharSequence text, int newCursorPosition) {
    174         if (mTextView == null) {
    175             return super.commitText(text, newCursorPosition);
    176         }
    177         if (text instanceof Spanned) {
    178             Spanned spanned = ((Spanned) text);
    179             SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
    180             mIMM.registerSuggestionSpansForNotification(spans);
    181         }
    182 
    183         mTextView.resetErrorChangedFlag();
    184         boolean success = super.commitText(text, newCursorPosition);
    185         mTextView.hideErrorIfUnchanged();
    186 
    187         return success;
    188     }
    189 
    190     @Override
    191     public boolean requestCursorUpdates(int cursorUpdateMode) {
    192         if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);
    193 
    194         // It is possible that any other bit is used as a valid flag in a future release.
    195         // We should reject the entire request in such a case.
    196         final int KNOWN_FLAGS_MASK = InputConnection.CURSOR_UPDATE_IMMEDIATE |
    197                 InputConnection.CURSOR_UPDATE_MONITOR;
    198         final int unknownFlags = cursorUpdateMode & ~KNOWN_FLAGS_MASK;
    199         if (unknownFlags != 0) {
    200             if (DEBUG) {
    201                 Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags." +
    202                         " cursorUpdateMode=" + cursorUpdateMode +
    203                         " unknownFlags=" + unknownFlags);
    204             }
    205             return false;
    206         }
    207 
    208         if (mIMM == null) {
    209             // In this case, TYPE_CURSOR_ANCHOR_INFO is not handled.
    210             // TODO: Return some notification code rather than false to indicate method that
    211             // CursorAnchorInfo is temporarily unavailable.
    212             return false;
    213         }
    214         mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode);
    215         if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
    216             if (mTextView == null) {
    217                 // In this case, FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is silently ignored.
    218                 // TODO: Return some notification code for the input method that indicates
    219                 // FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is ignored.
    220             } else if (mTextView.isInLayout()) {
    221                 // In this case, the view hierarchy is currently undergoing a layout pass.
    222                 // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
    223                 // pass is finished.
    224             } else {
    225                 // This will schedule a layout pass of the view tree, and the layout event
    226                 // eventually triggers IMM#updateCursorAnchorInfo.
    227                 mTextView.requestLayout();
    228             }
    229         }
    230         return true;
    231     }
    232 }
    233