blob: 3ac6c605e492516d1c192f98e47fcec63924fbdf [file] [log] [blame]
/*
* Copyright (C) 2007-2008 Esmertec AG.
* Copyright (C) 2007-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.service;
import com.android.im.IChatListener;
import com.android.im.IChatSession;
import com.android.im.engine.ChatGroup;
import com.android.im.engine.ChatGroupManager;
import com.android.im.engine.ChatSession;
import com.android.im.engine.Contact;
import com.android.im.engine.GroupListener;
import com.android.im.engine.GroupMemberListener;
import com.android.im.engine.ImConnection;
import com.android.im.engine.ImEntity;
import com.android.im.engine.ImErrorInfo;
import com.android.im.engine.Message;
import com.android.im.engine.MessageListener;
import com.android.im.engine.Presence;
import com.android.im.provider.Imps;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ChatSessionAdapter extends IChatSession.Stub {
private static final String NON_CHAT_MESSAGE_SELECTION = Imps.Messages.TYPE
+ "!=" + Imps.MessageType.INCOMING + " AND " + Imps.Messages.TYPE
+ "!=" + Imps.MessageType.OUTGOING;
static final String TAG = RemoteImService.TAG;
/**
* The registered remote listeners.
*/
final RemoteCallbackList<IChatListener> mRemoteListeners
= new RemoteCallbackList<IChatListener>();
ImConnectionAdapter mConnection;
ChatSessionManagerAdapter mChatManager;
ChatSession mAdaptee;
ListenerAdapter mListenerAdapter;
boolean mIsGroupChat;
StatusBarNotifier mStatusBarNotifier;
private ContentResolver mContentResolver;
/*package*/Uri mChatURI;
private Uri mMessageURI;
private boolean mConvertingToGroupChat;
private static final int MAX_HISTORY_COPY_COUNT = 10;
private HashMap<String, Integer> mContactStatusMap
= new HashMap<String, Integer>();
private boolean mHasUnreadMessages;
public ChatSessionAdapter(ChatSession adaptee,
ImConnectionAdapter connection) {
mAdaptee = adaptee;
mConnection = connection;
RemoteImService service = connection.getContext();
mContentResolver = service.getContentResolver();
mStatusBarNotifier = service.getStatusBarNotifier();
mChatManager = (ChatSessionManagerAdapter) connection.getChatSessionManager();
mListenerAdapter = new ListenerAdapter();
mAdaptee.addMessageListener(mListenerAdapter);
ImEntity participant = mAdaptee.getParticipant();
if(participant instanceof ChatGroup) {
init((ChatGroup)participant);
} else {
init((Contact)participant);
}
}
private void init(ChatGroup group) {
mIsGroupChat = true;
long groupId = insertGroupContactInDb(group);
group.addMemberListener(mListenerAdapter);
mMessageURI = Imps.Messages.getContentUriByThreadId(groupId);
mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, groupId);
insertOrUpdateChat(null);
for (Contact c : group.getMembers()) {
mContactStatusMap.put(c.getName(), c.getPresence().getStatus());
}
}
private void init(Contact contact) {
mIsGroupChat = false;
ContactListManagerAdapter listManager =
(ContactListManagerAdapter) mConnection.getContactListManager();
long contactId = listManager.queryOrInsertContact(contact);
mMessageURI = Imps.Messages.getContentUriByThreadId(contactId);
mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, contactId);
insertOrUpdateChat(null);
mContactStatusMap.put(contact.getName(), contact.getPresence().getStatus());
}
private ChatGroupManager getGroupManager() {
return mConnection.getAdaptee().getChatGroupManager();
}
public ChatSession getAdaptee() {
return mAdaptee;
}
public Uri getChatUri() {
return mChatURI;
}
public String[] getPariticipants() {
if (mIsGroupChat) {
Contact self = mConnection.getLoginUser();
ChatGroup group = (ChatGroup)mAdaptee.getParticipant();
List<Contact> members = group.getMembers();
String[] result = new String[members.size() - 1];
int index = 0;
for (Contact c : members) {
if (!c.equals(self)) {
result[index++] = c.getAddress().getFullName();
}
}
return result;
} else {
return new String[] {mAdaptee.getParticipant().getAddress().getFullName()};
}
}
/**
* Convert this chat session to a group chat. If it's already a group chat,
* nothing will happen. The method works in async mode and the registered
* listener will be notified when it's converted to group chat successfully.
*
* Note that the method is not thread-safe since it's always called from
* the UI and Android uses single thread mode for UI.
*/
public void convertToGroupChat() {
if (mIsGroupChat || mConvertingToGroupChat) {
return;
}
mConvertingToGroupChat = true;
new ChatConvertor().convertToGroupChat();
}
public boolean isGroupChatSession() {
return mIsGroupChat;
}
public String getName() {
return mAdaptee.getParticipant().getAddress().getScreenName();
}
public String getAddress() {
return mAdaptee.getParticipant().getAddress().getFullName();
}
public long getId() {
return ContentUris.parseId(mChatURI);
}
public void inviteContact(String contact) {
if(!mIsGroupChat){
return;
}
ContactListManagerAdapter listManager =
(ContactListManagerAdapter) mConnection.getContactListManager();
Contact invitee = listManager.getContactByAddress(contact);
if(invitee == null) {
ImErrorInfo error = new ImErrorInfo(ImErrorInfo.ILLEGAL_CONTACT_ADDRESS,
"Cannot find contact with address: " + contact);
mListenerAdapter.onError((ChatGroup)mAdaptee.getParticipant(), error);
} else {
getGroupManager().inviteUserAsync((ChatGroup)mAdaptee.getParticipant(),
invitee);
}
}
public void leave() {
if (mIsGroupChat) {
getGroupManager().leaveChatGroupAsync((ChatGroup)mAdaptee.getParticipant());
}
mContentResolver.delete(mMessageURI, null, null);
mContentResolver.delete(mChatURI, null, null);
mStatusBarNotifier.dismissChatNotification(
mConnection.getProviderId(), getAddress());
mChatManager.closeChatSession(this);
}
public void leaveIfInactive() {
if (mAdaptee.getHistoryMessages().isEmpty()) {
leave();
}
}
public void sendMessage(String text) {
if (mConnection.getState() == ImConnection.SUSPENDED) {
// connection has been suspended, save the message without send it
insertMessageInDb(null, text, -1, Imps.MessageType.POSTPONED);
return;
}
Message msg = new Message(text);
mAdaptee.sendMessageAsync(msg);
long now = System.currentTimeMillis();
insertMessageInDb(null, text, now, Imps.MessageType.OUTGOING);
}
void sendPostponedMessages() {
String[] projection = new String[] {
BaseColumns._ID,
Imps.Messages.BODY,
Imps.Messages.DATE,
Imps.Messages.TYPE,
};
String selection = "messages.type=?";
Cursor c = mContentResolver.query(mMessageURI, projection, selection,
new String[]{Integer.toString(Imps.MessageType.POSTPONED)}, null);
if (c == null) {
Log.e(TAG, "Query error while querying postponed messages");
return;
}
while (c.moveToNext()) {
String body = c.getString(1);
mAdaptee.sendMessageAsync(new Message(body));
c.updateLong(2, System.currentTimeMillis());
c.updateInt(3, Imps.MessageType.OUTGOING);
}
c.commitUpdates();
c.close();
}
public void registerChatListener(IChatListener listener) {
if (listener != null) {
mRemoteListeners.register(listener);
}
}
public void unregisterChatListener(IChatListener listener) {
if (listener != null) {
mRemoteListeners.unregister(listener);
}
}
public void markAsRead() {
if (mHasUnreadMessages) {
ContentValues values = new ContentValues(1);
values.put(Imps.Chats.LAST_UNREAD_MESSAGE, (String) null);
mConnection.getContext().getContentResolver().update(mChatURI, values, null, null);
mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), getAddress());
mHasUnreadMessages = false;
}
}
String getNickName(String username) {
ImEntity participant = mAdaptee.getParticipant();
if (mIsGroupChat) {
ChatGroup group = (ChatGroup)participant;
List<Contact> members = group.getMembers();
for (Contact c : members) {
if (username.equals(c.getAddress().getFullName())) {
return c.getName();
}
}
// not found, impossible
return username;
} else {
return ((Contact)participant).getName();
}
}
void onConvertToGroupChatSuccess(ChatGroup group) {
Contact oldParticipant = (Contact)mAdaptee.getParticipant();
String oldAddress = getAddress();
mAdaptee.setParticipant(group);
mChatManager.updateChatSession(oldAddress, this);
Uri oldChatUri = mChatURI;
Uri oldMessageUri = mMessageURI;
init(group);
copyHistoryMessages(oldParticipant);
mContentResolver.delete(oldMessageUri, NON_CHAT_MESSAGE_SELECTION, null);
mContentResolver.delete(oldChatUri, null, null);
mListenerAdapter.notifyChatSessionConverted();
mConvertingToGroupChat = false;
}
private void copyHistoryMessages(Contact oldParticipant) {
List<Message> historyMessages = mAdaptee.getHistoryMessages();
int total = historyMessages.size();
int start = total > MAX_HISTORY_COPY_COUNT ? total - MAX_HISTORY_COPY_COUNT : 0;
for (int i = start; i < total; i++) {
Message msg = historyMessages.get(i);
boolean incoming = msg.getFrom().equals(oldParticipant.getAddress());
String contact = incoming ? oldParticipant.getName() : null;
long time = msg.getDateTime().getTime();
insertMessageInDb(contact, msg.getBody(), time,
incoming ? Imps.MessageType.INCOMING : Imps.MessageType.OUTGOING);
}
}
void insertOrUpdateChat(String message) {
ContentValues values = new ContentValues(2);
values.put(Imps.Chats.LAST_MESSAGE_DATE, System.currentTimeMillis());
values.put(Imps.Chats.LAST_UNREAD_MESSAGE, message);
// ImProvider.insert() will replace the chat if it already exist.
mContentResolver.insert(mChatURI, values);
}
private long insertGroupContactInDb(ChatGroup group) {
// Insert a record in contacts table
ContentValues values = new ContentValues(4);
values.put(Imps.Contacts.USERNAME, group.getAddress().getFullName());
values.put(Imps.Contacts.NICKNAME, group.getName());
values.put(Imps.Contacts.CONTACTLIST, ContactListManagerAdapter.FAKE_TEMPORARY_LIST_ID);
values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_GROUP);
Uri contactUri = ContentUris.withAppendedId(ContentUris.withAppendedId(
Imps.Contacts.CONTENT_URI, mConnection.mProviderId), mConnection.mAccountId);
long id = ContentUris.parseId(mContentResolver.insert(contactUri, values));
ArrayList<ContentValues> memberValues = new ArrayList<ContentValues>();
Contact self = mConnection.getLoginUser();
for (Contact member : group.getMembers()) {
if (!member.equals(self)) { // avoid to insert the user himself
ContentValues memberValue = new ContentValues(2);
memberValue.put(Imps.GroupMembers.USERNAME,
member.getAddress().getFullName());
memberValue.put(Imps.GroupMembers.NICKNAME,
member.getName());
memberValues.add(memberValue);
}
}
if (!memberValues.isEmpty()) {
ContentValues[] result = new ContentValues[memberValues.size()];
memberValues.toArray(result);
Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, id);
mContentResolver.bulkInsert(memberUri, result);
}
return id;
}
void insertGroupMemberInDb(Contact member) {
ContentValues values1 = new ContentValues(2);
values1.put(Imps.GroupMembers.USERNAME, member.getAddress().getFullName());
values1.put(Imps.GroupMembers.NICKNAME, member.getName());
ContentValues values = values1;
long groupId = ContentUris.parseId(mChatURI);
Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId);
mContentResolver.insert(uri, values);
insertMessageInDb(member.getName(), null, System.currentTimeMillis(),
Imps.MessageType.PRESENCE_AVAILABLE);
}
void deleteGroupMemberInDb(Contact member) {
String where = Imps.GroupMembers.USERNAME + "=?";
String[] selectionArgs = { member.getAddress().getFullName() };
long groupId = ContentUris.parseId(mChatURI);
Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId);
mContentResolver.delete(uri, where, selectionArgs);
insertMessageInDb(member.getName(), null, System.currentTimeMillis(),
Imps.MessageType.PRESENCE_UNAVAILABLE);
}
void insertPresenceUpdatesMsg(String contact, Presence presence) {
int status = presence.getStatus();
Integer previousStatus = mContactStatusMap.get(contact);
if (previousStatus != null && previousStatus == status) {
// don't insert the presence message if it's the same status
// with the previous presence update notification
return;
}
mContactStatusMap.put(contact, status);
int messageType;
switch (status) {
case Presence.AVAILABLE:
messageType = Imps.MessageType.PRESENCE_AVAILABLE;
break;
case Presence.AWAY:
case Presence.IDLE:
messageType = Imps.MessageType.PRESENCE_AWAY;
break;
case Presence.DO_NOT_DISTURB:
messageType = Imps.MessageType.PRESENCE_DND;
break;
default:
messageType = Imps.MessageType.PRESENCE_UNAVAILABLE;
break;
}
if(mIsGroupChat) {
insertMessageInDb(contact, null, System.currentTimeMillis(), messageType);
} else {
insertMessageInDb(null, null, System.currentTimeMillis(), messageType);
}
}
void removeMessageInDb(int type) {
mContentResolver.delete(mMessageURI, Imps.Messages.TYPE + "=?",
new String[]{Integer.toString(type)});
}
Uri insertMessageInDb(String contact, String body, long time, int type) {
return insertMessageInDb(contact, body, time, type, 0/*No error*/);
}
Uri insertMessageInDb(String contact, String body, long time, int type, int errCode) {
ContentValues values = new ContentValues(mIsGroupChat ? 4 : 3);
values.put(Imps.Messages.BODY, body);
values.put(Imps.Messages.DATE, time);
values.put(Imps.Messages.TYPE, type);
values.put(Imps.Messages.ERROR_CODE, errCode);
if (mIsGroupChat) {
values.put(Imps.Messages.NICKNAME, contact);
values.put(Imps.Messages.IS_GROUP_CHAT, 1);
}
return mContentResolver.insert(mMessageURI, values);
}
class ListenerAdapter implements MessageListener, GroupMemberListener {
public void onIncomingMessage(ChatSession ses, final Message msg) {
String body = msg.getBody();
String username = msg.getFrom().getFullName();
String nickname = getNickName(username);
long time = msg.getDateTime().getTime();
if(mIsGroupChat) {
insertOrUpdateChat(nickname + ": " + body);
} else {
insertOrUpdateChat(body);
}
insertMessageInDb(nickname, body, time, Imps.MessageType.INCOMING);
int N = mRemoteListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IChatListener listener = mRemoteListeners.getBroadcastItem(i);
try {
listener.onIncomingMessage(ChatSessionAdapter.this, msg);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing the
// dead listeners.
}
}
mRemoteListeners.finishBroadcast();
mStatusBarNotifier.notifyChat(mConnection.getProviderId(),
mConnection.getAccountId(), getId(), username, nickname, body, N > 0);
mHasUnreadMessages = true;
}
public void onSendMessageError(ChatSession ses, final Message msg,
final ImErrorInfo error) {
insertMessageInDb(null, null, System.currentTimeMillis(),
Imps.MessageType.OUTGOING, error.getCode());
final int N = mRemoteListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IChatListener listener = mRemoteListeners.getBroadcastItem(i);
try {
listener.onSendMessageError(ChatSessionAdapter.this, msg, error);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing the
// dead listeners.
}
}
mRemoteListeners.finishBroadcast();
}
public void onMemberJoined(ChatGroup group, final Contact contact) {
insertGroupMemberInDb(contact);
final int N = mRemoteListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IChatListener listener = mRemoteListeners.getBroadcastItem(i);
try {
listener.onContactJoined(ChatSessionAdapter.this, contact);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing the
// dead listeners.
}
}
mRemoteListeners.finishBroadcast();
}
public void onMemberLeft(ChatGroup group, final Contact contact) {
deleteGroupMemberInDb(contact);
final int N = mRemoteListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IChatListener listener = mRemoteListeners.getBroadcastItem(i);
try {
listener.onContactLeft(ChatSessionAdapter.this, contact);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing the
// dead listeners.
}
}
mRemoteListeners.finishBroadcast();
}
public void onError(ChatGroup group, final ImErrorInfo error) {
// TODO: insert an error message?
final int N = mRemoteListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IChatListener listener = mRemoteListeners.getBroadcastItem(i);
try {
listener.onInviteError(ChatSessionAdapter.this, error);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing the
// dead listeners.
}
}
mRemoteListeners.finishBroadcast();
}
public void notifyChatSessionConverted() {
final int N = mRemoteListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IChatListener listener = mRemoteListeners.getBroadcastItem(i);
try {
listener.onConvertedToGroupChat(ChatSessionAdapter.this);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing the
// dead listeners.
}
}
mRemoteListeners.finishBroadcast();
}
}
class ChatConvertor implements GroupListener, GroupMemberListener {
private ChatGroupManager mGroupMgr;
private String mGroupName;
public ChatConvertor() {
mGroupMgr = mConnection.mGroupManager;
}
public void convertToGroupChat() {
mGroupMgr.addGroupListener(this);
mGroupName = "G" + System.currentTimeMillis();
mGroupMgr.createChatGroupAsync(mGroupName);
}
public void onGroupCreated(ChatGroup group) {
if (mGroupName.equalsIgnoreCase(group.getName())) {
mGroupMgr.removeGroupListener(this);
group.addMemberListener(this);
mGroupMgr.inviteUserAsync(group, (Contact)mAdaptee.getParticipant());
}
}
public void onMemberJoined(ChatGroup group, Contact contact) {
if (mAdaptee.getParticipant().equals(contact)) {
onConvertToGroupChatSuccess(group);
}
mContactStatusMap.put(contact.getName(), contact.getPresence().getStatus());
}
public void onGroupDeleted(ChatGroup group) {
}
public void onGroupError(int errorType, String groupName, ImErrorInfo error) {
}
public void onJoinedGroup(ChatGroup group) {
}
public void onLeftGroup(ChatGroup group) {
}
public void onError(ChatGroup group, ImErrorInfo error) {
}
public void onMemberLeft(ChatGroup group, Contact contact) {
mContactStatusMap.remove(contact.getName());
}
}
}