/*
* Kontalk Android client
* Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kontalk.ui.view;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;
import com.rockerhieu.emojicon.EmojiconsView;
import com.rockerhieu.emojicon.OnEmojiconClickedListener;
import com.rockerhieu.emojicon.emoji.Emojicon;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.os.Vibrator;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.kontalk.Log;
import org.kontalk.R;
import org.kontalk.message.AudioComponent;
import org.kontalk.ui.AudioDialog;
import org.kontalk.ui.ComposeMessage;
import org.kontalk.util.MediaStorage;
import org.kontalk.util.Preferences;
import org.kontalk.util.SystemUtils;
/**
* The composer bar.
* @author Daniele Ricci
* @author Andrea Cappelli
*/
public class ComposerBar extends RelativeLayout implements
EmojiconsView.OnEmojiconBackspaceClickedListener, OnEmojiconClickedListener {
private static final String TAG = ComposeMessage.TAG;
private static final int MIN_RECORDING_TIME = 900;
static final long MAX_RECORDING_TIME = TimeUnit.MINUTES.toMillis(2);
private static final int AUDIO_RECORD_VIBRATION = 20;
private static final int AUDIO_RECORD_ANIMATION = 300;
private static final String MAX_RECORDING_TIME_TEXT = DateUtils
.formatElapsedTime(MAX_RECORDING_TIME / 1000);
private Context mContext;
// for the text entry
private boolean mSendEnabled = true;
private EditText mTextEntry;
private View mSendButton;
private ComposerListener mListener;
private TextWatcher mChatStateListener;
private boolean mEnterSend;
/** Used during audio recording to restore focus status of the text entry. */
private boolean mTextEntryFocus;
private boolean mComposeSent;
// for Emoji drawer
private ImageButton mEmojiButton;
private EmojiconsView mEmojiView;
private boolean mEmojiVisible;
KeyboardAwareRelativeLayout mRootView;
private WindowManager.LayoutParams mWindowLayoutParams;
// for PTT message
Handler mHandler;
private Runnable mMediaPlayerUpdater;
View mAudioButton;
View mRecordLayout;
View mSlideText;
private float mDraggingX = -1;
private float mDistMove;
private boolean mIsRecordingAudio;
private TextView mRecordText;
private File mRecordFile;
private MediaRecorder mRecord;
long startTime;
long elapsedTime;
private boolean mCheckMove;
private int mOrientation;
private Vibrator mVibrator;
// initialized in onCreate
int mMoveThreshold;
private int mMoveOffset;
private int mMoveOffset2;
public ComposerBar(Context context) {
super(context);
init(context);
}
public ComposerBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ComposerBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ComposerBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
mContext = context;
mHandler = new Handler();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTextEntry = (EditText) findViewById(R.id.text_editor);
// enter key flag
int inputTypeFlags;
String enterKeyMode = Preferences.getEnterKeyMode(mContext);
if ("newline".equals(enterKeyMode)) {
inputTypeFlags = mTextEntry.getInputType() | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE;
}
else if ("newline_send".equals(enterKeyMode)) {
inputTypeFlags = (mTextEntry.getInputType() & ~InputType.TYPE_TEXT_FLAG_MULTI_LINE) |
InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE;
mTextEntry.setImeOptions(EditorInfo.IME_ACTION_SEND);
mTextEntry.setInputType(inputTypeFlags);
mEnterSend = true;
}
else {
inputTypeFlags = mTextEntry.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE;
}
mTextEntry.setInputType(inputTypeFlags);
mTextEntry.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
// enable the send button if there is something to send
boolean textPresent = s.length() > 0;
if (mAudioButton != null) {
mAudioButton.setVisibility(textPresent ? View.INVISIBLE : View.VISIBLE);
mSendButton.setVisibility(textPresent ? View.VISIBLE : View.INVISIBLE);
}
// audio button should already be disabled if needed
mSendButton.setEnabled(textPresent && mSendEnabled);
if (mListener != null)
mListener.textChanged(s);
// convert ascii to emojis if preference set
/* FIXME doesn't work yet because of issues with Emojicon
if (Preferences.getEmojiConverter(mContext)) {
mTextEntry.removeTextChangedListener(this);
MessageUtils.convertSmileys(s);
mTextEntry.addTextChangedListener(this);
}
*/
}
});
mTextEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND && mSendEnabled) {
if (!mEnterSend) {
InputMethodManager imm = (InputMethodManager) mContext
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(v.getApplicationWindowToken(), 0);
}
submitSend();
return true;
}
return false;
}
});
mChatStateListener = new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mSendEnabled && Preferences.getSendTyping(mContext)) {
// send typing notification if necessary
if (!mComposeSent && mListener.sendTyping()) {
mComposeSent = true;
}
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
};
mTextEntry.addTextChangedListener(mChatStateListener);
mTextEntry.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (isEmojiVisible())
hideEmojiDrawer(false);
}
});
mTextEntry.setOnFocusChangeListener(new View.OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus && isEmojiVisible())
hideEmojiDrawer(false);
}
});
mSendButton = findViewById(R.id.send_button);
mSendButton.setEnabled(mTextEntry.length() > 0);
mSendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
submitSend();
}
});
if (AudioDialog.isSupported(mContext)) {
mAudioButton = findViewById(R.id.audio_send_button);
if (mTextEntry.length() <= 0) {
mSendButton.setVisibility(View.INVISIBLE);
mAudioButton.setVisibility(View.VISIBLE);
}
mSlideText = findViewById(R.id.slide_text);
mRecordText = (TextView) findViewById(R.id.recording_time);
int screenWidth = SystemUtils.getDisplaySize(mContext).x;
// position of "slide to cancel" label (actually left margin; screen dependent)
mMoveThreshold = screenWidth/8;
// these two are used to determine how much to drag in order to cancel recording
mMoveOffset = (int) (screenWidth/4.5);
mMoveOffset2 = (int) (screenWidth/7.5);
mDistMove = mMoveOffset;
mRecordLayout = findViewById(R.id.record_layout);
mAudioButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
mOrientation = SystemUtils.getDisplayRotation(mContext);
mCheckMove = false;
mDraggingX = -1;
startRecording();
animateRecordFrame();
mAudioButton.getParent().requestDisallowInterceptTouchEvent(true);
}
else if ((motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) &&
!mCheckMove && mIsRecordingAudio) {
if (mOrientation == SystemUtils.getDisplayRotation(mContext)) {
mDraggingX = -1;
stopRecording(motionEvent.getAction() == MotionEvent.ACTION_UP);
animateRecordFrame();
}
}
else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE && mIsRecordingAudio) {
float x = motionEvent.getX();
if (x < -mDistMove) {
mCheckMove = true;
stopRecording(false);
animateRecordFrame();
}
float currentX = ViewHelper.getX(mAudioButton);
x = x + currentX;
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mSlideText.getLayoutParams();
if (mDraggingX != -1) {
float dist = (x - mDraggingX);
params.leftMargin = mMoveThreshold + (int) dist;
mSlideText.setLayoutParams(params);
float alpha = 1.0f + dist / mDistMove;
if (alpha > 1) {
alpha = 1;
}
else if (alpha < 0) {
alpha = 0;
}
ViewHelper.setAlpha(mSlideText, alpha);
}
if (x <= currentX + mSlideText.getWidth() + mMoveThreshold) {
if (mDraggingX == -1) {
mDraggingX = x;
mDistMove = (mRecordLayout.getMeasuredWidth() - mSlideText.getMeasuredWidth() - mMoveOffset2) / 2.0f;
if (mDistMove <= 0) {
mDistMove = mMoveOffset;
}
else if (mDistMove > mMoveOffset) {
mDistMove = mMoveOffset;
}
}
}
if (params.leftMargin > mMoveThreshold) {
params.leftMargin = mMoveThreshold;
mSlideText.setLayoutParams(params);
ViewHelper.setAlpha(mSlideText, 1);
mDraggingX = -1;
}
}
view.onTouchEvent(motionEvent);
return true;
}
});
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
}
mEmojiButton = (ImageButton) findViewById(R.id.emoji_button);
mEmojiButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleEmojiDrawer();
}
});
doSetSendEnabled();
}
public void onPause() {
if (mIsRecordingAudio) {
abortRecording();
}
}
public void setSendEnabled(boolean enabled) {
mSendEnabled = enabled;
doSetSendEnabled();
}
private void doSetSendEnabled() {
mSendButton.setEnabled(mSendEnabled);
if (mAudioButton != null)
mAudioButton.setEnabled(mSendEnabled);
}
public void setRootView(View rootView) {
if (!(rootView instanceof KeyboardAwareRelativeLayout)) {
rootView = rootView.findViewById(R.id.root_view);
}
mRootView = (KeyboardAwareRelativeLayout) rootView;
// this will handle closing of keyboard while emoji drawer is open
mRootView.setOnKeyboardShownListener(new KeyboardAwareRelativeLayout.OnKeyboardShownListener() {
@Override
public void onKeyboardShown(boolean visible) {
if (!visible && mRootView.getPaddingBottom() == 0 && isEmojiVisible()) {
hideEmojiDrawer(false);
}
}
});
}
public void forceHideKeyboard() {
InputMethodManager imm = (InputMethodManager) mContext
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mTextEntry.getApplicationWindowToken(), 0);
}
public void onSaveInstanceState(Bundle out) {
// TODO
}
private void disableTextEntry() {
mTextEntryFocus = mTextEntry.hasFocus();
mTextEntry.setFocusable(false);
mTextEntry.setFocusableInTouchMode(false);
}
private void enableTextEntry() {
mTextEntry.setFocusable(true);
mTextEntry.setFocusableInTouchMode(true);
if (mTextEntryFocus)
mTextEntry.requestFocus();
}
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return mTextEntry.requestFocus(direction, previouslyFocusedRect);
}
void animateRecordFrame() {
int screenWidth = SystemUtils.getDisplaySize(mContext).x;
if (mIsRecordingAudio) {
mRecordLayout.setVisibility(View.VISIBLE);
setRecordText(0);
FrameLayout.LayoutParams params =
(FrameLayout.LayoutParams) mSlideText.getLayoutParams();
params.leftMargin = mMoveThreshold;
mSlideText.setLayoutParams(params);
ViewHelper.setAlpha(mSlideText, 1);
ViewHelper.setX(mRecordLayout, screenWidth);
ViewPropertyAnimator.animate(mRecordLayout)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
ViewHelper.setX(mRecordLayout, 0);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
})
.setDuration(AUDIO_RECORD_ANIMATION)
.translationX(0)
.start();
}
else {
ViewPropertyAnimator.animate(mRecordLayout)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
FrameLayout.LayoutParams params =
(FrameLayout.LayoutParams) mSlideText.getLayoutParams();
params.leftMargin = mMoveThreshold;
mSlideText.setLayoutParams(params);
ViewHelper.setAlpha(mSlideText, 1);
mRecordLayout.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
})
.setDuration(AUDIO_RECORD_ANIMATION)
.translationX(screenWidth)
.start();
}
}
private void startRecording() {
// ask parent to stop all sounds
mListener.stopAllSounds();
try {
mRecordFile = MediaStorage.getOutgoingAudioFile();
}
catch (IOException e) {
Log.e(TAG, "error creating audio file", e);
Toast.makeText(mContext, R.string.err_audio_record_writing,
Toast.LENGTH_LONG).show();
return;
}
mRecord = new MediaRecorder();
try {
mRecord.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecord.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecord.setOutputFile(mRecordFile.getAbsolutePath());
mRecord.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mVibrator.vibrate(AUDIO_RECORD_VIBRATION);
startTimer();
mRecord.prepare();
// Start recording
mRecord.start();
mIsRecordingAudio = true;
lockScreen();
disableTextEntry();
}
catch (IllegalStateException e) {
Log.e(TAG, "error starting audio recording:", e);
}
catch (IOException e) {
Log.e(TAG, "error writing on external storage:", e);
Toast.makeText(mContext, R.string.err_audio_record_writing,
Toast.LENGTH_LONG).show();
}
catch (RuntimeException e) {
Log.e(TAG, "error starting audio recording:", e);
Toast.makeText(mContext, R.string.err_audio_record,
Toast.LENGTH_LONG).show();
}
}
void stopRecording(boolean send) {
mIsRecordingAudio = false;
unlockScreen();
enableTextEntry();
mVibrator.vibrate(AUDIO_RECORD_VIBRATION);
if (mMediaPlayerUpdater != null)
mHandler.removeCallbacks(mMediaPlayerUpdater);
boolean minDuration = (elapsedTime > MIN_RECORDING_TIME);
boolean canSend = send && minDuration;
// reset elapsed recording time
elapsedTime = 0;
try {
if (mRecord != null) {
mRecord.stop();
if (canSend) {
mListener.sendBinaryMessage(Uri.fromFile(mRecordFile),
AudioDialog.DEFAULT_MIME, false, AudioComponent.class);
}
else if (send) {
Toast.makeText(mContext, R.string.hint_ptt,
Toast.LENGTH_LONG).show();
}
}
}
catch (IllegalStateException e) {
Log.w(TAG, "error stopping recording", e);
canSend = false;
}
catch (RuntimeException e) {
if (send) {
int msgId;
if (!minDuration) {
msgId = R.string.hint_ptt;
}
else {
Log.w(TAG, "no audio data received", e);
msgId = R.string.err_audio_record_noaudio;
}
Toast.makeText(mContext, msgId, Toast.LENGTH_LONG).show();
}
canSend = false;
}
finally {
if (mRecord != null) {
mRecord.reset();
mRecord.release();
}
if (!canSend && mRecordFile != null)
mRecordFile.delete();
}
}
/** Stops push-to-talk recording immediately. */
private void abortRecording() {
// this will prevent the touch event to be processed
mCheckMove = true;
// stop the actual recording without sending
stopRecording(false);
// hide the recording layout immediately
mRecordLayout.setVisibility(View.GONE);
}
private void lockScreen() {
int orientation = SystemUtils.getScreenOrientation((Activity) mContext);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2)
orientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED;
//noinspection ResourceType
((Activity) mContext).setRequestedOrientation(orientation);
SystemUtils.acquireScreenOn((Activity) mContext);
}
private void unlockScreen() {
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
SystemUtils.releaseScreenOn((Activity) mContext);
}
private void startTimer() {
startTime = SystemClock.uptimeMillis();
mMediaPlayerUpdater = new Runnable() {
@Override
public void run() {
elapsedTime = SystemClock.uptimeMillis() - startTime;
setRecordText(elapsedTime);
mHandler.postDelayed(this, 100);
if (elapsedTime >= MAX_RECORDING_TIME) {
mAudioButton.setPressed(false);
stopRecording(true);
animateRecordFrame();
}
}
};
mHandler.postDelayed(mMediaPlayerUpdater, 100);
}
void setRecordText(long millis) {
mRecordText.setText(mContext.getString(R.string.audio_duration_max,
DateUtils.formatElapsedTime(millis / 1000),
MAX_RECORDING_TIME_TEXT));
}
private void submitSend() {
mTextEntry.removeTextChangedListener(mChatStateListener);
// send message
mListener.sendTextMessage(mTextEntry.getText().toString());
// empty text
mTextEntry.setText("");
// hide softkeyboard
hideSoftKeyboard();
// reset compose sent flag
mComposeSent = false;
mTextEntry.addTextChangedListener(mChatStateListener);
// revert to keyboard if emoji panel was open
if (isEmojiVisible()) {
hideEmojiDrawer();
}
}
/** Returns true if typing message was sent. */
public boolean isComposeSent() {
return mComposeSent;
}
@Override
public void onEmojiconBackspaceClicked(View v) {
EmojiconsView.backspace(mTextEntry);
}
@Override
public void onEmojiconClicked(Emojicon emojicon) {
EmojiconsView.input(mTextEntry, emojicon);
}
public boolean isEmojiVisible() {
return mEmojiVisible;
}
private void toggleEmojiDrawer() {
// TODO animate drawer enter & exit
if (isEmojiVisible()) {
hideEmojiDrawer();
}
else {
showEmojiDrawer();
}
}
private void showEmojiDrawer() {
int keyboardHeight = mRootView.getKeyboardHeight();
mEmojiVisible = true;
if (mEmojiView == null) {
mEmojiView = (EmojiconsView) LayoutInflater
.from(mContext).inflate(R.layout.emojicons, mRootView, false);
mEmojiView.setId(R.id.emoji_drawer);
mEmojiView.setOnEmojiconBackspaceClickedListener(this);
mEmojiView.setOnEmojiconClickedListener(this);
mWindowLayoutParams = new WindowManager.LayoutParams();
mWindowLayoutParams.gravity = Gravity.BOTTOM | Gravity.LEFT;
mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
mWindowLayoutParams.token = ((Activity) mContext).getWindow().getDecorView().getWindowToken();
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
mWindowLayoutParams.height = keyboardHeight;
mWindowLayoutParams.width = SystemUtils.getDisplaySize(mContext).x;
WindowManager wm = (WindowManager) mContext.getSystemService(Activity.WINDOW_SERVICE);
try {
if (mEmojiView.getParent() != null) {
wm.removeViewImmediate(mEmojiView);
}
}
catch (Exception e) {
Log.e(TAG, "error removing emoji view", e);
}
try {
wm.addView(mEmojiView, mWindowLayoutParams);
}
catch (Exception e) {
Log.e(TAG, "error adding emoji view", e);
return;
}
if (!mRootView.isKeyboardVisible()) {
mRootView.setPadding(0, 0, 0, keyboardHeight);
// TODO mEmojiButton.setImageResource(R.drawable.ic_msg_panel_hide);
}
mEmojiButton.setImageResource(R.drawable.ic_keyboard);
}
private void hideEmojiDrawer() {
hideEmojiDrawer(true);
}
public void hideEmojiDrawer(boolean showKeyboard) {
if (showKeyboard) {
InputMethodManager input = (InputMethodManager) mContext
.getSystemService(Context.INPUT_METHOD_SERVICE);
input.showSoftInput(mTextEntry, 0);
}
if (mEmojiView != null && mEmojiView.getParent() != null) {
WindowManager wm = (WindowManager) mContext
.getSystemService(Context.WINDOW_SERVICE);
wm.removeViewImmediate(mEmojiView);
}
mEmojiButton.setImageResource(R.drawable.ic_emoji);
mRootView.setPadding(0, 0, 0, 0);
mEmojiVisible = false;
}
public void setComposerListener(ComposerListener listener) {
mListener = listener;
}
public void onKeyboardStateChanged(boolean isKeyboardOpen) {
if (isKeyboardOpen) {
mTextEntry.setFocusableInTouchMode(true);
mTextEntry.setHint(R.string.hint_type_to_compose);
}
else {
mTextEntry.setFocusableInTouchMode(false);
mTextEntry.setHint(R.string.hint_open_kbd_to_compose);
}
}
public void restoreText(CharSequence text) {
if (text != null) {
mTextEntry.removeTextChangedListener(mChatStateListener);
// restore text (if any and only if user hasn't inserted text)
if (mTextEntry.getText().length() == 0) {
mTextEntry.setText(text);
// move cursor to end
mTextEntry.setSelection(mTextEntry.getText().length());
}
mTextEntry.addTextChangedListener(mChatStateListener);
}
}
public void setText(CharSequence text) {
mTextEntry.setText(text);
}
public CharSequence getText() {
return mTextEntry.getText();
}
private void hideSoftKeyboard() {
InputMethodManager imm = (InputMethodManager) mContext
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mTextEntry.getWindowToken(),
InputMethodManager.HIDE_IMPLICIT_ONLY);
}
public void resetCompose() {
mComposeSent = false;
}
public void onDestroy() {
if (mTextEntry != null) {
mTextEntry.removeTextChangedListener(mChatStateListener);
mTextEntry.setText("");
}
if (mIsRecordingAudio) {
stopRecording(false);
}
}
}