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(e)) {
 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        final KeyboardListener listener = this.keyboardListener;
 91        if (listener == null) {
 92            return;
 93        }
 94        this.mTypingHandler.removeCallbacks(mTypingTimeout);
 95        this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000);
 96        if (!isUserTyping && length > 0) {
 97            this.isUserTyping = true;
 98            listener.onTypingStarted();
 99        } else if (length == 0) {
100            this.isUserTyping = false;
101            listener.onTextDeleted();
102        }
103        listener.onTextChanged();
104    }
105
106    public void setKeyboardListener(KeyboardListener listener) {
107        this.keyboardListener = listener;
108        if (listener != null) {
109            this.isUserTyping = false;
110        }
111    }
112
113    @Override
114    public boolean onTextContextMenuItem(int id) {
115        if (id == android.R.id.paste) {
116            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
117                return super.onTextContextMenuItem(android.R.id.pasteAsPlainText);
118            } else {
119                Editable editable = getEditableText();
120                InputFilter[] filters = editable.getFilters();
121                InputFilter[] tempFilters = new InputFilter[filters != null ? filters.length + 1 : 1];
122                if (filters != null) {
123                    System.arraycopy(filters, 0, tempFilters, 1, filters.length);
124                }
125                tempFilters[0] = SPAN_FILTER;
126                editable.setFilters(tempFilters);
127                try {
128                    return super.onTextContextMenuItem(id);
129                } finally {
130                    editable.setFilters(filters);
131                }
132            }
133        } else {
134            return super.onTextContextMenuItem(id);
135        }
136    }
137
138    public void setRichContentListener(String[] mimeTypes, OnCommitContentListener listener) {
139        this.mimeTypes = mimeTypes;
140        this.mCommitContentListener = listener;
141    }
142
143    public void insertAsQuote(String text) {
144        text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)", "$1> ").replaceAll("\n$", "");
145        Editable editable = getEditableText();
146        int position = getSelectionEnd();
147        if (position == -1) position = editable.length();
148        if (position > 0 && editable.charAt(position - 1) != '\n') {
149            editable.insert(position++, "\n");
150        }
151        editable.insert(position, text);
152        position += text.length();
153        editable.insert(position++, "\n");
154        if (position < editable.length() && editable.charAt(position) != '\n') {
155            editable.insert(position, "\n");
156        }
157        setSelection(position);
158    }
159
160    @Override
161    public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
162        final InputConnection ic = super.onCreateInputConnection(editorInfo);
163
164        if (mimeTypes != null && mCommitContentListener != null && ic != null) {
165            EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
166            return InputConnectionCompat.createWrapper(ic, editorInfo, (inputContentInfo, flags, opts) -> EditMessage.this.mCommitContentListener.onCommitContent(inputContentInfo, flags, opts, mimeTypes));
167        } else {
168            return ic;
169        }
170    }
171
172    public void refreshIme() {
173        SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getContext());
174        final boolean usingEnterKey = p.getBoolean("display_enter_key", getResources().getBoolean(R.bool.display_enter_key));
175        final boolean enterIsSend = p.getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send));
176
177        if (usingEnterKey && enterIsSend) {
178            setInputType(getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
179            setInputType(getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
180        } else if (usingEnterKey) {
181            setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
182            setInputType(getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
183        } else {
184            setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
185            setInputType(getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE);
186        }
187    }
188
189    public interface OnCommitContentListener {
190        boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] mimeTypes);
191    }
192
193    public interface KeyboardListener {
194        boolean onEnterPressed(KeyEvent event);
195
196        void onTypingStarted();
197
198        void onTypingStopped();
199
200        void onTextDeleted();
201
202        void onTextChanged();
203
204        boolean onTabPressed(boolean repeated);
205    }
206}