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