EditMessage.java

  1package eu.siacs.conversations.ui.widget;
  2
  3import android.content.SharedPreferences;
  4import android.preference.PreferenceManager;
  5import android.support.v13.view.inputmethod.EditorInfoCompat;
  6import android.support.v13.view.inputmethod.InputConnectionCompat;
  7import android.support.v13.view.inputmethod.InputContentInfoCompat;
  8
  9import android.content.Context;
 10import android.os.Build;
 11import android.os.Bundle;
 12import android.os.Handler;
 13import android.text.Editable;
 14import android.text.InputFilter;
 15import android.text.InputType;
 16import android.text.Spanned;
 17import android.util.AttributeSet;
 18import android.view.KeyEvent;
 19import android.view.inputmethod.EditorInfo;
 20import android.view.inputmethod.InputConnection;
 21
 22import java.util.concurrent.ExecutorService;
 23import java.util.concurrent.Executors;
 24
 25import eu.siacs.conversations.Config;
 26import eu.siacs.conversations.R;
 27
 28public class EditMessage extends EmojiWrapperEditText {
 29
 30    private static final InputFilter SPAN_FILTER = (source, start, end, dest, dstart, dend) -> source instanceof Spanned ? source.toString() : source;
 31    private final ExecutorService executor = Executors.newSingleThreadExecutor();
 32    protected Handler mTypingHandler = new Handler();
 33    protected KeyboardListener keyboardListener;
 34    private OnCommitContentListener mCommitContentListener = null;
 35    private String[] mimeTypes = null;
 36    private boolean isUserTyping = false;
 37    private final Runnable mTypingTimeout = new Runnable() {
 38        @Override
 39        public void run() {
 40            if (isUserTyping && keyboardListener != null) {
 41                keyboardListener.onTypingStopped();
 42                isUserTyping = false;
 43            }
 44        }
 45    };
 46    private boolean lastInputWasTab = false;
 47
 48    public EditMessage(Context context, AttributeSet attrs) {
 49        super(context, attrs);
 50    }
 51
 52    public EditMessage(Context context) {
 53        super(context);
 54    }
 55
 56    @Override
 57    public boolean onKeyDown(int keyCode, KeyEvent e) {
 58        if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) {
 59            lastInputWasTab = false;
 60            if (keyboardListener != null && keyboardListener.onEnterPressed()) {
 61                return true;
 62            }
 63        } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) {
 64            if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) {
 65                lastInputWasTab = true;
 66                return true;
 67            }
 68        } else {
 69            lastInputWasTab = false;
 70        }
 71        return super.onKeyDown(keyCode, e);
 72    }
 73
 74    @Override
 75    public int getAutofillType() {
 76        return AUTOFILL_TYPE_NONE;
 77    }
 78
 79
 80    @Override
 81    public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
 82        super.onTextChanged(text, start, lengthBefore, lengthAfter);
 83        lastInputWasTab = false;
 84        if (this.mTypingHandler != null && this.keyboardListener != null) {
 85            executor.execute(() -> triggerKeyboardEvents(text.length()));
 86        }
 87    }
 88
 89    private void triggerKeyboardEvents(final int length) {
 90        this.mTypingHandler.removeCallbacks(mTypingTimeout);
 91        this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000);
 92        if (!isUserTyping && length > 0) {
 93            this.isUserTyping = true;
 94            this.keyboardListener.onTypingStarted();
 95        } else if (length == 0) {
 96            this.isUserTyping = false;
 97            this.keyboardListener.onTextDeleted();
 98        }
 99        this.keyboardListener.onTextChanged();
100    }
101
102    public void setKeyboardListener(KeyboardListener listener) {
103        this.keyboardListener = listener;
104        if (listener != null) {
105            this.isUserTyping = false;
106        }
107    }
108
109    @Override
110    public boolean onTextContextMenuItem(int id) {
111        if (id == android.R.id.paste) {
112            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
113                return super.onTextContextMenuItem(android.R.id.pasteAsPlainText);
114            } else {
115                Editable editable = getEditableText();
116                InputFilter[] filters = editable.getFilters();
117                InputFilter[] tempFilters = new InputFilter[filters != null ? filters.length + 1 : 1];
118                if (filters != null) {
119                    System.arraycopy(filters, 0, tempFilters, 1, filters.length);
120                }
121                tempFilters[0] = SPAN_FILTER;
122                editable.setFilters(tempFilters);
123                try {
124                    return super.onTextContextMenuItem(id);
125                } finally {
126                    editable.setFilters(filters);
127                }
128            }
129        } else {
130            return super.onTextContextMenuItem(id);
131        }
132    }
133
134    public void setRichContentListener(String[] mimeTypes, OnCommitContentListener listener) {
135        this.mimeTypes = mimeTypes;
136        this.mCommitContentListener = listener;
137    }
138
139    public void insertAsQuote(String text) {
140        text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)", "$1> ").replaceAll("\n$", "");
141        Editable editable = getEditableText();
142        int position = getSelectionEnd();
143        if (position == -1) position = editable.length();
144        if (position > 0 && editable.charAt(position - 1) != '\n') {
145            editable.insert(position++, "\n");
146        }
147        editable.insert(position, text);
148        position += text.length();
149        editable.insert(position++, "\n");
150        if (position < editable.length() && editable.charAt(position) != '\n') {
151            editable.insert(position, "\n");
152        }
153        setSelection(position);
154    }
155
156    @Override
157    public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
158        final InputConnection ic = super.onCreateInputConnection(editorInfo);
159
160        if (mimeTypes != null && mCommitContentListener != null && ic != null) {
161            EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
162            return InputConnectionCompat.createWrapper(ic, editorInfo, (inputContentInfo, flags, opts) -> EditMessage.this.mCommitContentListener.onCommitContent(inputContentInfo, flags, opts, mimeTypes));
163        } else {
164            return ic;
165        }
166    }
167
168    public void refreshIme() {
169        SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getContext());
170        final boolean usingEnterKey = p.getBoolean("display_enter_key", getResources().getBoolean(R.bool.display_enter_key));
171        final boolean enterIsSend = p.getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send));
172
173        if (usingEnterKey && enterIsSend) {
174            setInputType(getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
175            setInputType(getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
176        } else if (usingEnterKey) {
177            setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
178            setInputType(getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
179        } else {
180            setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
181            setInputType(getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE);
182        }
183    }
184
185    public interface OnCommitContentListener {
186        boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] mimeTypes);
187    }
188
189    public interface KeyboardListener {
190        boolean onEnterPressed();
191
192        void onTypingStarted();
193
194        void onTypingStopped();
195
196        void onTextDeleted();
197
198        void onTextChanged();
199
200        boolean onTabPressed(boolean repeated);
201    }
202}