| /* |
| * 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()); |
| } |
| } |
| } |