EditMessage.java

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