/*
* 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.OtrDebugLogger;
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.IDataListener;
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.service.ImServiceConstants;
import info.guardianproject.util.LogCleaner;
import info.guardianproject.util.SystemServices;
import info.guardianproject.util.SystemServices.Scanner;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import net.java.otr4j.session.SessionStatus;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.AsyncQueryHandler;
import android.content.ClipData;
import android.content.ClipboardManager;
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.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Browser;
import android.support.v4.app.NotificationCompat;
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.webkit.MimeTypeMap;
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.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
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.TextView;
import android.widget.Toast;
import com.google.gson.JsonSyntaxException;
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
};
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 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 mActivity;
ImApp mApp;
SimpleAlertHandler mHandler;
Cursor mCursor;
//private ImageView mStatusIcon;
// private TextView mTitle;
/*package*/ListView mHistory;
EditText mComposeMessage;
private ImageButton mSendButton;
private View mStatusWarningView;
private ImageView mWarningIcon;
private TextView mWarningText;
private ViewPager mEmojiPager;
private View mActionBox;
private ImageView mDeliveryIcon;
private boolean mExpectingDelivery;
private CompoundButton mOtrSwitch;
private boolean mOtrSwitchTouched = false;
private boolean mIsSelected = false;
SessionStatus mLastSessionStatus = null;
public void setSelected (boolean isSelected)
{
mIsSelected = isSelected;
}
private OnCheckedChangeListener mOtrListener = new OnCheckedChangeListener ()
{
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setOTRState(isChecked);
mOtrSwitchTouched = true;
updateWarningView();
}
};
public void setOTRState(boolean otrEnabled) {
try {
IImConnection conn = mApp.getConnection(mProviderId);
boolean isConnected = (conn == null) ? false : conn.getState() != ImConnection.SUSPENDED;
if (isConnected)
{
mCurrentChatSession = conn.getChatSessionManager().getChatSession(mRemoteAddress);
if (mCurrentChatSession != null)
{
IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession();
if (otrChatSession != null)
{
if (otrEnabled) {
otrChatSession.startChatEncryption();
}
else
{
otrChatSession.stopChatEncryption();
}
}
}
}
}
catch (RemoteException e) {
Log.d(ImApp.LOG_TAG, "error getting remote activity", e);
}
}
private MessageAdapter mMessageAdapter;
private boolean isServiceUp;
private IChatSession mCurrentChatSession;
private DataAdapter mDataListenerAdapter = new DataAdapter();
long mLastChatId=-1;
int mType;
String mRemoteNickname;
String mRemoteAddress;
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; // 1 minute
private static final long SHOW_DELIVERY_INTERVAL = 5 * 1000; // 10 seconds
private static final long DEFAULT_QUERY_INTERVAL = 1000;
private static final long FAST_QUERY_INTERVAL = 100;
private static final int QUERY_TOKEN = 10;
// Async QueryHandler
private final class QueryHandler extends AsyncQueryHandler {
public QueryHandler(Context context) {
super(context.getContentResolver());
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor c) {
mExpectingDelivery = false;
setDeliveryIcon();
if (c != null)
{
Cursor cursor = new DeltaCursor(c);
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("onQueryComplete: cursor.count=" + cursor.getCount());
}
if (mMessageAdapter != null && cursor != null)
mMessageAdapter.changeCursor(cursor);
}
}
}
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>(mActivity,
android.R.layout.select_dialog_item, linkUrls);
AlertDialog.Builder b = new AlertDialog.Builder(mActivity);
b.setTitle(R.string.select_link_title);
b.setCancelable(true);
b.setAdapter(a, new DialogInterface.OnClickListener() {
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, mActivity.getPackageName());
mActivity.startActivity(intent);
}
});
b.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
b.show();
}
else
{
viewProfile();
}
}
};
private IChatListener mChatListener = new ChatListenerAdapter() {
@Override
public boolean onIncomingMessage(IChatSession ses,
info.guardianproject.otr.app.im.engine.Message msg) {
scheduleRequery(FAST_QUERY_INTERVAL);
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);
};
@Override
public void onIncomingData(IChatSession ses, byte[] data) {
try {
Log.i("OTR_DATA", "incoming data " + new String(data, "UTF8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
};
};
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) {
}
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))) {
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);
mActivity = (NewChatActivity) context;
mApp = (ImApp)mActivity.getApplication();
mHandler = new ChatViewHandler(mActivity);
mContext = context;
ThemeableActivity.setBackgroundImage(this, mActivity);
}
void registerForConnEvents() {
mApp.registerForConnEvents(mHandler);
}
void unregisterForConnEvents() {
mApp.unregisterForConnEvents(mHandler);
}
@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);
mStatusWarningView = findViewById(R.id.warning);
mWarningIcon = (ImageView) findViewById(R.id.warningIcon);
mWarningText = (TextView) findViewById(R.id.warningText);
mOtrSwitch = (CompoundButton)findViewById(R.id.otrSwitch);
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)
{
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager)
mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
String textToCopy = ((MessageView)arg1).getLastMessage();
ClipData clip = ClipData.newPlainText("chat",textToCopy);
clipboard.setPrimaryClip(clip);
Toast.makeText(mActivity, "message copied to the clipboard", Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
});
mWarningText.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
viewProfile();
}
});
mOtrSwitch.setOnCheckedChangeListener(mOtrListener);
mComposeMessage.setOnKeyListener(new OnKeyListener() {
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();
}
});
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)
{
mActivity.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)
{
mActivity.startFilePicker();
}
else
{
mHandler.showServiceErrorAlert(getContext().getString(R.string.please_enable_chat_encryption_to_share_files));
}
}
});
initEmoji();
mMessageAdapter = new MessageAdapter(mActivity, null);
mHistory.setAdapter(mMessageAdapter);
}
private static EmojiManager emojiManager = null;
private synchronized void initEmoji ()
{
if (emojiManager == null)
{
emojiManager = EmojiManager.getInstance(mContext);
try
{
emojiManager.addJsonDefinitions("emoji/phantom.json", "emoji/phantom", "png");
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);
Collection<EmojiGroup> emojiGroups = emojiManager.getEmojiGroups();
EmojiPagerAdapter emojiPagerAdapter = new EmojiPagerAdapter(mActivity, mComposeMessage, new ArrayList<EmojiGroup>(emojiGroups));
mEmojiPager.setAdapter(emojiPagerAdapter);
ImageView btnEmoji = (ImageView)findViewById(R.id.btnEmoji);
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);
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getPointerCount() > 1 && event.getAction() == MotionEvent.ACTION_DOWN)
{
ChatView.this.closeChatSession();
return true;
}
return false;
}
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() {
mCursor.close();
mCursor = null;
mMessageAdapter.changeCursor(null);
}
void updateChat() {
setViewType(VIEW_TYPE_CHAT);
updateContactInfo();
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();
setDeliveryIcon();
}
private void updateContactInfo() {
mProviderId = mCursor.getLong(PROVIDER_COLUMN);
mAccountId = mCursor.getLong(ACCOUNT_COLUMN);
mPresenceStatus = mCursor.getInt(PRESENCE_STATUS_COLUMN);
mType = mCursor.getInt(TYPE_COLUMN);
mRemoteNickname = mCursor.getString(NICKNAME_COLUMN);
mRemoteAddress = mCursor.getString(USERNAME_COLUMN);
}
/*
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 = mActivity.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(',');
}
}
}
mActivity.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(")");
mActivity.setTitle(buf.toString());
Drawable avatar = loadAvatar(mUserName);
// if (avatar != null)
// mActivity.setHomeIcon(avatar);
// }
}*/
private void setStatusIcon() {
if (mType == 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 setDeliveryIcon() {
if (mExpectingDelivery) {
mDeliveryIcon.setVisibility(VISIBLE);
} else {
mDeliveryIcon.setVisibility(GONE);
}
}
private void deleteChat ()
{
Uri chatUri = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mLastChatId);
mActivity.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);
mCursor = mActivity.getContentResolver().query(contactUri, CHAT_PROJECTION, null, null, null);
if (!mCursor.moveToFirst()) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("Failed to query chat: " + chatId);
}
mLastChatId = -1;
} else {
updateContactInfo();
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 = mActivity.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);
}
// mActivity.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));
mActivity.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));
mActivity.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 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(true);
// TODO: async query?
Cursor cursor = getMessageCursor();
if (cursor != null) {
cursor.requery();
}
}
private Cursor getMessageCursor() {
return mMessageAdapter == null ? null : mMessageAdapter.getCursor();
}
public void closeChatSession() {
if (getChatSession() != null) {
try {
getChatSession().leave();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
}
}
deleteChat();
}
public void viewProfile() {
if (getChatId() == -1)
return;
Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, getChatId());
Intent intent = new Intent(Intent.ACTION_VIEW, data);
intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId);
intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId);
if (mRemoteAddress != null)
intent.putExtra("jid", mRemoteAddress);
mActivity.startActivity(intent);
}
public void blockContact() {
// TODO: unify with codes in ContactListView
DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
try {
IImConnection conn = mApp.getConnection(mProviderId);
IContactListManager manager = conn.getContactListManager();
manager.blockContact(Address.stripResource(mRemoteAddress));
// mActivity.finish();
} catch (RemoteException 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() {
IImConnection conn = mApp.getConnection(mProviderId);
if (conn != null) {
try {
IChatSessionManager sessionMgr = conn.getChatSessionManager();
if (sessionMgr != null) {
IChatSession session = sessionMgr.createChatSession(Address.stripResource(mRemoteAddress));
return session;
}
} catch (RemoteException e) {
mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
}
}
return null;
}
private IChatSession getChatSession() {
IImConnection conn = mApp.getConnection(mProviderId);
if (conn != null) {
try {
IChatSessionManager sessionMgr = conn.getChatSessionManager();
if (sessionMgr != null) {
IChatSession session = sessionMgr.getChatSession(Address.stripResource(mRemoteAddress));
// if (session == null)
// session = sessionMgr.createChatSession(Address.stripResource(mRemoteAddress));
return session;
}
} catch (RemoteException e) {
mHandler.showServiceErrorAlert(e.getLocalizedMessage());
LogCleaner.error(ImApp.LOG_TAG, "send message error",e);
}
}
return null;
}
boolean isGroupChat() {
boolean isGroupChat = false;
if (mCurrentChatSession != null)
{
try {
isGroupChat = mCurrentChatSession.isGroupChatSession();
}
catch (Exception e){}
}
return isGroupChat;
}
void sendMessage() {
mEmojiPager.setVisibility(View.GONE);
mActionBox.setVisibility(View.GONE);
String msg = mComposeMessage.getText().toString();
if (TextUtils.isEmpty(msg.trim())) {
return;
}
IChatSession session = getChatSession();
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 sendMessage(String msg) {
if (TextUtils.isEmpty(msg.trim())) {
return;
}
if (getChatSession() != null) {
try {
getChatSession().sendMessage(msg);
requeryCursor();
} 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);
getChatSession().setDataListener(mDataListenerAdapter);
}
IImConnection conn = mApp.getConnection(mProviderId);
if (conn != null) {
IContactListManager listMgr = conn.getContactListManager();
listMgr.registerContactListListener(mContactListListener);
}
} catch (RemoteException 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().setDataListener(null);
getChatSession().unregisterChatListener(mChatListener);
}
IImConnection conn = mApp.getConnection(mProviderId);
if (conn != null) {
IContactListManager listMgr = conn.getContactListManager();
listMgr.unregisterContactListListener(mContactListListener);
}
} catch (RemoteException e) {
Log.w(ImApp.LOG_TAG, "<ChatView> unregisterChatListener fail:" + e.getMessage());
}
}
void updateWarningView()
{
updateWarningView(false);
}
void updateWarningView(boolean overrideUserTouch) {
int visibility = View.GONE;
int iconVisibility = View.GONE;
String message = null;
boolean isConnected;
if (overrideUserTouch)
mOtrSwitchTouched = false;
if (this.isGroupChat())
{
//no OTR in group chat
mStatusWarningView.setVisibility(View.GONE);
return;
}
try {
IImConnection conn = mApp.getConnection(mProviderId);
isConnected = (conn == null) ? false : conn.getState() == ImConnection.LOGGED_IN;
} catch (RemoteException e) {
isConnected = false;
}
if (isConnected && mCurrentChatSession != null) {
try {
IOtrChatSession 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 (mType == Imps.Contacts.TYPE_GROUP) {
visibility = View.GONE;
message = "";
}
else if (mType == Imps.Contacts.TYPE_TEMPORARY) {
visibility = View.VISIBLE;
message = mContext.getString(R.string.contact_not_in_list_warning, mRemoteNickname);
} else if (mPresenceStatus == Imps.Presence.OFFLINE) {
visibility = View.VISIBLE;
message = mContext.getString(R.string.contact_offline_warning, mRemoteNickname);
} else {
visibility = View.VISIBLE;
}
if (mPresenceStatus == Imps.Presence.OFFLINE)
{
mWarningText.setTextColor(Color.WHITE);
mStatusWarningView.setBackgroundColor(Color.DKGRAY);
message = mContext.getString(R.string.presence_offline);
}
else if (mLastSessionStatus == SessionStatus.PLAINTEXT) {
mSendButton.setImageResource(R.drawable.ic_send_holo_light);
mComposeMessage.setHint(R.string.compose_hint);
if (!mOtrSwitchTouched)
{
mOtrSwitch.setOnCheckedChangeListener(null);
mOtrSwitch.setChecked(false);
mOtrSwitch.setOnCheckedChangeListener(mOtrListener);
}
mWarningText.setTextColor(Color.WHITE);
mStatusWarningView.setBackgroundResource(R.color.otr_red);
message = mContext.getString(R.string.otr_session_status_plaintext);
}
else if (mLastSessionStatus == SessionStatus.ENCRYPTED) {
mSendButton.setImageResource(R.drawable.ic_send_secure);
mOtrSwitch.setOnCheckedChangeListener(null);
mOtrSwitch.setChecked(true);
mOtrSwitch.setOnCheckedChangeListener(mOtrListener);
try {
IOtrChatSession 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);
}
}
String rFingerprint = OtrChatSession.getRemoteFingerprint();
boolean rVerified = OtrChatSession.isKeyVerified(mRemoteAddress);
if (rFingerprint != null) {
if (!rVerified) {
message = mContext.getString(R.string.otr_session_status_encrypted);
mWarningText.setTextColor(Color.BLACK);
mStatusWarningView.setBackgroundResource(R.color.otr_yellow);
} else {
message = mContext.getString(R.string.otr_session_status_verified);
mWarningText.setTextColor(Color.BLACK);
mStatusWarningView.setBackgroundResource(R.color.otr_green);
}
} else {
mWarningText.setTextColor(Color.WHITE);
mStatusWarningView.setBackgroundResource(R.color.otr_red);
message = mContext.getString(R.string.otr_session_status_plaintext);
}
} catch (RemoteException e) {
LogCleaner.error(ImApp.LOG_TAG, "error getting OTR session in ChatView", e);
}
} else if (mLastSessionStatus == SessionStatus.FINISHED) {
mSendButton.setImageResource(R.drawable.ic_send_holo_light);
mComposeMessage.setHint(R.string.compose_hint);
if (!mOtrSwitchTouched)
{
mOtrSwitch.setOnCheckedChangeListener(null);
mOtrSwitch.setChecked(true);
mOtrSwitch.setOnCheckedChangeListener(mOtrListener);
}
mWarningText.setTextColor(Color.WHITE);
mStatusWarningView.setBackgroundColor(Color.DKGRAY);
message = mContext.getString(R.string.otr_session_status_finished);
}
} else {
mSendButton.setImageResource(R.drawable.ic_send_holo_light);
mComposeMessage.setHint(R.string.compose_hint);
if (!mOtrSwitchTouched)
{
mOtrSwitch.setOnCheckedChangeListener(null);
mOtrSwitch.setChecked(false);
mOtrSwitch.setOnCheckedChangeListener(mOtrListener);
}
visibility = View.VISIBLE;
iconVisibility = View.VISIBLE;
mWarningText.setTextColor(Color.WHITE);
mStatusWarningView.setBackgroundColor(Color.DKGRAY);
message = mContext.getString(R.string.disconnected_warning);
}
mStatusWarningView.setVisibility(visibility);
if (visibility == View.VISIBLE) {
mWarningIcon.setVisibility(iconVisibility);
mWarningText.setText(message);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
userActionDetected();
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
userActionDetected();
return super.dispatchTouchEvent(ev);
}
@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;
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;
}
public int getType(int arg0) {
// TODO Auto-generated method stub
return 0;
}
}
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 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);
}
@Override
public void changeCursor(Cursor cursor) {
if (getCursor() != null && (!getCursor().isClosed()))
getCursor().close();
super.changeCursor(cursor);
if (cursor != null) {
resolveColumnIndex(cursor);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.new_message_item, parent, false);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
MessageView messageView = (MessageView) view;
mType = cursor.getInt(mTypeColumn);
String nickname = isGroupChat() ? cursor.getString(mNicknameColumn) : mRemoteNickname;
String body = cursor.getString(mBodyColumn);
long delta = cursor.getLong(mDeltaColumn);
boolean showTimeStamp = (delta > SHOW_TIME_STAMP_INTERVAL);
long timestamp = cursor.getLong(mDateColumn);
Date date = showTimeStamp ? new Date(timestamp) : null;
boolean isDelivered = cursor.getLong(mDeliveredColumn) > 0;
boolean showDelivery = ((System.currentTimeMillis() - timestamp) > SHOW_DELIVERY_INTERVAL);
DeliveryState deliveryState = DeliveryState.NEUTRAL;
if (showDelivery && !isDelivered && mExpectingDelivery) {
deliveryState = DeliveryState.UNDELIVERED;
}
EncryptionState encState = EncryptionState.NONE;
if (mType == Imps.MessageType.INCOMING_ENCRYPTED)
{
mType = Imps.MessageType.INCOMING;
encState = EncryptionState.ENCRYPTED;
}
else if (mType == Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED)
{
mType = Imps.MessageType.INCOMING;
encState = EncryptionState.ENCRYPTED_AND_VERIFIED;
}
else if (mType == Imps.MessageType.OUTGOING_ENCRYPTED)
{
mType = Imps.MessageType.OUTGOING;
encState = EncryptionState.ENCRYPTED;
}
else if (mType == Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED)
{
mType = Imps.MessageType.OUTGOING;
encState = EncryptionState.ENCRYPTED_AND_VERIFIED;
}
switch (mType) {
case Imps.MessageType.INCOMING:
if (body != null)
{
messageView.bindIncomingMessage(mRemoteAddress, nickname, body, date, mMarkup, isScrolling(), encState, isGroupChat());
}
break;
case Imps.MessageType.OUTGOING:
case Imps.MessageType.POSTPONED:
int errCode = cursor.getInt(mErrCodeColumn);
if (errCode != 0) {
messageView.bindErrorMessage(errCode);
} else {
messageView.bindOutgoingMessage(null, body, date, mMarkup, isScrolling(),
deliveryState, encState);
}
break;
default:
messageView.bindPresenceMessage(mRemoteAddress, mType, isGroupChat(), isScrolling());
}
// updateWarningView();
if (!mExpectingDelivery && isDelivered) {
log("Setting delivery icon");
mExpectingDelivery = true;
setDeliveryIcon();
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;
}
class DataAdapter extends IDataListener.Stub {
@Override
public void onTransferComplete(String from, String url, String type, String filePath) {
// TODO have a specific notifier for files / data
//String username = from.getScreenName();
File file = new File(filePath);
try {
Message msg = Message.obtain(mTransferHandler, 3);
msg.getData().putString("path", file.getCanonicalPath());
msg.getData().putString("type", type);
mTransferHandler.sendMessage(msg);
} catch (IOException e) {
mHandler.showAlert("Transfer Error", "Unable to read file to storage");
OtrDebugLogger.log("error reading file", e);
}
}
@Override
public void onTransferFailed(String from, String url, String reason) {
String[] path = url.split("/");
String sanitizedPath = SystemServices.sanitize(path[path.length - 1]);
Message msg = Message.obtain(mTransferHandler, 2);
msg.getData().putInt("progress", (int)0);
msg.getData().putString("status", sanitizedPath + " transfer failed: " + reason);
mTransferHandler.sendMessage(msg);
}
@Override
public void onTransferProgress(String from, String url, float percentF) {
long percent = (long)(100.00*percentF);
String[] path = url.split("/");
String sanitizedPath = SystemServices.sanitize(path[path.length - 1]);
Message msg = Message.obtain(mTransferHandler, 2);
msg.getData().putInt("progress", (int)percent);
msg.getData().putString("status", sanitizedPath);
mTransferHandler.sendMessage(msg);
}
private boolean mAcceptTransfer = false;
private boolean mWaitingForResponse = false;
@Override
public boolean onTransferRequested(String from, String to, String transferUrl) {
mAcceptTransfer = false;
mWaitingForResponse = true;
Message msg = Message.obtain(mTransferHandler, 1);
msg.getData().putString("from", from);
msg.getData().putString("url", transferUrl);
mTransferHandler.sendMessage(msg);
while (mWaitingForResponse)
{
try { Thread.sleep(500);} catch (Exception e){}
}
return mAcceptTransfer;
}
private Handler mTransferHandler = new Handler ()
{
@Override
public void handleMessage(Message msg) {
if (msg.what == 1)
{
String transferUrl = msg.getData().getString("url");
String transferFrom = msg.getData().getString("from");
String[] path = transferUrl.split("/");
String sanitizedPath = SystemServices.sanitize(path[path.length - 1]);
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(mContext.getString(R.string.file_transfer));
builder.setMessage(transferFrom + ' ' + mContext.getString(R.string.wants_to_send_you_the_file)
+ " '" + sanitizedPath + "'. " + mContext.getString(R.string.accept_transfer_));
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mAcceptTransfer = true;
mWaitingForResponse = false;
NOTIFY_DOWNLOAD_ID++;
dialog.dismiss();
}
});
builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mAcceptTransfer = false;
mWaitingForResponse = false;
// Do nothing
dialog.dismiss();
}
});
AlertDialog alert = builder.create();
alert.show();
}
else if (msg.what == 2) //progress update
{
int progressValue = msg.getData().getInt("progress");
String progressText = msg.getData().getString("status");
if (mNotifyManager == null)
{
mNotifyManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(mContext);
mBuilder.setContentTitle(mContext.getString(R.string.file_transfer));
mBuilder.setTicker(mContext.getString(R.string.transfer_in_progress) + ": " + progressText);
mBuilder .setSmallIcon(R.drawable.ic_secure_xfer);
mBuilder.setContentIntent(PendingIntent.getActivity(mActivity,0,new Intent(mContext,NewChatActivity.class),0));
}
mBuilder.setContentText(mContext.getString(R.string.transfer_in_progress) + ": " + progressText);
mBuilder.setProgress(100, progressValue, false);
mNotifyManager.notify(NOTIFY_DOWNLOAD_ID, mBuilder.build());
}
else if (msg.what == 3)
{
String filePath = msg.getData().getString("path");
String fileType = msg.getData().getString("type");
if (mNotifyManager == null)
{
mNotifyManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(mContext);
mBuilder.setContentTitle(mContext.getString(R.string.file_transfer));
mBuilder .setSmallIcon(R.drawable.ic_secure_xfer);
}
String[] path = filePath.split("/");
String sanitizedPath = SystemServices.sanitize(path[path.length - 1]);
Uri fileUri = Scanner.scan(mContext, filePath);
if (fileType == null)
{
String fileExt = null;
String[] fileParts = filePath.split("\\.");
if (fileParts.length > 0)
{
fileExt = fileParts[fileParts.length-1];
MimeTypeMap mimeTypeMap =
MimeTypeMap.getSingleton();
fileType = mimeTypeMap.getMimeTypeFromExtension(fileExt);
}
}
Intent intentView = new Intent(Intent.ACTION_VIEW);
if (fileType != null)
{
// String generalType = fileType.split("/")[0] + "/*";
intentView.setDataAndType(fileUri,fileType);
}
else
intentView.setDataAndType(fileUri,"*/*");
PendingIntent contentIntent =
PendingIntent.getActivity(mActivity, 0, intentView, 0);
mBuilder.setContentIntent(contentIntent);
mBuilder.setLights(0xff00ff00, 300, 1000);
String status = mContext.getString(R.string.transfer_complete) + ": " + sanitizedPath;
mBuilder.setContentText(status)
// Removes the progress bar
.setProgress(0,0,false)
.setTicker(status)
.setWhen(System.currentTimeMillis());
mNotifyManager.notify(NOTIFY_DOWNLOAD_ID, mBuilder.build());
}
super.handleMessage(msg);
}
};
NotificationManager mNotifyManager;
NotificationCompat.Builder mBuilder;
int NOTIFY_DOWNLOAD_ID = 898989;
}
public void onServiceConnected() {
if (!isServiceUp) {
bindChat(mLastChatId);
startListening();
}
}
}