1package eu.siacs.conversations.ui.widget;
2
3import android.support.text.emoji.widget.EmojiAppCompatEditText;
4import android.support.v13.view.inputmethod.EditorInfoCompat;
5import android.support.v13.view.inputmethod.InputConnectionCompat;
6import android.support.v13.view.inputmethod.InputContentInfoCompat;
7
8import android.content.Context;
9import android.os.Build;
10import android.os.Bundle;
11import android.os.Handler;
12import android.text.Editable;
13import android.text.InputFilter;
14import android.text.Spanned;
15import android.util.AttributeSet;
16import android.view.KeyEvent;
17import android.view.inputmethod.EditorInfo;
18import android.view.inputmethod.InputConnection;
19
20import eu.siacs.conversations.Config;
21
22public class EditMessage extends EmojiAppCompatEditText {
23
24 private static final InputFilter SPAN_FILTER = (source, start, end, dest, dstart, dend) -> source instanceof Spanned ? source.toString() : source;
25 protected Handler mTypingHandler = new Handler();
26 protected KeyboardListener keyboardListener;
27 private OnCommitContentListener mCommitContentListener = null;
28 private String[] mimeTypes = null;
29 private boolean isUserTyping = false;
30 protected Runnable mTypingTimeout = new Runnable() {
31 @Override
32 public void run() {
33 if (isUserTyping && keyboardListener != null) {
34 keyboardListener.onTypingStopped();
35 isUserTyping = false;
36 }
37 }
38 };
39 private boolean lastInputWasTab = false;
40
41 public EditMessage(Context context, AttributeSet attrs) {
42 super(context, attrs);
43 }
44
45 public EditMessage(Context context) {
46 super(context);
47 }
48
49 @Override
50 public boolean onKeyDown(int keyCode, KeyEvent e) {
51 if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) {
52 lastInputWasTab = false;
53 if (keyboardListener != null && keyboardListener.onEnterPressed()) {
54 return true;
55 }
56 } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) {
57 if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) {
58 lastInputWasTab = true;
59 return true;
60 }
61 } else {
62 lastInputWasTab = false;
63 }
64 return super.onKeyDown(keyCode, e);
65 }
66
67 @Override
68 public int getAutofillType() {
69 return AUTOFILL_TYPE_NONE;
70 }
71
72 @Override
73 public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
74 super.onTextChanged(text, start, lengthBefore, lengthAfter);
75 lastInputWasTab = false;
76 if (this.mTypingHandler != null && this.keyboardListener != null) {
77 this.mTypingHandler.removeCallbacks(mTypingTimeout);
78 this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000);
79 final int length = text.length();
80 if (!isUserTyping && length > 0) {
81 this.isUserTyping = true;
82 this.keyboardListener.onTypingStarted();
83 } else if (length == 0) {
84 this.isUserTyping = false;
85 this.keyboardListener.onTextDeleted();
86 }
87 this.keyboardListener.onTextChanged();
88 }
89 }
90
91 public void setKeyboardListener(KeyboardListener listener) {
92 this.keyboardListener = listener;
93 if (listener != null) {
94 this.isUserTyping = false;
95 }
96 }
97
98 @Override
99 public boolean onTextContextMenuItem(int id) {
100 if (id == android.R.id.paste) {
101 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
102 return super.onTextContextMenuItem(android.R.id.pasteAsPlainText);
103 } else {
104 Editable editable = getEditableText();
105 InputFilter[] filters = editable.getFilters();
106 InputFilter[] tempFilters = new InputFilter[filters != null ? filters.length + 1 : 1];
107 if (filters != null) {
108 System.arraycopy(filters, 0, tempFilters, 1, filters.length);
109 }
110 tempFilters[0] = SPAN_FILTER;
111 editable.setFilters(tempFilters);
112 try {
113 return super.onTextContextMenuItem(id);
114 } finally {
115 editable.setFilters(filters);
116 }
117 }
118 } else {
119 return super.onTextContextMenuItem(id);
120 }
121 }
122
123 public void setRichContentListener(String[] mimeTypes, OnCommitContentListener listener) {
124 this.mimeTypes = mimeTypes;
125 this.mCommitContentListener = listener;
126 }
127
128 @Override
129 public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
130 final InputConnection ic = super.onCreateInputConnection(editorInfo);
131
132 if (mimeTypes != null && mCommitContentListener != null) {
133 EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
134 return InputConnectionCompat.createWrapper(ic, editorInfo, new InputConnectionCompat.OnCommitContentListener() {
135 @Override
136 public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) {
137 return EditMessage.this.mCommitContentListener.onCommitContent(inputContentInfo, flags, opts, mimeTypes);
138 }
139 });
140 } else {
141 return ic;
142 }
143 }
144
145 public interface OnCommitContentListener {
146 boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] mimeTypes);
147 }
148
149 public interface KeyboardListener {
150 boolean onEnterPressed();
151
152 void onTypingStarted();
153
154 void onTypingStopped();
155
156 void onTextDeleted();
157
158 void onTextChanged();
159
160 boolean onTabPressed(boolean repeated);
161 }
162}