blob: ebc1038e9274b713148d3f33e26524bd25589df7 [file] [log] [blame]
/*
* 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 com.android.im.app;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
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.content.res.Configuration;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.CursorIndexOutOfBoundsException;
import android.database.DataSetObserver;
import android.database.CharArrayBuffer;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Browser;
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.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView.OnItemClickListener;
import com.android.im.IChatListener;
import com.android.im.IChatSession;
import com.android.im.IChatSessionListener;
import com.android.im.IChatSessionManager;
import com.android.im.IContactList;
import com.android.im.IContactListListener;
import com.android.im.IContactListManager;
import com.android.im.IImConnection;
import com.android.im.R;
import com.android.im.app.adapter.ChatListenerAdapter;
import com.android.im.app.adapter.ChatSessionListenerAdapter;
import com.android.im.engine.Contact;
import com.android.im.engine.ImConnection;
import com.android.im.engine.ImErrorInfo;
import com.android.im.plugin.BrandingResourceIDs;
import com.android.im.provider.Imps;
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,
};
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 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);
Markup mMarkup;
Activity mScreen;
ImApp mApp;
SimpleAlertHandler mHandler;
Cursor mCursor;
private ImageView mStatusIcon;
private TextView mTitle;
/*package*/ListView mHistory;
EditText mEdtInput;
private Button mSendButton;
private View mStatusWarningView;
private ImageView mWarningIcon;
private TextView mWarningText;
private MessageAdapter mMessageAdapter;
private IChatSessionManager mChatSessionMgr;
private IChatSessionListener mChatSessionListener;
private IChatSession mChatSession;
private long mChatId;
int mType;
String mNickName;
String mUserName;
long mProviderId;
long mAccountId;
long mInvitationId;
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 = 60 * 1000; // 1 minute
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) {
Cursor cursor = new DeltaCursor(c);
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
log("onQueryComplete: cursor.count=" + cursor.getCount());
}
mMessageAdapter.changeCursor(cursor);
}
}
private QueryHandler mQueryHandler;
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){
return;
}
final ArrayList<String> linkUrls = new ArrayList<String>(links.length);
for (URLSpan u : links) {
linkUrls.add(u.getURL());
}
ArrayAdapter<String> a = new ArrayAdapter<String>(mScreen,
android.R.layout.select_dialog_item, linkUrls);
AlertDialog.Builder b = new AlertDialog.Builder(mScreen);
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(Browser.EXTRA_APPLICATION_ID, mScreen.getPackageName());
mScreen.startActivity(intent);
}
});
b.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
b.show();
}
};
private IChatListener mChatListener = new ChatListenerAdapter() {
@Override
public void onIncomingMessage(IChatSession ses,
com.android.im.engine.Message msg) {
scheduleRequery(0);
}
@Override
public void onContactJoined(IChatSession ses, Contact contact) {
scheduleRequery(0);
}
@Override
public void onContactLeft(IChatSession ses, Contact contact) {
scheduleRequery(0);
}
@Override
public void onSendMessageError(IChatSession ses,
com.android.im.engine.Message msg, ImErrorInfo error) {
scheduleRequery(0);
}
};
private Runnable mUpdateChatCallback = new Runnable() {
public void run() {
if (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().getFullName().equals(mUserName)) {
mHandler.post(mUpdateChatCallback);
scheduleRequery(0);
break;
}
}
}
};
static final void log(String msg) {
Log.d(ImApp.LOG_TAG, "<ChatView> " +msg);
}
public ChatView(Context context, AttributeSet attrs) {
super(context, attrs);
mScreen = (Activity) context;
mApp = ImApp.getApplication(mScreen);
mHandler = new ChatViewHandler();
}
void registerForConnEvents() {
mApp.registerForConnEvents(mHandler);
}
void unregisterForConnEvents() {
mApp.unregisterForConnEvents(mHandler);
}
@Override
protected void onFinishInflate() {
mStatusIcon = (ImageView) findViewById(R.id.statusIcon);
mTitle = (TextView) findViewById(R.id.title);
mHistory = (ListView) findViewById(R.id.history);
mEdtInput = (EditText) findViewById(R.id.edtInput);
mSendButton = (Button)findViewById(R.id.btnSend);
mHistory.setOnItemClickListener(mOnItemClickListener);
mStatusWarningView = findViewById(R.id.warning);
mWarningIcon = (ImageView)findViewById(R.id.warningIcon);
mWarningText = (TextView)findViewById(R.id.warningText);
Button acceptInvitation = (Button)findViewById(R.id.btnAccept);
Button declineInvitation= (Button)findViewById(R.id.btnDecline);
Button approveSubscription = (Button)findViewById(R.id.btnApproveSubscription);
Button declineSubscription = (Button)findViewById(R.id.btnDeclineSubscription);
acceptInvitation.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
acceptInvitation();
}
});
declineInvitation.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
declineInvitation();
}
});
approveSubscription.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
approveSubscription();
}
});
declineSubscription.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
declineSubscription();
}
});
mEdtInput.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()) {
mEdtInput.append("\n");
return true;
}
}
}
return false;
}
});
mEdtInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event != null) {
if (event.isAltPressed()) {
return false;
}
}
sendMessage();
return true;
}
});
// TODO: this is a hack to implement BUG #1611278, when dispatchKeyEvent() works with
// the soft keyboard, we should remove this hack.
mEdtInput.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();
}
});
}
public void onResume(){
if (mViewType == VIEW_TYPE_CHAT) {
Cursor cursor = getMessageCursor();
if (cursor == null) {
startQuery();
} else {
requeryCursor();
}
updateWarningView();
}
registerChatListener();
registerForConnEvents();
}
public void onPause(){
Cursor cursor = getMessageCursor();
if (cursor != null) {
cursor.deactivate();
}
cancelRequery();
if (mViewType == VIEW_TYPE_CHAT && mChatSession != null) {
try {
mChatSession.markAsRead();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
unregisterChatListener();
unregisterForConnEvents();
unregisterChatSessionListener();
}
private void closeSoftKeyboard() {
InputMethodManager inputMethodManager =
(InputMethodManager)mApp.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(mEdtInput.getWindowToken(), 0);
}
void updateChat() {
setViewType(VIEW_TYPE_CHAT);
long oldChatId = mChatId;
updateContactInfo();
setStatusIcon();
setTitle();
IImConnection conn = mApp.getConnection(mProviderId);
if (conn == null) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) log("Connection has been signed out");
mScreen.finish();
return;
}
BrandingResources brandingRes = mApp.getBrandingResource(mProviderId);
mHistory.setBackgroundDrawable(
brandingRes.getDrawable(BrandingResourceIDs.DRAWABLE_CHAT_WATERMARK));
if (mMarkup == null) {
mMarkup = new Markup(brandingRes);
}
if (mMessageAdapter == null) {
mMessageAdapter = new MessageAdapter(mScreen, null);
mHistory.setAdapter(mMessageAdapter);
}
// only change the message adapter when we switch to another chat
if (mChatId != oldChatId) {
startQuery();
mEdtInput.setText("");
}
updateWarningView();
}
private void updateContactInfo() {
mChatId = mCursor.getLong(CONTACT_ID_COLUMN);
mProviderId = mCursor.getLong(PROVIDER_COLUMN);
mAccountId = mCursor.getLong(ACCOUNT_COLUMN);
mPresenceStatus = mCursor.getInt(PRESENCE_STATUS_COLUMN);
mType = mCursor.getInt(TYPE_COLUMN);
mUserName = mCursor.getString(USERNAME_COLUMN);
mNickName = mCursor.getString(NICKNAME_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 = mScreen.getContentResolver();
Cursor c = cr.query(memberUri, projection, null, null, null);
StringBuilder buf = new StringBuilder();
if(c != null) {
while(c.moveToNext()) {
buf.append(c.getString(0));
if(!c.isLast()) {
buf.append(',');
}
}
c.close();
}
mTitle.setText(mContext.getString(R.string.chat_with, buf.toString()));
} else {
mTitle.setText(mContext.getString(R.string.chat_with, mNickName));
}
}
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));
}
}
public void bindChat(long chatId) {
if (mCursor != null) {
mCursor.deactivate();
}
Uri contactUri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, chatId);
mCursor = mScreen.managedQuery(contactUri, CHAT_PROJECTION, null, null);
if (mCursor == null || !mCursor.moveToFirst()) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
log("Failed to query chat: " + chatId);
}
mScreen.finish();
return;
} else {
mChatSession = getChatSession(mCursor);
updateChat();
registerChatListener();
}
}
public void bindInvitation(long invitationId) {
Uri uri = ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, invitationId);
ContentResolver cr = mScreen.getContentResolver();
Cursor cursor = cr.query(uri, INVITATION_PROJECT, null, null, null);
if (cursor == null || !cursor.moveToFirst()) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
log("Failed to query invitation: " + invitationId);
}
mScreen.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));
mTitle.setText(mContext.getString(R.string.chat_with, sender));
}
if (cursor != null) {
cursor.close();
}
}
public void bindSubscription(long providerId, String from) {
mProviderId = providerId;
mUserName = 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));
mTitle.setText(mContext.getString(R.string.chat_with, displayableAddr));
mApp.dismissChatNotification(providerId, from);
}
void acceptInvitation() {
try {
IImConnection conn = mApp.getConnection(mProviderId);
if (conn != null) {
// register a chat session listener and wait for a group chat
// session to be created after we accept the invitation.
registerChatSessionListener();
conn.acceptInvitation(mInvitationId);
}
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
void declineInvitation() {
try {
IImConnection conn = mApp.getConnection(mProviderId);
if (conn != null) {
conn.rejectInvitation(mInvitationId);
}
mScreen.finish();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
void approveSubscription() {
IImConnection conn = mApp.getConnection(mProviderId);
try {
IContactListManager manager = conn.getContactListManager();
manager.approveSubscription(mUserName);
} catch (RemoteException ex) {
mHandler.showServiceErrorAlert();
}
mScreen.finish();
}
void declineSubscription() {
IImConnection conn = mApp.getConnection(mProviderId);
try {
IContactListManager manager = conn.getContactListManager();
manager.declineSubscription(mUserName);
} catch (RemoteException ex) {
mHandler.showServiceErrorAlert();
}
mScreen.finish();
}
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) {
mEdtInput.setEnabled(enabled);
mSendButton.setEnabled(enabled);
if (enabled) {
mEdtInput.requestFocus();
} else {
mHistory.setAdapter(null);
}
}
private void startQuery() {
if (mQueryHandler == null) {
mQueryHandler = new QueryHandler(mContext);
} else {
// Cancel any pending queries
mQueryHandler.cancelOperation(QUERY_TOKEN);
}
Uri uri = Imps.Messages.getContentUriByThreadId(mChatId);
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
log("queryCursor: uri=" + uri);
}
mQueryHandler.startQuery(QUERY_TOKEN, null,
uri,
null,
null /* selection */,
null /* selection args */,
null);
}
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;
}
// TODO: async query?
Cursor cursor = getMessageCursor();
if (cursor != null) {
cursor.requery();
}
}
private Cursor getMessageCursor() {
return mMessageAdapter == null ? null : mMessageAdapter.getCursor();
}
public void insertSmiley(String smiley) {
mEdtInput.append(mMarkup.applyEmoticons(smiley));
}
public void closeChatSession() {
if (mChatSession != null) {
try {
mChatSession.leave();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
} else {
// the conversation is already closed, clear data in database
ContentResolver cr = mContext.getContentResolver();
cr.delete(ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mChatId),
null, null);
}
mScreen.finish();
}
public void closeChatSessionIfInactive() {
if (mChatSession != null) {
try {
mChatSession.leaveIfInactive();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
}
public void viewProfile() {
Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, mChatId);
Intent intent = new Intent(Intent.ACTION_VIEW, data);
mScreen.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(mUserName);
mScreen.finish();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
};
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, mNickName))
.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 String getUserName() {
return mUserName;
}
public long getChatId () {
try {
return mChatSession == null ? -1 : mChatSession.getId();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
return -1;
}
}
public IChatSession getCurrentChatSession() {
return mChatSession;
}
private IChatSessionManager getChatSessionManager(long providerId) {
if (mChatSessionMgr == null) {
IImConnection conn = mApp.getConnection(providerId);
if (conn != null) {
try {
mChatSessionMgr = conn.getChatSessionManager();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
}
return mChatSessionMgr;
}
private IChatSession getChatSession(Cursor cursor) {
long providerId = cursor.getLong(PROVIDER_COLUMN);
String username = cursor.getString(USERNAME_COLUMN);
IChatSessionManager sessionMgr = getChatSessionManager(providerId);
if (sessionMgr != null) {
try {
return sessionMgr.getChatSession(username);
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
return null;
}
boolean isGroupChat() {
return Imps.Contacts.TYPE_GROUP == mType;
}
void sendMessage() {
String msg = mEdtInput.getText().toString();
if (TextUtils.isEmpty(msg.trim())) {
return;
}
if (mChatSession != null) {
try {
mChatSession.sendMessage(msg);
mEdtInput.setText("");
mEdtInput.requestFocus();
requeryCursor();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
// Close the soft on-screen keyboard if we're in landscape mode so the user can see the
// conversation.
Configuration config = getResources().getConfiguration();
if (config.orientation == config.ORIENTATION_LANDSCAPE) {
closeSoftKeyboard();
}
}
void registerChatListener() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
log("registerChatListener");
}
try {
if (mChatSession != null) {
mChatSession.registerChatListener(mChatListener);
}
IImConnection conn = mApp.getConnection(mProviderId);
if (conn != null) {
IContactListManager listMgr = conn.getContactListManager();
listMgr.registerContactListListener(mContactListListener);
}
mApp.dismissChatNotification(mProviderId, mUserName);
} 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");
}
try {
if (mChatSession != null) {
mChatSession.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 registerChatSessionListener() {
IChatSessionManager sessionMgr = getChatSessionManager(mProviderId);
if (sessionMgr != null) {
mChatSessionListener = new ChatSessionListener();
try {
sessionMgr.registerChatSessionListener(mChatSessionListener);
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
}
void unregisterChatSessionListener() {
if (mChatSessionListener != null) {
try {
IChatSessionManager sessionMgr = getChatSessionManager(mProviderId);
sessionMgr.unregisterChatSessionListener(mChatSessionListener);
// We unregister the listener when the chat session we are
// waiting for has been created or the activity is stopped.
// Clear the listener so that we won't unregister the listener
// twice.
mChatSessionListener = null;
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
}
void updateWarningView() {
int visibility = View.GONE;
int iconVisibility = View.GONE;
String message = null;
boolean isConnected;
try {
IImConnection conn = mApp.getConnection(mProviderId);
isConnected = (conn == null) ? false
: conn.getState() != ImConnection.SUSPENDED;
} catch (RemoteException e) {
// do nothing
return;
}
if (isConnected) {
if (mType == Imps.Contacts.TYPE_TEMPORARY) {
visibility = View.VISIBLE;
message = mContext.getString(R.string.contact_not_in_list_warning, mNickName);
} else if (mPresenceStatus == Imps.Presence.OFFLINE) {
visibility = View.VISIBLE;
message = mContext.getString(R.string.contact_offline_warning, mNickName);
}
} else {
visibility = View.VISIBLE;
iconVisibility = View.VISIBLE;
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() {
if (mChatSession != null) {
try {
mChatSession.markAsRead();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
}
private final class ChatViewHandler extends SimpleAlertHandler {
public ChatViewHandler() {
super(mScreen);
}
@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_LOGGED_IN:
log("Connection resumed");
updateWarningView();
return;
case ImApp.EVENT_CONNECTION_SUSPENDED:
log("Connection suspended");
updateWarningView();
return;
}
super.handleMessage(msg);
}
}
class ChatSessionListener extends ChatSessionListenerAdapter {
@Override
public void onChatSessionCreated(IChatSession session) {
try {
if (session.isGroupChatSession()) {
final long id = session.getId();
unregisterChatSessionListener();
mHandler.post(new Runnable() {
public void run() {
bindChat(id);
}});
}
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
}
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 boolean deleteRow() {
return mInnerCursor.deleteRow();
}
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 boolean supportsUpdates() {
return mInnerCursor.supportsUpdates();
}
public boolean hasUpdates() {
return mInnerCursor.hasUpdates();
}
public boolean updateBlob(int columnIndex, byte[] value) {
if (columnIndex == mDeltaColumn) {
return false;
}
return mInnerCursor.updateBlob(columnIndex, value);
}
public boolean updateString(int columnIndex, String value) {
if (columnIndex == mDeltaColumn) {
return false;
}
return mInnerCursor.updateString(columnIndex, value);
}
public boolean updateShort(int columnIndex, short value) {
if (columnIndex == mDeltaColumn) {
return false;
}
return mInnerCursor.updateShort(columnIndex, value);
}
public boolean updateInt(int columnIndex, int value) {
if (columnIndex == mDeltaColumn) {
return false;
}
return mInnerCursor.updateInt(columnIndex, value);
}
public boolean updateLong(int columnIndex, long value) {
if (columnIndex == mDeltaColumn) {
return false;
}
return mInnerCursor.updateLong(columnIndex, value);
}
public boolean updateFloat(int columnIndex, float value) {
if (columnIndex == mDeltaColumn) {
return false;
}
return mInnerCursor.updateFloat(columnIndex, value);
}
public boolean updateDouble(int columnIndex, double value) {
if (columnIndex == mDeltaColumn) {
return false;
}
return mInnerCursor.updateDouble(columnIndex, value);
}
public boolean updateToNull(int columnIndex) {
if (columnIndex == mDeltaColumn) {
return false;
}
return mInnerCursor.updateToNull(columnIndex);
}
public boolean commitUpdates() {
return mInnerCursor.commitUpdates();
}
public boolean commitUpdates(Map<? extends Long,
? extends Map<String,Object>> values) {
return mInnerCursor.commitUpdates(values);
}
public void abortUpdates() {
mInnerCursor.abortUpdates();
}
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;
}
}
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 ChatBackgroundMaker mBgMaker;
private LayoutInflater mInflater;
public MessageAdapter(Activity context, Cursor c) {
super(context, c, false);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mBgMaker = new ChatBackgroundMaker(context);
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);
}
@Override
public void changeCursor(Cursor cursor) {
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 chatMsgView = (MessageView) view;
int type = cursor.getInt(mTypeColumn);
String contact = isGroupChat() ? cursor.getString(mNicknameColumn) : mNickName;
String body = cursor.getString(mBodyColumn);
long delta = cursor.getLong(mDeltaColumn);
boolean showTimeStamp = (delta > SHOW_TIME_STAMP_INTERVAL);
Date date = showTimeStamp ? new Date(cursor.getLong(mDateColumn)) : null;
switch (type) {
case Imps.MessageType.INCOMING:
chatMsgView.bindIncomingMessage(contact, body, date, mMarkup, isScrolling());
break;
case Imps.MessageType.OUTGOING:
case Imps.MessageType.POSTPONED:
int errCode = cursor.getInt(mErrCodeColumn);
if (errCode != 0) {
chatMsgView.bindErrorMessage(errCode);
} else {
chatMsgView.bindOutgoingMessage(body, date, mMarkup, isScrolling());
}
break;
default:
chatMsgView.bindPresenceMessage(contact, type, isGroupChat(), isScrolling());
}
if (!isScrolling()) {
mBgMaker.setBackground(chatMsgView, contact, type);
}
// 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 (cursor.getPosition() == cursor.getCount()-1) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
log("delta = " + delta + ", showTs=" + showTimeStamp);
}
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 (mChatSession != null) {
try {
mChatSession.markAsRead();
} catch (RemoteException e) {
mHandler.showServiceErrorAlert();
}
}
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;
}
}
}