/*
* Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source
* Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package info.guardianproject.otr.app.im.app;
import info.guardianproject.emoji.EmojiGroup;
import info.guardianproject.emoji.EmojiManager;
import info.guardianproject.emoji.EmojiPagerAdapter;
import info.guardianproject.otr.IOtrChatSession;
import info.guardianproject.otr.app.im.IChatListener;
import info.guardianproject.otr.app.im.IChatSession;
import info.guardianproject.otr.app.im.IChatSessionManager;
import info.guardianproject.otr.app.im.IContactList;
import info.guardianproject.otr.app.im.IContactListListener;
import info.guardianproject.otr.app.im.IContactListManager;
import info.guardianproject.otr.app.im.IImConnection;
import info.guardianproject.otr.app.im.R;
import info.guardianproject.otr.app.im.app.MessageView.DeliveryState;
import info.guardianproject.otr.app.im.app.MessageView.EncryptionState;
import info.guardianproject.otr.app.im.app.adapter.ChatListenerAdapter;
import info.guardianproject.otr.app.im.engine.Address;
import info.guardianproject.otr.app.im.engine.Contact;
import info.guardianproject.otr.app.im.engine.ImConnection;
import info.guardianproject.otr.app.im.engine.ImErrorInfo;
import info.guardianproject.otr.app.im.engine.Presence;
import info.guardianproject.otr.app.im.provider.Imps;
import info.guardianproject.otr.app.im.provider.ImpsAddressUtils;
import info.guardianproject.otr.app.im.service.ImServiceConstants;
import info.guardianproject.otr.app.im.ui.RoundedAvatarDrawable;
import info.guardianproject.util.LogCleaner;
import info.guardianproject.util.SystemServices;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import net.java.otr4j.OtrPolicy;
import net.java.otr4j.session.SessionStatus;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.CursorIndexOutOfBoundsException;
import android.database.DataSetObserver;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Browser;
import android.support.v4.view.ViewPager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.gson.JsonSyntaxException;
import com.google.zxing.integration.android.IntentIntegrator;
import info.guardianproject.emoji.EmojiGroup;
import info.guardianproject.emoji.EmojiManager;
import info.guardianproject.emoji.EmojiPagerAdapter;
import info.guardianproject.otr.IOtrChatSession;
import info.guardianproject.otr.app.im.IChatListener;
import info.guardianproject.otr.app.im.IChatSession;
import info.guardianproject.otr.app.im.IChatSessionManager;
import info.guardianproject.otr.app.im.IContactList;
import info.guardianproject.otr.app.im.IContactListListener;
import info.guardianproject.otr.app.im.IContactListManager;
import info.guardianproject.otr.app.im.IImConnection;
import info.guardianproject.otr.app.im.R;
import info.guardianproject.otr.app.im.app.MessageView.DeliveryState;
import info.guardianproject.otr.app.im.app.MessageView.EncryptionState;
import info.guardianproject.otr.app.im.app.adapter.ChatListenerAdapter;
import info.guardianproject.otr.app.im.engine.Address;
import info.guardianproject.otr.app.im.engine.Contact;
import info.guardianproject.otr.app.im.engine.ImConnection;
import info.guardianproject.otr.app.im.engine.ImErrorInfo;
import info.guardianproject.otr.app.im.provider.Imps;
import info.guardianproject.otr.app.im.provider.ImpsAddressUtils;
import info.guardianproject.otr.app.im.service.ImServiceConstants;
import info.guardianproject.otr.app.im.ui.RoundedAvatarDrawable;
import info.guardianproject.util.LogCleaner;
import info.guardianproject.util.SystemServices;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import net.java.otr4j.OtrPolicy;
import net.java.otr4j.session.SessionStatus;
public class ChatView extends LinearLayout {
// This projection and index are set for the query of active chats
static final String[] CHAT_PROJECTION = { Imps.Contacts._ID, Imps.Contacts.ACCOUNT,
Imps.Contacts.PROVIDER, Imps.Contacts.USERNAME,
Imps.Contacts.NICKNAME, Imps.Contacts.TYPE,
Imps.Presence.PRESENCE_STATUS,
Imps.Chats.LAST_UNREAD_MESSAGE,
Imps.Chats._ID,
Imps.Contacts.SUBSCRIPTION_TYPE,
Imps.Contacts.SUBSCRIPTION_STATUS,
Imps.Contacts.AVATAR_DATA
};
static final int CONTACT_ID_COLUMN = 0;
static final int ACCOUNT_COLUMN = 1;
static final int PROVIDER_COLUMN = 2;
static final int USERNAME_COLUMN = 3;
static final int NICKNAME_COLUMN = 4;
static final int TYPE_COLUMN = 5;
static final int PRESENCE_STATUS_COLUMN = 6;
static final int LAST_UNREAD_MESSAGE_COLUMN = 7;
static final int CHAT_ID_COLUMN = 8;
static final int SUBSCRIPTION_TYPE_COLUMN = 9;
static final int SUBSCRIPTION_STATUS_COLUMN = 10;
static final int AVATAR_COLUMN = 11;
//static final int MIME_TYPE_COLUMN = 9;
static final String[] INVITATION_PROJECT = { Imps.Invitation._ID, Imps.Invitation.PROVIDER,
Imps.Invitation.SENDER, };
static final int INVITATION_ID_COLUMN = 0;
static final int INVITATION_PROVIDER_COLUMN = 1;
static final int INVITATION_SENDER_COLUMN = 2;
static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD);
static final StyleSpan STYLE_NORMAL = new StyleSpan(Typeface.NORMAL);
Markup mMarkup;
NewChatActivity mNewChatActivity;
ImApp mApp;
SimpleAlertHandler mHandler;
IImConnection mConn;
//private ImageView mStatusIcon;
// private TextView mTitle;
/*package*/ListView mHistory;
EditText mComposeMessage;
private ImageButton mSendButton;
private ImageButton mButtonAttach;
private View mViewAttach;
private View mStatusWarningView;
private TextView mWarningText;
private ProgressBar mProgressTransfer;
private ViewPager mEmojiPager;
// private View mActionBox;
private ImageView mDeliveryIcon;
private boolean mExpectingDelivery;
private boolean mIsSelected = false;
private SessionStatus mLastSessionStatus = null;
private boolean mIsStartingOtr = false;
private boolean mIsVerified = false;
public void setSelected (boolean isSelected)
{
mIsSelected = isSelected;
if (mIsSelected)
{
bindChat(mLastChatId);
setTitle();
updateWarningView();
mComposeMessage.requestFocus();
userActionDetected();
try
{
boolean isConnected = (mConn == null) ? false : mConn.getState() != ImConnection.SUSPENDED;
if (mLastSessionStatus == SessionStatus.PLAINTEXT && isConnected) {
boolean otrPolicyAuto = mNewChatActivity.getOtrPolicy() == OtrPolicy.OTRL_POLICY_ALWAYS
|| this.mNewChatActivity.getOtrPolicy() == OtrPolicy.OPPORTUNISTIC;
if (mCurrentChatSession == null)
mCurrentChatSession = getChatSession();
if (mCurrentChatSession == null)
return;
IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession();
if (otrChatSession != null)
{
String remoteJID = otrChatSession.getRemoteUserId();
boolean isChatSecure = (remoteJID != null && remoteJID.contains("ChatSecure"));
if (otrPolicyAuto && isChatSecure) //if set to auto, and is chatsecure, then start encryption
{
//automatically attempt to turn on OTR after 1 second
mHandler.postAtTime(new Runnable (){
public void run (){ setOTRState(true);}
},1000);
}
}
}
}
catch (RemoteException re){}
}
}
private boolean checkConnection () throws RemoteException
{
if (mConn == null)
{
mConn = mApp.createConnection(mProviderId,mAccountId);
if (mConn != null)
return false;
}
return true;
}
public void setOTRState(boolean otrEnabled) {
try {
boolean isConnected = (mConn == null) ? false : mConn.getState() != ImConnection.SUSPENDED;
if (isConnected)
{
if (mCurrentChatSession == null)
mCurrentChatSession = getChatSession();
if (mCurrentChatSession != null)
{
IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession();
if (otrChatSession != null)
{
if (otrEnabled) {
otrChatSession.startChatEncryption();
mIsStartingOtr = true;
mProgressBarOtr.setVisibility(View.VISIBLE);
// Toast.makeText(getContext(),getResources().getString(R.string.starting_otr_chat), Toast.LENGTH_LONG).show();
}
else
{
otrChatSession.stopChatEncryption();
// Toast.makeText(getContext(),getResources().getString(R.string.stopping_otr_chat), Toast.LENGTH_LONG).show();
}
}
}
}
updateWarningView();
}
catch (RemoteException e) {
Log.d(ImApp.LOG_TAG, "error getting remote activity", e);
}
}
private MessageAdapter mMessageAdapter;
private boolean isServiceUp;
private IChatSession mCurrentChatSession;
long mLastChatId=-1;
String mRemoteNickname;
String mRemoteAddress;
RoundedAvatarDrawable mRemoteAvatar = null;
int mSubscriptionType;
int mSubscriptionStatus;
long mProviderId;
long mAccountId;
long mInvitationId;
private Context mContext; // TODO
private int mPresenceStatus;
private int mViewType;
private static final int VIEW_TYPE_CHAT = 1;
private static final int VIEW_TYPE_INVITATION = 2;
private static final int VIEW_TYPE_SUBSCRIPTION = 3;
private static final long SHOW_TIME_STAMP_INTERVAL = 30 * 1000; // 15 seconds
private static final long SHOW_DELIVERY_INTERVAL = 5 * 1000; // 5 seconds
private static final long SHOW_MEDIA_DELIVERY_INTERVAL = 120 * 1000; // 2 minutes
private static final long DEFAULT_QUERY_INTERVAL = 2000;
private static final long FAST_QUERY_INTERVAL = 200;
private static final int QUERY_TOKEN = 10;
// Async QueryHandler
private final class QueryHandler extends AsyncQueryHandler {
private Cursor mLastCursor = null;
public QueryHandler(Context context) {
super(context.getContentResolver());
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor c) {
mExpectingDelivery = false;
if (c != null)
{
closeCursor ();
mLastCursor = new DeltaCursor(c);
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("onQueryComplete: cursor.count=" + mLastCursor.getCount());
}
if (mMessageAdapter != null)
mMessageAdapter.changeCursor(mLastCursor);
}
}
public void closeCursor ()
{
if (mLastCursor != null && (!mLastCursor.isClosed()))
mLastCursor.close();
}
}
private QueryHandler mQueryHandler;
public SimpleAlertHandler getHandler() {
return mHandler;
}
public int getType() {
return mViewType;
}
private class RequeryCallback implements Runnable {
public void run() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("RequeryCallback");
}
requeryCursor();
}
}
private RequeryCallback mRequeryCallback = null;
private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (!(view instanceof MessageView)) {
return;
}
URLSpan[] links = ((MessageView) view).getMessageLinks();
if (links.length > 0) {
final ArrayList<String> linkUrls = new ArrayList<String>(links.length);
for (URLSpan u : links) {
linkUrls.add(u.getURL());
}
ArrayAdapter<String> a = new ArrayAdapter<String>(mNewChatActivity,
android.R.layout.select_dialog_item, linkUrls);
AlertDialog.Builder b = new AlertDialog.Builder(mNewChatActivity);
b.setTitle(R.string.select_link_title);
b.setCancelable(true);
b.setAdapter(a, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Uri uri = Uri.parse(linkUrls.get(which));
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId);
intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, mNewChatActivity.getPackageName());
mNewChatActivity.startActivity(intent);
}
});
b.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
b.show();
}
}
};
private final static int PROMPT_FOR_DATA_TRANSFER = 9999;
private final static int SHOW_DATA_PROGRESS = 9998;
private final static int SHOW_DATA_ERROR = 9997;
private IChatListener mChatListener = new ChatListenerAdapter() {
@Override
public boolean onIncomingMessage(IChatSession ses,
info.guardianproject.otr.app.im.engine.Message msg) {
scheduleRequery(FAST_QUERY_INTERVAL);
updatePresenceDisplay();
return mIsSelected;
}
@Override
public void onContactJoined(IChatSession ses, Contact contact) {
scheduleRequery(DEFAULT_QUERY_INTERVAL);
}
@Override
public void onContactLeft(IChatSession ses, Contact contact) {
scheduleRequery(DEFAULT_QUERY_INTERVAL);
}
@Override
public void onSendMessageError(IChatSession ses,
info.guardianproject.otr.app.im.engine.Message msg, ImErrorInfo error) {
scheduleRequery(FAST_QUERY_INTERVAL);
}
@Override
public void onIncomingReceipt(IChatSession ses, String packetId) throws RemoteException {
scheduleRequery(FAST_QUERY_INTERVAL);
}
@Override
public void onStatusChanged(IChatSession ses) throws RemoteException {
scheduleRequery(DEFAULT_QUERY_INTERVAL);
updatePresenceDisplay();
};
@Override
public void onIncomingFileTransfer(String transferFrom, String transferUrl) throws RemoteException {
String[] path = transferUrl.split("/");
String sanitizedPath = SystemServices.sanitize(path[path.length - 1]);
android.os.Message message = android.os.Message.obtain(null, PROMPT_FOR_DATA_TRANSFER, (int) (mProviderId >> 32),
(int) mProviderId, -1);
message.getData().putString("from", transferFrom);
message.getData().putString("file", sanitizedPath);
mHandler.sendMessage(message);
}
@Override
public void onIncomingFileTransferProgress(String file, int percent)
throws RemoteException {
android.os.Message message = android.os.Message.obtain(null, SHOW_DATA_PROGRESS, (int) (mProviderId >> 32),
(int) mProviderId, -1);
message.getData().putString("file", file);
message.getData().putInt("progress", percent);
scheduleRequery(FAST_QUERY_INTERVAL);
mHandler.sendMessage(message);
}
@Override
public void onIncomingFileTransferError(String file, String err) throws RemoteException {
android.os.Message message = android.os.Message.obtain(null, SHOW_DATA_ERROR, (int) (mProviderId >> 32),
(int) mProviderId, -1);
message.getData().putString("file", file);
message.getData().putString("err", err);
mHandler.sendMessage(message);
}
};
private void showPromptForData (String transferFrom, String filePath)
{
AlertDialog.Builder builder = new AlertDialog.Builder(mNewChatActivity);
builder.setTitle(mContext.getString(R.string.file_transfer));
builder.setMessage(transferFrom + ' ' + mNewChatActivity.getString(R.string.wants_to_send_you_the_file)
+ " '" + filePath + "'. " + mNewChatActivity.getString(R.string.accept_transfer_));
builder.setNeutralButton(R.string.button_yes_accept_all,new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try {
mCurrentChatSession.setIncomingFileResponse(true, true);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
dialog.dismiss();
}
});
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try {
mCurrentChatSession.setIncomingFileResponse(true, false);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
dialog.dismiss();
}
});
builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
mCurrentChatSession.setIncomingFileResponse(false, false);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Do nothing
dialog.dismiss();
}
});
AlertDialog alert = builder.create();
alert.show();
}
private Runnable mUpdateChatCallback = new Runnable() {
public void run() {
// if (mCursor != null && mCursor.requery() && mCursor.moveToFirst()) {
updateChat();
// }
}
};
private IContactListListener mContactListListener = new IContactListListener.Stub() {
public void onAllContactListsLoaded() {
}
public void onContactChange(int type, IContactList list, Contact contact) {
if (contact != null && contact.getPresence() != null)
mPresenceStatus = contact.getPresence().getStatus();
}
public void onContactError(int errorType, ImErrorInfo error, String listName,
Contact contact) {
}
public void onContactsPresenceUpdate(Contact[] contacts) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("onContactsPresenceUpdate()");
}
for (Contact c : contacts) {
if (c.getAddress().getBareAddress().equals(Address.stripResource(mRemoteAddress))) {
if (c != null && c.getPresence() != null)
{
mPresenceStatus = c.getPresence().getStatus();
updatePresenceDisplay();
}
mHandler.post(mUpdateChatCallback);
scheduleRequery(DEFAULT_QUERY_INTERVAL);
break;
}
}
}
};
private boolean mIsListening;
static final void log(String msg) {
Log.d(ImApp.LOG_TAG, "<ChatView> " + msg);
}
public ChatView(Context context, AttributeSet attrs) {
super(context, attrs);
mNewChatActivity = (NewChatActivity) context;
mApp = (ImApp)mNewChatActivity.getApplication();
mHandler = new ChatViewHandler(mNewChatActivity);
mContext = context;
ThemeableActivity.setBackgroundImage(this, mNewChatActivity);
}
void registerForConnEvents() {
mApp.registerForConnEvents(mHandler);
}
void unregisterForConnEvents() {
mApp.unregisterForConnEvents(mHandler);
}
ProgressBar mProgressBarOtr;
@Override
protected void onFinishInflate() {
// mStatusIcon = (ImageView) findViewById(R.id.statusIcon);
// mDeliveryIcon = (ImageView) findViewById(R.id.deliveryIcon);
// mTitle = (TextView) findViewById(R.id.title);
mHistory = (ListView) findViewById(R.id.history);
mComposeMessage = (EditText) findViewById(R.id.composeMessage);
mSendButton = (ImageButton) findViewById(R.id.btnSend);
mHistory.setOnItemClickListener(mOnItemClickListener);
mButtonAttach = (ImageButton) findViewById(R.id.btnAttach);
mViewAttach = findViewById(R.id.attachPanel);
mStatusWarningView = findViewById(R.id.warning);
mWarningText = (TextView) findViewById(R.id.warningText);
mProgressTransfer = (ProgressBar)findViewById(R.id.progressTransfer);
// mOtrSwitch = (CompoundButton)findViewById(R.id.otrSwitch);
mProgressBarOtr = (ProgressBar)findViewById(R.id.progressBarOtr);
mButtonAttach.setOnClickListener(new OnClickListener ()
{
@Override
public void onClick(View v) {
if (mViewAttach.getVisibility() == View.GONE)
mViewAttach.setVisibility(View.VISIBLE);
else
mViewAttach.setVisibility(View.GONE);
}
});
((ImageButton) findViewById(R.id.btnAttachAudio)).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
mNewChatActivity.startAudioPicker();
}
});
((ImageButton) findViewById(R.id.btnAttachPicture)).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
mNewChatActivity.startImagePicker();
}
});
((ImageButton) findViewById(R.id.btnTakePicture)).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
mNewChatActivity.startPhotoTaker();
}
});
((ImageButton) findViewById(R.id.btnAttachFile)).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
mNewChatActivity.startFilePicker();
}
});
mHistory.setOnItemLongClickListener(new OnItemLongClickListener ()
{
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
if (arg1 instanceof MessageView)
{
String textToCopy = ((MessageView)arg1).getLastMessage();
int sdk = android.os.Build.VERSION.SDK_INT;
if(sdk < android.os.Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) mNewChatActivity.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(textToCopy); //
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) mNewChatActivity.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText("chat",textToCopy);
clipboard.setPrimaryClip(clip); //
}
Toast.makeText(mNewChatActivity, mContext.getString(R.string.toast_chat_copied_to_clipboard), Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
});
mWarningText.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showVerifyDialog();
}
});
//mOtrSwitch.setOnCheckedChangeListener(mOtrListener);
mComposeMessage.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
sendMessage();
return true;
case KeyEvent.KEYCODE_ENTER:
if (event.isAltPressed()) {
mComposeMessage.append("\n");
return true;
}
}
}
return false;
}
});
mComposeMessage.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event != null) {
if (event.isAltPressed()) {
return false;
}
}
InputMethodManager imm = (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null && imm.isActive(v)) {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
sendMessage();
return true;
}
});
// TODO: this is a hack to implement BUG #1611278, when dispatchKeyEvent() works with
// the soft keyboard, we should remove this hack.
mComposeMessage.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int before, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int after) {
//log("TextWatcher: " + s);
userActionDetected();
}
public void afterTextChanged(Editable s) {
}
});
mSendButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
sendMessage();
}
});
Button btnApproveSubscription = (Button)findViewById(R.id.btnApproveSubscription);
btnApproveSubscription.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
mNewChatActivity.approveSubscription(mProviderId, mRemoteAddress);
mHandler.postDelayed(new Runnable () { public void run () {bindChat(mLastChatId); } }, 2000);
}
});
Button btnDeclineSubscription = (Button)findViewById(R.id.btnDeclineSubscription);
btnDeclineSubscription.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
mHandler.postDelayed(new Runnable () { public void run () {
mNewChatActivity.declineSubscription(mProviderId, mRemoteAddress);
} }, 500);
}
});
/*
mActionBox = (View)findViewById(R.id.actionBox);
ImageButton btnActionBox = (ImageButton)findViewById(R.id.btnActionBox);
btnActionBox.setOnClickListener(new OnClickListener ()
{
@Override
public void onClick(View v) {
mEmojiPager.setVisibility(View.GONE);
if (mActionBox.getVisibility() == View.GONE)
mActionBox.setVisibility(View.VISIBLE);
else
mActionBox.setVisibility(View.GONE);
}
});
View btnEndChat = findViewById(R.id.btnEndChat);
btnEndChat.setOnClickListener(new OnClickListener ()
{
@Override
public void onClick(View v) {
ChatView.this.closeChatSession();
}
});
View btnProfile = findViewById(R.id.btnProfile);
btnProfile.setOnClickListener(new OnClickListener ()
{
@Override
public void onClick(View v) {
viewProfile();
}
});
View btnSharePicture = findViewById(R.id.btnSendPicture);
btnSharePicture.setOnClickListener(new OnClickListener ()
{
@Override
public void onClick(View v) {
if (mLastSessionStatus != null && mLastSessionStatus == SessionStatus.ENCRYPTED)
{
mNewChatActivity.startImagePicker();
}
else
{
mHandler.showServiceErrorAlert(getContext().getString(R.string.please_enable_chat_encryption_to_share_files));
}
}
});
View btnShareFile = findViewById(R.id.btnSendFile);
btnShareFile.setOnClickListener(new OnClickListener ()
{
@Override
public void onClick(View v) {
if (mLastSessionStatus != null && mLastSessionStatus == SessionStatus.ENCRYPTED)
{
mNewChatActivity.startFilePicker();
}
else
{
mHandler.showServiceErrorAlert(getContext().getString(R.string.please_enable_chat_encryption_to_share_files));
}
}
});
*/
initEmoji();
mMessageAdapter = new MessageAdapter(mNewChatActivity, null);
mHistory.setAdapter(mMessageAdapter);
}
private static EmojiManager emojiManager = null;
private synchronized void initEmoji ()
{
if (emojiManager == null)
{
emojiManager = EmojiManager.getInstance(mContext);
try
{
emojiManager.addJsonPlugins();
}
catch (JsonSyntaxException jse)
{
Log.e(ImApp.LOG_TAG,"could not parse json", jse);
}
catch (IOException fe)
{
Log.e(ImApp.LOG_TAG,"could not load emoji definition",fe);
}
catch (Exception fe)
{
Log.e(ImApp.LOG_TAG,"could not load emoji definition",fe);
}
}
mEmojiPager = (ViewPager)this.findViewById(R.id.emojiPager);
ImageView btnEmoji = (ImageView)findViewById(R.id.btnEmoji);
Collection<EmojiGroup> emojiGroups = emojiManager.getEmojiGroups();
if (emojiGroups.size() > 0)
{
btnEmoji.setVisibility(View.VISIBLE);
EmojiPagerAdapter emojiPagerAdapter = new EmojiPagerAdapter(mNewChatActivity, mComposeMessage, new ArrayList<EmojiGroup>(emojiGroups));
mEmojiPager.setAdapter(emojiPagerAdapter);
btnEmoji.setOnClickListener(new OnClickListener ()
{
@Override
public void onClick(View v) {
// mActionBox.setVisibility(View.GONE);
if (mEmojiPager.getVisibility() == View.GONE)
mEmojiPager.setVisibility(View.VISIBLE);
else
mEmojiPager.setVisibility(View.GONE);
}
});
}
else
{
btnEmoji.setVisibility(View.GONE);
btnEmoji.setOnClickListener(new OnClickListener ()
{
@Override
public void onClick(View v) {
//what? prompt to install?
}
});
}
}
public void startListening() {
if (!isServiceUp)
return;
mIsListening = true;
if (mViewType == VIEW_TYPE_CHAT) {
Cursor cursor = getMessageCursor();
if (cursor == null) {
long chatId = getChatId();
if (chatId != -1)
startQuery(chatId);
} else {
requeryCursor();
}
}
registerChatListener();
registerForConnEvents();
updateWarningView();
}
public void stopListening() {
//Cursor cursor = getMessageCursor();
//if (cursor != null && (!cursor.isClosed())) {
// cursor.close();
// }
cancelRequery();
unregisterChatListener();
unregisterForConnEvents();
mIsListening = false;
}
public void unbind() {
if (mQueryHandler != null)
mQueryHandler.closeCursor();
}
void updateChat() {
setViewType(VIEW_TYPE_CHAT);
// updateSessionInfo();
setStatusIcon();
//n8fr8 + devrandom: commented out on 15 Oct 2013: we really do want the chat to update w/o a connection
//so we can show message history in offline mode
/*
*
if (!isServiceUp)
return;
IImConnection conn = mApp.getConnection(mProviderId);
if (conn == null) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG))
log("Connection has been signed out");
return;
}*/
mHistory.invalidate();
startQuery(getChatId());
// This is not needed, now that there is a ChatView per fragment. It also causes a spurious detection of user action
// on fragments adjacent to the current one, when they get initialized.
//mComposeMessage.setText("");
updateWarningView();
}
int mContactType = -1;
private void updateSessionInfo(Cursor c) {
if (c != null && (!c.isClosed()))
{
mProviderId = c.getLong(PROVIDER_COLUMN);
mAccountId = c.getLong(ACCOUNT_COLUMN);
mPresenceStatus = c.getInt(PRESENCE_STATUS_COLUMN);
mContactType = c.getInt(TYPE_COLUMN);
mRemoteNickname = c.getString(NICKNAME_COLUMN);
mRemoteAddress = c.getString(USERNAME_COLUMN);
mSubscriptionType = c.getInt(SUBSCRIPTION_TYPE_COLUMN);
mSubscriptionStatus = c.getInt(SUBSCRIPTION_STATUS_COLUMN);
if ((mSubscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM)
&& (mSubscriptionStatus == Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING)) {
bindSubscription(mProviderId, mRemoteAddress);
}
}
}
public void setTitle ()
{
if (mIsSelected)
{
mNewChatActivity.setTitle(mRemoteNickname,mRemoteAvatar);
}
}
private void updatePresenceDisplay ()
{
if (mRemoteAvatar == null)
return;
switch (mPresenceStatus) {
case Presence.AVAILABLE:
mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_green_light));
mRemoteAvatar.setAlpha(255);
break;
case Presence.IDLE:
mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_green_dark));
mRemoteAvatar.setAlpha(255);
break;
case Presence.AWAY:
mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_orange_light));
mRemoteAvatar.setAlpha(255);
break;
case Presence.DO_NOT_DISTURB:
mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_red_dark));
mRemoteAvatar.setAlpha(255);
break;
case Presence.OFFLINE:
mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_grey_light));
mRemoteAvatar.setAlpha(100);
break;
default:
}
}
/*
private void setTitle() {
if (mType == Imps.Contacts.TYPE_GROUP) {
final String[] projection = { Imps.GroupMembers.NICKNAME };
Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, mChatId);
ContentResolver cr = mNewChatActivity.getContentResolver();
Cursor c = cr.query(memberUri, projection, null, null, null);
StringBuilder buf = new StringBuilder();
BrandingResources brandingRes = mApp.getBrandingResource(mProviderId);
if (c != null) {
while (c.moveToNext()) {
String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME));
int status = c.getInt(c.getColumnIndexOrThrow(Imps.Contacts.PRESENCE_STATUS));
buf.append(nickname);
buf.append(" (");
buf.append(brandingRes.getString(PresenceUtils.getStatusStringRes(this.mPresenceStatus)));
buf.append(")");
if (!c.isLast()) {
buf.append(',');
}
}
}
mNewChatActivity.setTitle(buf.toString());
} else {
StringBuilder buf = new StringBuilder();
BrandingResources brandingRes = mApp.getBrandingResource(mProviderId);
buf.append(this.mNickName);
buf.append(" (");
buf.append(brandingRes.getString(PresenceUtils.getStatusStringRes(this.mPresenceStatus)));
buf.append(")");
mNewChatActivity.setTitle(buf.toString());
Drawable avatar = loadAvatar(mUserName);
// if (avatar != null)
// mNewChatActivity.setHomeIcon(avatar);
// }
}*/
private void setStatusIcon() {
if (mContactType == Imps.Contacts.TYPE_GROUP) {
// hide the status icon for group chat.
// mStatusIcon.setVisibility(GONE);
} else {
// mStatusIcon.setVisibility(VISIBLE);
BrandingResources brandingRes = mApp.getBrandingResource(mProviderId);
int presenceResId = PresenceUtils.getStatusIconId(mPresenceStatus);
//mStatusIcon.setImageDrawable(brandingRes.getDrawable(presenceResId));
}
}
private void deleteChat ()
{
Uri chatUri = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mLastChatId);
mNewChatActivity.getContentResolver().delete(chatUri,null,null);
}
public void bindChat(long chatId) {
log("bind " + this + " " + chatId);
mLastChatId = chatId;
Uri contactUri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, chatId);
Cursor c = mNewChatActivity.getContentResolver().query(contactUri, CHAT_PROJECTION, null, null, null);
if (c == null)
return;
if (!c.moveToFirst()) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("Failed to query chat: " + chatId);
}
mLastChatId = -1;
c.close();
} else {
updateSessionInfo(c);
if (mRemoteAvatar == null)
{
try {mRemoteAvatar =DatabaseUtils.getAvatarFromCursor(c, AVATAR_COLUMN, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT);}
catch (Exception e){}
if (mRemoteAvatar == null)
{
mRemoteAvatar = new RoundedAvatarDrawable(BitmapFactory.decodeResource(getResources(),
R.drawable.avatar_unknown));
}
updatePresenceDisplay();
}
c.close();
mCurrentChatSession = getChatSession();
if (mCurrentChatSession == null)
mCurrentChatSession = createChatSession();
if (mCurrentChatSession != null) {
isServiceUp = true;
}
updateChat();
}
}
public void bindInvitation(long invitationId) {
Uri uri = ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, invitationId);
ContentResolver cr = mNewChatActivity.getContentResolver();
Cursor cursor = cr.query(uri, INVITATION_PROJECT, null, null, null);
try {
if (!cursor.moveToFirst()) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("Failed to query invitation: " + invitationId);
}
// mNewChatActivity.finish();
} else {
setViewType(VIEW_TYPE_INVITATION);
mInvitationId = cursor.getLong(INVITATION_ID_COLUMN);
mProviderId = cursor.getLong(INVITATION_PROVIDER_COLUMN);
String sender = cursor.getString(INVITATION_SENDER_COLUMN);
TextView mInvitationText = (TextView) findViewById(R.id.txtInvitation);
mInvitationText.setText(mContext.getString(R.string.invitation_prompt, sender));
// mNewChatActivity.setTitle(mContext.getString(R.string.chat_with, sender));
}
} finally {
cursor.close();
}
}
public void bindSubscription(long providerId, String from) {
mProviderId = providerId;
// mRemoteAddressString = from;
setViewType(VIEW_TYPE_SUBSCRIPTION);
TextView text = (TextView) findViewById(R.id.txtSubscription);
String displayableAddr = ImpsAddressUtils.getDisplayableAddress(from);
text.setText(mContext.getString(R.string.subscription_prompt, displayableAddr));
//.displayableAdd mNewChatActivity.setTitle(mContext.getString(R.string.chat_with, displayableAddr));
mApp.dismissChatNotification(providerId, from);
}
private void setViewType(int type) {
mViewType = type;
if (type == VIEW_TYPE_CHAT) {
findViewById(R.id.invitationPanel).setVisibility(GONE);
findViewById(R.id.subscription).setVisibility(GONE);
setChatViewEnabled(true);
} else if (type == VIEW_TYPE_INVITATION) {
//setChatViewEnabled(false);
findViewById(R.id.invitationPanel).setVisibility(VISIBLE);
findViewById(R.id.btnAccept).requestFocus();
} else if (type == VIEW_TYPE_SUBSCRIPTION) {
//setChatViewEnabled(false);
findViewById(R.id.subscription).setVisibility(VISIBLE);
findViewById(R.id.btnApproveSubscription).requestFocus();
}
}
private void setChatViewEnabled(boolean enabled) {
mComposeMessage.setEnabled(enabled);
mSendButton.setEnabled(enabled);
if (enabled) {
// This can steal focus from the fragment that's i n front of the user
//mComposeMessage.requestFocus();
} else {
mHistory.setAdapter(null);
}
}
ListView getHistoryView() {
return mHistory;
}
private synchronized void startQuery(long chatId) {
if (mQueryHandler == null) {
mQueryHandler = new QueryHandler(mContext);
} else {
// Cancel any pending queries
mQueryHandler.cancelOperation(QUERY_TOKEN);
}
Uri uri = Imps.Messages.getContentUriByThreadId(chatId);
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("queryCursor: uri=" + uri);
}
mQueryHandler.startQuery(QUERY_TOKEN, null, uri, null, null /* selection */,
null /* selection args */, "date");
}
void scheduleRequery(long interval) {
if (mRequeryCallback == null) {
mRequeryCallback = new RequeryCallback();
} else {
mHandler.removeCallbacks(mRequeryCallback);
}
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("scheduleRequery");
}
mHandler.postDelayed(mRequeryCallback, interval);
}
void cancelRequery() {
if (mRequeryCallback != null) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("cancelRequery");
}
mHandler.removeCallbacks(mRequeryCallback);
mRequeryCallback = null;
}
}
void requeryCursor() {
if (mMessageAdapter.isScrolling()) {
mMessageAdapter.setNeedRequeryCursor(true);
return;
}
// This is redundant if there are messages in view, because the cursor requery will update everything.
// However, if there are no messages, no update will trigger below, and we still want this to update.
updateWarningView();
// TODO: async query?
Cursor cursor = getMessageCursor();
if (cursor != null) {
cursor.requery();
}
}
private Cursor getMessageCursor() {
return mMessageAdapter == null ? null : mMessageAdapter.getCursor();
}
public void closeChatSession(boolean doDelete) {
if (getChatSession() != null) {
try {
if (doDelete)
setOTRState(false);
updateWarningView();
getChatSession().leave();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
}
}
if (doDelete)
deleteChat();
}
public void verifyScannedFingerprint (String scannedFingerprint)
{
try
{
IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession();
if (scannedFingerprint != null && scannedFingerprint.equalsIgnoreCase(otrChatSession.getRemoteFingerprint())) {
verifyRemoteFingerprint();
}
}
catch (RemoteException e)
{
LogCleaner.error(ImApp.LOG_TAG, "unable to perform manual key verification", e);
}
}
public void showVerifyDialog() {
if (getChatId() == -1)
return;
try {
IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession();
if (otrChatSession == null) {
return;
}
String localFingerprint = otrChatSession.getLocalFingerprint();
String remoteFingerprint = otrChatSession.getRemoteFingerprint();
if (TextUtils.isEmpty(localFingerprint) || TextUtils.isEmpty(remoteFingerprint)) {
return;
}
StringBuffer message = new StringBuffer();
message.append(mContext.getString(R.string.fingerprint_for_you)).append("\n")
.append(prettyPrintFingerprint(localFingerprint)).append("\n\n");
message.append(mContext.getString(R.string.fingerprint_for_))
.append(otrChatSession.getRemoteUserId()).append("\n")
.append(prettyPrintFingerprint(remoteFingerprint)).append("\n\n");
message.append(mContext.getString(R.string.are_you_sure_you_want_to_confirm_this_key_));
new AlertDialog.Builder(mContext)
.setTitle(R.string.verify_key_)
.setMessage(message.toString())
.setPositiveButton(R.string.menu_verify_fingerprint,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
verifyRemoteFingerprint();
}
})
.setNegativeButton(R.string.menu_verify_secret,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
initSmpUI();
}
})
.setNeutralButton(R.string.menu_scan, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
new IntentIntegrator(mNewChatActivity).initiateScan();
}
}).show();
} catch (RemoteException e) {
LogCleaner.error(ImApp.LOG_TAG, "unable to perform manual key verification", e);
}
}
private void initSmpUI() {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View viewSmp = inflater.inflate(R.layout.smp_question_dialog, null, false);
if (viewSmp != null)
{
new AlertDialog.Builder(mContext).setTitle(mContext.getString(R.string.otr_qa_title)).setView(viewSmp)
.setPositiveButton(mContext.getString(R.string.otr_qa_send), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
EditText eiQuestion = (EditText) viewSmp.findViewById(R.id.editSmpQuestion);
EditText eiAnswer = (EditText) viewSmp.findViewById(R.id.editSmpAnswer);
String question = eiQuestion.getText().toString();
String answer = eiAnswer.getText().toString();
initSmp(question, answer);
}
}).setNegativeButton(mContext.getString(R.string.otr_qa_cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Do nothing.
}
}).show();
}
}
private void initSmp(String question, String answer) {
try {
if (mCurrentChatSession != null)
{
IOtrChatSession iOtrSession = mCurrentChatSession.getOtrChatSession();
iOtrSession.initSmpVerification(question, answer);
}
} catch (RemoteException e) {
Log.e(ImApp.LOG_TAG, "error init SMP", e);
}
}
private void verifyRemoteFingerprint() {
try {
IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession();
otrChatSession.verifyKey(otrChatSession.getRemoteUserId());
} catch (RemoteException e) {
Log.e(ImApp.LOG_TAG, "error init otr", e);
}
updateWarningView();
}
private static String prettyPrintFingerprint (String fingerprint)
{
StringBuffer spacedFingerprint = new StringBuffer();
for (int i = 0; i + 8 <= fingerprint.length(); i+=8)
{
spacedFingerprint.append(fingerprint.subSequence(i,i+8));
spacedFingerprint.append(' ');
}
return spacedFingerprint.toString();
}
public void blockContact() {
// TODO: unify with codes in ContactListView
DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
try {
checkConnection();
mConn = mApp.getConnection(mProviderId);
IContactListManager manager = mConn.getContactListManager();
manager.blockContact(Address.stripResource(mRemoteAddress));
// mNewChatActivity.finish();
} catch (Exception e) {
mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
}
}
};
Resources r = getResources();
// The positive button is deliberately set as no so that
// the no is the default value
new AlertDialog.Builder(mContext).setTitle(R.string.confirm)
.setMessage(r.getString(R.string.confirm_block_contact, mRemoteNickname))
.setPositiveButton(R.string.yes, confirmListener) // default button
.setNegativeButton(R.string.no, null).setCancelable(false).show();
}
public long getProviderId() {
return mProviderId;
}
public long getAccountId() {
return mAccountId;
}
public long getChatId() {
return mLastChatId;
}
private IChatSession createChatSession() {
try
{
checkConnection ();
if (mConn != null) {
IChatSessionManager sessionMgr = mConn.getChatSessionManager();
if (sessionMgr != null) {
String remoteAddress = mRemoteAddress;
IChatSession session = null;
if (mContactType == Imps.Contacts.TYPE_GROUP)
{
session = sessionMgr.createMultiUserChatSession(remoteAddress,null, false);
}
else
{
remoteAddress = Address.stripResource(mRemoteAddress);
session = sessionMgr.createChatSession(remoteAddress,false);
}
return session;
}
}
} catch (Exception e) {
//mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "issue getting chat session",e);
}
return null;
}
private IChatSession getChatSession() {
try {
if ( checkConnection ()) {
if (mConn != null)
{
IChatSessionManager sessionMgr = mConn.getChatSessionManager();
if (sessionMgr != null) {
IChatSession session = sessionMgr.getChatSession(Address.stripResource(mRemoteAddress));
return session;
}
}
}
} catch (Exception e) {
//mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "error getting chat session",e);
}
return null;
}
boolean isGroupChat() {
return this.mContactType == Imps.Contacts.TYPE_GROUP;
}
void sendMessage() {
mEmojiPager.setVisibility(View.GONE);
String msg = mComposeMessage.getText().toString();
if (TextUtils.isEmpty(msg.trim())) {
return;
}
IChatSession session = getChatSession();
if (session == null)
session = createChatSession();
if (session != null) {
try {
session.sendMessage(msg);
mComposeMessage.setText("");
mComposeMessage.requestFocus();
requeryCursor();
} catch (RemoteException e) {
// mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
} catch (Exception e) {
// mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
}
}
}
void registerChatListener() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("registerChatListener " + mLastChatId);
}
try {
if (getChatSession() != null) {
getChatSession().registerChatListener(mChatListener);
}
checkConnection();
if (mConn != null)
{
IContactListManager listMgr = mConn.getContactListManager();
listMgr.registerContactListListener(mContactListListener);
}
} catch (Exception e) {
Log.w(ImApp.LOG_TAG, "<ChatView> registerChatListener fail:" + e.getMessage());
}
}
void unregisterChatListener() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("unregisterChatListener " + mLastChatId);
}
try {
if (getChatSession() != null) {
getChatSession().unregisterChatListener(mChatListener);
}
checkConnection ();
if (mConn != null) {
IContactListManager listMgr = mConn.getContactListManager();
listMgr.unregisterContactListListener(mContactListListener);
}
} catch (Exception e) {
Log.w(ImApp.LOG_TAG, "<ChatView> unregisterChatListener fail:" + e.getMessage());
}
}
void updateWarningView() {
int visibility = View.GONE;
int iconVisibility = View.GONE;
String message = null;
boolean isConnected;
try {
checkConnection();
isConnected = (mConn == null) ? false : mConn.getState() == ImConnection.LOGGED_IN;
} catch (Exception e) {
isConnected = false;
}
if (this.isGroupChat())
{
//anything to do here?
/*
visibility = View.VISIBLE;
message = getContext().getString(R.string.this_is_a_group_chat);
mWarningText.setTextColor(Color.WHITE);
mStatusWarningView.setBackgroundColor(Color.LTGRAY);
*/
mButtonAttach.setVisibility(View.GONE);
mSendButton.setImageResource(R.drawable.ic_send_holo_light);
mComposeMessage.setHint(R.string.this_is_a_group_chat);
}
else if (mCurrentChatSession != null) {
IOtrChatSession otrChatSession = null;
try {
otrChatSession = mCurrentChatSession.getOtrChatSession();
//check if the chat is otr or not
if (otrChatSession != null) {
try {
mLastSessionStatus = SessionStatus.values()[otrChatSession.getChatStatus()];
} catch (RemoteException e) {
Log.w("Gibber", "Unable to call remote OtrChatSession from ChatView", e);
}
}
} catch (RemoteException e) {
LogCleaner.error(ImApp.LOG_TAG, "error getting OTR session in ChatView", e);
}
if (mContactType == Imps.Contacts.TYPE_GROUP) {
message = "";
}
else if ((mSubscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM)) {
bindSubscription(mProviderId, mRemoteAddress);
visibility = View.VISIBLE;
//message = mContext.getString(R.string.contact_not_in_list_warning, mRemoteNickname);
//mWarningText.setTextColor(Color.WHITE);
//mStatusWarningView.setBackgroundColor(Color.DKGRAY);
} else {
visibility = View.GONE;
}
if (mLastSessionStatus == SessionStatus.PLAINTEXT) {
mSendButton.setImageResource(R.drawable.ic_send_holo_light);
mComposeMessage.setHint(R.string.compose_hint);
}
else if (mLastSessionStatus == SessionStatus.ENCRYPTED) {
if (mIsStartingOtr)
{
mIsStartingOtr = false; //it's started!
mProgressBarOtr.setVisibility(View.GONE);
}
mComposeMessage.setHint(R.string.compose_hint_secure);
mSendButton.setImageResource(R.drawable.ic_send_secure);
try
{
String rFingerprint = otrChatSession.getRemoteFingerprint();
mIsVerified = otrChatSession.isKeyVerified(mRemoteAddress);
}
catch (RemoteException re){}
} else if (mLastSessionStatus == SessionStatus.FINISHED) {
mSendButton.setImageResource(R.drawable.ic_send_holo_light);
mComposeMessage.setHint(R.string.compose_hint);
mWarningText.setTextColor(Color.WHITE);
mStatusWarningView.setBackgroundColor(Color.DKGRAY);
message = mContext.getString(R.string.otr_session_status_finished);
visibility = View.VISIBLE;
}
}
if (!isConnected)
{
// visibility = View.VISIBLE;
// iconVisibility = View.VISIBLE;
// mWarningText.setTextColor(Color.WHITE);
// mStatusWarningView.setBackgroundColor(Color.DKGRAY);
// message = mContext.getString(R.string.disconnected_warning);
mComposeMessage.setHint(R.string.error_suspended_connection);
}
mStatusWarningView.setVisibility(visibility);
if (visibility == View.VISIBLE) {
if (message != null && message.length() > 0)
{
mWarningText.setText(message);
mWarningText.setVisibility(View.VISIBLE);
}
else
{
mWarningText.setVisibility(View.GONE);
}
}
mNewChatActivity.updateEncryptionMenuState();
}
public SessionStatus getOtrSessionStatus ()
{
return mLastSessionStatus;
}
public boolean isOtrSessionVerified ()
{
return mIsVerified;
}
public int getRemotePresence ()
{
return mPresenceStatus;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
userActionDetected();
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
try {
userActionDetected();
return super.dispatchTouchEvent(ev);
} catch (ActivityNotFoundException e) {
/* if the user clicked a link, e.g. geo:60.17,24.829, and there is
* no app to handle that kind of link, catch the exception */
Toast.makeText(getContext(), R.string.error_no_app_to_handle_url, Toast.LENGTH_SHORT)
.show();
return true;
}
}
@Override
public boolean dispatchTrackballEvent(MotionEvent ev) {
userActionDetected();
return super.dispatchTrackballEvent(ev);
}
private void userActionDetected() {
// Check that we have a chat session and that our fragment is resumed
// The latter filters out bogus TextWatcher events on restore from saved
if (getChatSession() != null && mIsListening) {
try {
getChatSession().markAsRead();
// updateWarningView();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
}
}
}
private final class ChatViewHandler extends SimpleAlertHandler {
public ChatViewHandler(Activity activity) {
super(activity);
}
@Override
public void handleMessage(Message msg) {
long providerId = ((long) msg.arg1 << 32) | msg.arg2;
if (providerId != mProviderId) {
return;
}
switch (msg.what) {
case ImApp.EVENT_CONNECTION_DISCONNECTED:
log("Handle event connection disconnected.");
updateWarningView();
promptDisconnectedEvent(msg);
return;
case PROMPT_FOR_DATA_TRANSFER:
showPromptForData(msg.getData().getString("from"),msg.getData().getString("file"));
break;
case SHOW_DATA_ERROR:
String fileName = msg.getData().getString("file");
String error = msg.getData().getString("err");
Toast.makeText(mContext, "Error transferring file: " + error, Toast.LENGTH_LONG).show();
mProgressTransfer.setVisibility(View.GONE);
break;
case SHOW_DATA_PROGRESS:
int percent = msg.getData().getInt("progress");
mProgressTransfer.setVisibility(View.VISIBLE);
mProgressTransfer.setProgress(percent);
mProgressTransfer.setMax(100);
if (percent > 95)
{
mProgressTransfer.setVisibility(View.GONE);
requeryCursor();
mMessageAdapter.notifyDataSetChanged();
}
break;
default:
updateWarningView();
}
super.handleMessage(msg);
}
}
public static class DeltaCursor implements Cursor {
static final String DELTA_COLUMN_NAME = "delta";
private Cursor mInnerCursor;
private String[] mColumnNames;
private int mDateColumn = -1;
private int mDeltaColumn = -1;
DeltaCursor(Cursor cursor) {
mInnerCursor = cursor;
String[] columnNames = cursor.getColumnNames();
int len = columnNames.length;
mColumnNames = new String[len + 1];
for (int i = 0; i < len; i++) {
mColumnNames[i] = columnNames[i];
if (mColumnNames[i].equals(Imps.Messages.DATE)) {
mDateColumn = i;
}
}
mDeltaColumn = len;
mColumnNames[mDeltaColumn] = DELTA_COLUMN_NAME;
//if (DBG) log("##### DeltaCursor constructor: mDeltaColumn=" +
// mDeltaColumn + ", columnName=" + mColumnNames[mDeltaColumn]);
}
public int getCount() {
return mInnerCursor.getCount();
}
public int getPosition() {
return mInnerCursor.getPosition();
}
public boolean move(int offset) {
return mInnerCursor.move(offset);
}
public boolean moveToPosition(int position) {
return mInnerCursor.moveToPosition(position);
}
public boolean moveToFirst() {
return mInnerCursor.moveToFirst();
}
public boolean moveToLast() {
return mInnerCursor.moveToLast();
}
public boolean moveToNext() {
return mInnerCursor.moveToNext();
}
public boolean moveToPrevious() {
return mInnerCursor.moveToPrevious();
}
public boolean isFirst() {
return mInnerCursor.isFirst();
}
public boolean isLast() {
return mInnerCursor.isLast();
}
public boolean isBeforeFirst() {
return mInnerCursor.isBeforeFirst();
}
public boolean isAfterLast() {
return mInnerCursor.isAfterLast();
}
public int getColumnIndex(String columnName) {
if (DELTA_COLUMN_NAME.equals(columnName)) {
return mDeltaColumn;
}
int columnIndex = mInnerCursor.getColumnIndex(columnName);
return columnIndex;
}
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
if (DELTA_COLUMN_NAME.equals(columnName)) {
return mDeltaColumn;
}
return mInnerCursor.getColumnIndexOrThrow(columnName);
}
public String getColumnName(int columnIndex) {
if (columnIndex == mDeltaColumn) {
return DELTA_COLUMN_NAME;
}
return mInnerCursor.getColumnName(columnIndex);
}
public int getColumnCount() {
return mInnerCursor.getColumnCount() + 1;
}
public void deactivate() {
mInnerCursor.deactivate();
}
public boolean requery() {
return mInnerCursor.requery();
}
public void close() {
mInnerCursor.close();
}
public boolean isClosed() {
return mInnerCursor.isClosed();
}
public void registerContentObserver(ContentObserver observer) {
mInnerCursor.registerContentObserver(observer);
}
public void unregisterContentObserver(ContentObserver observer) {
mInnerCursor.unregisterContentObserver(observer);
}
public void registerDataSetObserver(DataSetObserver observer) {
mInnerCursor.registerDataSetObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mInnerCursor.unregisterDataSetObserver(observer);
}
public void setNotificationUri(ContentResolver cr, Uri uri) {
mInnerCursor.setNotificationUri(cr, uri);
}
public boolean getWantsAllOnMoveCalls() {
return mInnerCursor.getWantsAllOnMoveCalls();
}
public Bundle getExtras() {
return mInnerCursor.getExtras();
}
public Bundle respond(Bundle extras) {
return mInnerCursor.respond(extras);
}
public String[] getColumnNames() {
return mColumnNames;
}
private void checkPosition() {
int pos = mInnerCursor.getPosition();
int count = mInnerCursor.getCount();
if (-1 == pos || count == pos) {
throw new CursorIndexOutOfBoundsException(pos, count);
}
}
public byte[] getBlob(int column) {
checkPosition();
if (column == mDeltaColumn) {
return null;
}
return mInnerCursor.getBlob(column);
}
public String getString(int column) {
checkPosition();
if (column == mDeltaColumn) {
long value = getDeltaValue();
return Long.toString(value);
}
return mInnerCursor.getString(column);
}
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
checkPosition();
if (columnIndex == mDeltaColumn) {
long value = getDeltaValue();
String strValue = Long.toString(value);
int len = strValue.length();
char[] data = buffer.data;
if (data == null || data.length < len) {
buffer.data = strValue.toCharArray();
} else {
strValue.getChars(0, len, data, 0);
}
buffer.sizeCopied = strValue.length();
} else {
mInnerCursor.copyStringToBuffer(columnIndex, buffer);
}
}
public short getShort(int column) {
checkPosition();
if (column == mDeltaColumn) {
return (short) getDeltaValue();
}
return mInnerCursor.getShort(column);
}
public int getInt(int column) {
checkPosition();
if (column == mDeltaColumn) {
return (int) getDeltaValue();
}
return mInnerCursor.getInt(column);
}
public long getLong(int column) {
//if (DBG) log("DeltaCursor.getLong: column=" + column + ", mDeltaColumn=" + mDeltaColumn);
checkPosition();
if (column == mDeltaColumn) {
return getDeltaValue();
}
return mInnerCursor.getLong(column);
}
public float getFloat(int column) {
checkPosition();
if (column == mDeltaColumn) {
return getDeltaValue();
}
return mInnerCursor.getFloat(column);
}
public double getDouble(int column) {
checkPosition();
if (column == mDeltaColumn) {
return getDeltaValue();
}
return mInnerCursor.getDouble(column);
}
public boolean isNull(int column) {
checkPosition();
if (column == mDeltaColumn) {
return false;
}
return mInnerCursor.isNull(column);
}
private long getDeltaValue() {
int pos = mInnerCursor.getPosition();
//Log.i(LOG_TAG, "getDeltaValue: mPos=" + mPos);
long t2, t1;
if (pos == getCount() - 1) {
t1 = mInnerCursor.getLong(mDateColumn);
t2 = System.currentTimeMillis();
} else {
mInnerCursor.moveToPosition(pos + 1);
t2 = mInnerCursor.getLong(mDateColumn);
mInnerCursor.moveToPosition(pos);
t1 = mInnerCursor.getLong(mDateColumn);
}
return t2 - t1;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public int getType(int arg0) {
return mInnerCursor.getType(arg0);
}
@TargetApi(19)
@Override
public Uri getNotificationUri() {
return mInnerCursor.getNotificationUri();
}
}
private class MessageAdapter extends CursorAdapter implements AbsListView.OnScrollListener {
private int mScrollState;
private boolean mNeedRequeryCursor;
private int mNicknameColumn;
private int mBodyColumn;
private int mDateColumn;
private int mTypeColumn;
private int mErrCodeColumn;
private int mDeltaColumn;
private int mDeliveredColumn;
private int mMimeTypeColumn;
private int mIdColumn;
private LayoutInflater mInflater;
public MessageAdapter(Activity context, Cursor c) {
super(context, c, false);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (c != null) {
resolveColumnIndex(c);
}
}
private void resolveColumnIndex(Cursor c) {
mNicknameColumn = c.getColumnIndexOrThrow(Imps.Messages.NICKNAME);
mBodyColumn = c.getColumnIndexOrThrow(Imps.Messages.BODY);
mDateColumn = c.getColumnIndexOrThrow(Imps.Messages.DATE);
mTypeColumn = c.getColumnIndexOrThrow(Imps.Messages.TYPE);
mErrCodeColumn = c.getColumnIndexOrThrow(Imps.Messages.ERROR_CODE);
mDeltaColumn = c.getColumnIndexOrThrow(DeltaCursor.DELTA_COLUMN_NAME);
mDeliveredColumn = c.getColumnIndexOrThrow(Imps.Messages.IS_DELIVERED);
mMimeTypeColumn = c.getColumnIndexOrThrow(Imps.Messages.MIME_TYPE);
mIdColumn = c.getColumnIndexOrThrow(Imps.Messages._ID);
}
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
if (cursor != null) {
resolveColumnIndex(cursor);
}
}
@Override
public int getItemViewType(int position) {
Cursor c = getCursor();
c.moveToPosition(position);
int type = c.getInt(mTypeColumn);
boolean isLeft = (type == Imps.MessageType.INCOMING_ENCRYPTED)||(type == Imps.MessageType.INCOMING)||(type == Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED);
if (isLeft)
return 0;
else
return 1;
}
@Override
public int getViewTypeCount() {
return 2;
}
void setLinkifyForMessageView(MessageView messageView) {
try {
if (messageView == null)
return;
ContentResolver cr = getContext().getContentResolver();
Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,
new String[] { Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE },
Imps.ProviderSettings.PROVIDER + "=?", new String[] { Long
.toString(Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS) },
null);
Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(
pCursor, cr, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS,
false /* keep updated */, null /* no handler */);
if (settings != null)
{
if (mConn !=null)
messageView.setLinkify(!mConn.isUsingTor() || settings.getLinkifyOnTor());
settings.close();
}
if (pCursor != null)
pCursor.close();
} catch (RemoteException e) {
e.printStackTrace();
messageView.setLinkify(false);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View result;
int type = getItemViewType(cursor.getPosition());
if (type == 0)
result = mInflater.inflate(R.layout.message_view_left, null);
else
result = mInflater.inflate(R.layout.message_view_right, null);
return result;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
MessageView messageView = (MessageView) view;
setLinkifyForMessageView(messageView);
if (mApp.isThemeDark())
{
messageView.setMessageBackground(getResources().getDrawable(R.drawable.message_view_rounded_dark));
}
else
{
messageView.setMessageBackground(getResources().getDrawable(R.drawable.message_view_rounded_light));
}
int messageType = cursor.getInt(mTypeColumn);
String nickname = isGroupChat() ? cursor.getString(mNicknameColumn) : mRemoteNickname;
String mimeType = cursor.getString(mMimeTypeColumn);
int id = cursor.getInt(mIdColumn);
String body = cursor.getString(mBodyColumn);
long delta = cursor.getLong(mDeltaColumn);
boolean showTimeStamp = true;//(delta > SHOW_TIME_STAMP_INTERVAL);
long timestamp = cursor.getLong(mDateColumn);
Date date = showTimeStamp ? new Date(timestamp) : null;
boolean isDelivered = cursor.getLong(mDeliveredColumn) > 0;
long showDeliveryInterval = (mimeType == null) ? SHOW_DELIVERY_INTERVAL : SHOW_MEDIA_DELIVERY_INTERVAL;
boolean showDelivery = ((System.currentTimeMillis() - timestamp) > showDeliveryInterval);
DeliveryState deliveryState = DeliveryState.NEUTRAL;
if (showDelivery && !isDelivered && mExpectingDelivery) {
deliveryState = DeliveryState.UNDELIVERED;
}
else if (isDelivered)
{
deliveryState = DeliveryState.DELIVERED;
}
EncryptionState encState = EncryptionState.NONE;
if (messageType == Imps.MessageType.INCOMING_ENCRYPTED)
{
messageType = Imps.MessageType.INCOMING;
encState = EncryptionState.ENCRYPTED;
}
else if (messageType == Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED)
{
messageType = Imps.MessageType.INCOMING;
encState = EncryptionState.ENCRYPTED_AND_VERIFIED;
}
else if (messageType == Imps.MessageType.OUTGOING_ENCRYPTED)
{
messageType = Imps.MessageType.OUTGOING;
encState = EncryptionState.ENCRYPTED;
}
else if (messageType == Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED)
{
messageType = Imps.MessageType.OUTGOING;
encState = EncryptionState.ENCRYPTED_AND_VERIFIED;
}
switch (messageType) {
case Imps.MessageType.INCOMING:
messageView.bindIncomingMessage(id, messageType, mRemoteAddress, nickname, mimeType, body, date, mMarkup, isScrolling(), encState, isGroupChat(), mPresenceStatus);
break;
case Imps.MessageType.OUTGOING:
case Imps.MessageType.POSTPONED:
int errCode = cursor.getInt(mErrCodeColumn);
if (errCode != 0) {
messageView.bindErrorMessage(errCode);
} else {
messageView.bindOutgoingMessage(id, messageType, null, mimeType, body, date, mMarkup, isScrolling(),
deliveryState, encState);
}
break;
default:
messageView.bindPresenceMessage(mRemoteAddress, messageType, isGroupChat(), isScrolling());
}
// updateWarningView();
if (!mExpectingDelivery && isDelivered) {
log("Setting delivery icon");
mExpectingDelivery = true;
scheduleRequery(DEFAULT_QUERY_INTERVAL); // FIXME workaround to no refresh
} else if (cursor.getPosition() == cursor.getCount() - 1) {
// if showTimeStamp is false for the latest message, then set a timer to query the
// cursor again in a minute, so we can update the last message timestamp if no new
// message is received
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("delta = " + delta + ", showTs=" + showTimeStamp);
}
if (!showDelivery) {
scheduleRequery(SHOW_DELIVERY_INTERVAL);
} else if (!showTimeStamp) {
scheduleRequery(SHOW_TIME_STAMP_INTERVAL);
} else {
cancelRequery();
}
}
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
// do nothing
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
int oldState = mScrollState;
mScrollState = scrollState;
if (getChatSession() != null) {
try {
getChatSession().markAsRead();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
}
}
if (oldState == OnScrollListener.SCROLL_STATE_FLING) {
if (mNeedRequeryCursor) {
requeryCursor();
} else {
notifyDataSetChanged();
}
}
}
boolean isScrolling() {
return mScrollState == OnScrollListener.SCROLL_STATE_FLING;
}
void setNeedRequeryCursor(boolean requeryCursor) {
mNeedRequeryCursor = requeryCursor;
}
}
Cursor getMessageAtPosition(int position) {
Object item = mMessageAdapter.getItem(position);
return (Cursor) item;
}
EditText getComposedMessage() {
return mComposeMessage;
}
public void onServiceConnected() {
if (!isServiceUp) {
bindChat(mLastChatId);
startListening();
}
}
}