All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avos.avoscloud.im.v2.AVIMConversation Maven / Gradle / Ivy

The newest version!
package com.avos.avoscloud.im.v2;

import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.avos.avoscloud.AVCallback;
import com.avos.avoscloud.AVException;
import com.avos.avoscloud.AVOSCloud;
import com.avos.avoscloud.AVObject;
import com.avos.avoscloud.AVUtils;
import com.avos.avoscloud.IntentUtil;
import com.avos.avoscloud.LogUtil;
import com.avos.avoscloud.PushService;
import com.avos.avoscloud.PushServiceParcel;
import com.avos.avoscloud.QueryOperation;
import com.avos.avoscloud.SaveCallback;
import com.avos.avoscloud.QueryConditions;
import com.avos.avoscloud.im.v2.Conversation.AVIMOperation;
import com.avos.avoscloud.im.v2.callback.AVIMConversationCallback;
import com.avos.avoscloud.im.v2.callback.AVIMOperationFailure;
import com.avos.avoscloud.im.v2.callback.AVIMConversationMemberCountCallback;
import com.avos.avoscloud.im.v2.callback.AVIMConversationMemberQueryCallback;
import com.avos.avoscloud.im.v2.callback.AVIMOperationPartiallySucceededCallback;
import com.avos.avoscloud.im.v2.callback.AVIMConversationSimpleResultCallback;
import com.avos.avoscloud.im.v2.callback.AVIMMessageRecalledCallback;
import com.avos.avoscloud.im.v2.callback.AVIMMessageUpdatedCallback;
import com.avos.avoscloud.im.v2.callback.AVIMMessagesQueryCallback;
import com.avos.avoscloud.im.v2.callback.AVIMSingleMessageQueryCallback;
import com.avos.avoscloud.im.v2.conversation.AVIMConversationMemberInfo;
import com.avos.avoscloud.im.v2.conversation.ConversationMemberRole;
import com.avos.avoscloud.im.v2.messages.AVIMFileMessage;
import com.avos.avoscloud.im.v2.messages.AVIMFileMessageAccessor;
import com.avos.avoscloud.im.v2.messages.AVIMRecalledMessage;
import com.avos.avoscloud.utils.StringUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class AVIMConversation {

  /**
   * 暂存消息
   * 

* 只有在消息发送时,对方也是在线的才能收到这条消息 */ public static final int TRANSIENT_MESSAGE_FLAG = 0x10; /** * 回执消息 *

* 当消息送到到对方以后,发送方会收到消息回执说明消息已经成功达到接收方 */ public static final int RECEIPT_MESSAGE_FLAG = 0x100; private static final String ATTR_PERFIX = Conversation.ATTRIBUTE + "."; String conversationId; Set members; Map attributes; Map pendingAttributes; AVIMClient client; String creator; boolean isTransient; AVIMMessageStorage storage; // 注意,sqlite conversation 表中的 lastMessageAt、lastMessage 的来源是 AVIMConversationQuery // 所以并不一定是最新的,返回时要与 message 表中的数据比较,然后返回最新的, Date lastMessageAt; AVIMMessage lastMessage; String createdAt; String updatedAt; Map instanceData = new HashMap<>(); Map pendingInstanceData = new HashMap<>(); // 是否与数据库中同步了 lastMessage,避免多次走 sqlite 查询 private boolean isSyncLastMessage = false; /** * 未读消息数量 */ int unreadMessagesCount = 0; boolean unreadMessagesMentioned = false; /** * 对方最后收到消息的时间,此处仅针对双人会话有效 */ long lastDeliveredAt; /** * 对方最后读消息的时间,此处仅针对双人会话有效 */ long lastReadAt; /** * 是否是服务号 */ boolean isSystem = false; /** * 是否是服务号 */ public boolean isSystem() { return isSystem; } /** * 是否是临时对话 */ boolean isTemporary = false; /** * 是否是临时对话 */ public boolean isTemporary() { return isTemporary; } void setTemporary(boolean temporary) { isTemporary = temporary; } /** * 临时对话过期时间 */ long temporaryExpiredat = 0l; /** * 获取临时对话过期时间(以秒为单位) */ public long getTemporaryExpiredat() { return temporaryExpiredat; } /** * 设置临时对话过期时间(以秒为单位) * 仅对 临时对话 有效 */ public void setTemporaryExpiredat(long temporaryExpiredat) { if (this.isTemporary()) { this.temporaryExpiredat = temporaryExpiredat; } } protected int getType() { if (isSystem()) { return Conversation.CONV_TYPE_SYSTEM; } else if (isTransient()) { return Conversation.CONV_TYPE_TRANSIENT; } else if (isTemporary()) { return Conversation.CONV_TYPE_TEMPORARY; } else { return Conversation.CONV_TYPE_NORMAL; } } protected AVIMConversation(AVIMClient client, List members, Map attributes, boolean isTransient) { this.members = new HashSet(); if (members != null) { this.members.addAll(members); } this.attributes = new HashMap(); if (attributes != null) { this.attributes.putAll(attributes); } this.client = client; pendingAttributes = new HashMap(); this.isTransient = isTransient; this.storage = AVIMMessageStorage.getInstance(client.clientId); } protected AVIMConversation(AVIMClient client, String conversationId) { this(client, null, null, false); this.conversationId = conversationId; } public String getConversationId() { return this.conversationId; } protected void setConversationId(String id) { this.conversationId = id; } protected void setCreator(String creator) { this.creator = creator; } /** * 获取聊天对话的创建者 * * @return * @since 3.0 */ public String getCreator() { return this.creator; } /** * 发送一条非暂存消息 * * @param message * @param callback * @since 3.0 */ public void sendMessage(AVIMMessage message, final AVIMConversationCallback callback) { sendMessage(message, null, callback); } /** * 发送消息 * @param message * @param messageOption * @param callback */ public void sendMessage(final AVIMMessage message, final AVIMMessageOption messageOption, final AVIMConversationCallback callback) { message.setConversationId(conversationId); message.setFrom(client.clientId); message.generateUniqueToken(); message.setTimestamp(System.currentTimeMillis()); if (!AVUtils.isConnected(AVOSCloud.applicationContext)) { message.setMessageStatus(AVIMMessage.AVIMMessageStatus.AVIMMessageStatusFailed); if (callback != null) { callback.internalDone(new AVException(AVException.CONNECTION_FAILED, "Connection lost")); } return; } message.setMessageStatus(AVIMMessage.AVIMMessageStatus.AVIMMessageStatusSending); if (AVIMFileMessage.class.isAssignableFrom(message.getClass())) { AVIMFileMessageAccessor.upload((AVIMFileMessage) message, new SaveCallback() { public void done(AVException e) { if (e != null) { message.setMessageStatus(AVIMMessage.AVIMMessageStatus.AVIMMessageStatusFailed); if (callback != null) { callback.internalDone(e); } } else { boolean ret = sendCMDToPushService(null, message, messageOption, AVIMOperation.CONVERSATION_SEND_MESSAGE, callback, null); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } } }); } else { boolean ret = sendCMDToPushService(null, message, messageOption, AVIMOperation.CONVERSATION_SEND_MESSAGE, callback, null); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } } /** * 发送一条消息。 * * @param message * @param messageFlag 消息发送选项。 * @param callback * @since 3.0 * @deprecated Please use {@link #sendMessage(AVIMMessage, AVIMMessageOption, AVIMConversationCallback)} */ public void sendMessage(AVIMMessage message, int messageFlag, AVIMConversationCallback callback) { AVIMMessageOption option = new AVIMMessageOption(); option.setReceipt((messageFlag & AVIMConversation.RECEIPT_MESSAGE_FLAG) == AVIMConversation.RECEIPT_MESSAGE_FLAG); option.setTransient((messageFlag & AVIMConversation.TRANSIENT_MESSAGE_FLAG) == AVIMConversation.TRANSIENT_MESSAGE_FLAG); sendMessage(message, option, callback); } /** * update message content * @param oldMessage the message need to be modified * @param newMessage the content of the old message will be covered by the new message's * @param callback */ public void updateMessage(AVIMMessage oldMessage, AVIMMessage newMessage, AVIMMessageUpdatedCallback callback) { PushServiceParcel parcel = new PushServiceParcel(); parcel.setOldMessage(oldMessage); parcel.setNewMessage(newMessage); boolean ret = sendParcelToPushService(parcel, AVIMOperation.CONVERSATION_UPDATE_MESSAGE, callback); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * racall message * @param message the message need to be recalled * @param callback */ public void recallMessage(AVIMMessage message, AVIMMessageRecalledCallback callback) { PushServiceParcel parcel = new PushServiceParcel(); parcel.setRecallMessage(message); boolean ret = sendParcelToPushService(parcel, AVIMOperation.CONVERSATION_RECALL_MESSAGE, callback); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * save local message which failed to send to LeanCloud server. * Notice: this operation perhaps to block the main thread because that database operation is executing. * * @param message the message need to be saved to local. */ public void addToLocalCache(AVIMMessage message) { this.storage.insertLocalMessage(message); } /** * remove local message from cache. * Notice: this operation perhaps to block the main thread because that database operation is executing. * * @param message */ public void removeFromLocalCache(AVIMMessage message) { this.storage.removeLocalMessage(message); } /** * 查询最近的20条消息记录 * * @param callback */ public void queryMessages(final AVIMMessagesQueryCallback callback) { this.queryMessages(20, callback); } /** * 从服务器端拉取最新消息 * @param limit * @param callback */ public void queryMessagesFromServer(int limit, final AVIMMessagesQueryCallback callback) { queryMessagesFromServer(null, 0, limit, null, 0, new AVIMMessagesQueryCallback() { @Override public void done(List messages, AVIMException e) { if (null == e) { if (AVIMClient.messageQueryCacheEnabled) { processContinuousMessages(messages); } callback.internalDone(messages, null); } else { callback.internalDone(null, e); } } }); } /** * 从本地缓存中拉取消息 * @param limit * @param callback */ public void queryMessagesFromCache(int limit, AVIMMessagesQueryCallback callback) { queryMessagesFromCache(null, 0, limit, callback); } private void queryMessagesFromServer(String msgId, long timestamp, int limit, String toMsgId, long toTimestamp, AVIMMessagesQueryCallback callback) { queryMessagesFromServer(msgId, timestamp, false, toMsgId, toTimestamp, false, AVIMMessageQueryDirection.AVIMMessageQueryDirectionFromNewToOld, limit, callback); } /** * 获取特停类型的历史消息。 * 注意:这个操作总是会从云端获取记录。 * 另,该函数和 queryMessagesByType(type, msgId, timestamp, limit, callback) 配合使用可以实现翻页效果。 * * @param msgType 消息类型,可以参看 `AVIMMessageType` 里的定义。 * @param limit 本批次希望获取的消息数量。 * @param callback 结果回调函数 */ public void queryMessagesByType(int msgType, int limit, final AVIMMessagesQueryCallback callback) { queryMessagesByType(msgType, null, 0, limit, callback); } /** * 获取特定类型的历史消息。 * 注意:这个操作总是会从云端获取记录。 * 另,如果不指定 msgId 和 timestamp,则该函数效果等同于 queryMessageByType(type, limit, callback) * * @param msgType 消息类型,可以参看 `AVIMMessageType` 里的定义。 * @param msgId 消息id,从特定消息 id 开始向前查询(结果不会包含该记录) * @param timestamp 查询起始的时间戳,返回小于这个时间的记录,必须配合 msgId 一起使用。 * 要从最新消息开始获取时,请用 0 代替客户端的本地当前时间(System.currentTimeMillis()) * @param limit 返回条数限制 * @param callback 结果回调函数 */ public void queryMessagesByType(int msgType, final String msgId, final long timestamp, final int limit, final AVIMMessagesQueryCallback callback) { if (null == callback) { return; } Map params = new HashMap(); params.put(Conversation.PARAM_MESSAGE_QUERY_MSGID, msgId); params.put(Conversation.PARAM_MESSAGE_QUERY_TIMESTAMP, timestamp); params.put(Conversation.PARAM_MESSAGE_QUERY_STARTCLOSED, false); params.put(Conversation.PARAM_MESSAGE_QUERY_TO_MSGID, ""); params.put(Conversation.PARAM_MESSAGE_QUERY_TO_TIMESTAMP, 0); params.put(Conversation.PARAM_MESSAGE_QUERY_TOCLOSED, false); params.put(Conversation.PARAM_MESSAGE_QUERY_DIRECT, AVIMMessageQueryDirection.AVIMMessageQueryDirectionFromNewToOld.getCode()); params.put(Conversation.PARAM_MESSAGE_QUERY_LIMIT, limit); params.put(Conversation.PARAM_MESSAGE_QUERY_TYPE, msgType); boolean ret = sendNonSideEffectCommand(JSON.toJSONString(params), AVIMOperation.CONVERSATION_MESSAGE_QUERY, callback); if (!ret) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } private void queryMessagesFromServer(String msgId, long timestamp, boolean startClosed, String toMsgId, long toTimestamp, boolean toClosed, AVIMMessageQueryDirection direction, int limit, AVIMMessagesQueryCallback cb) { Map params = new HashMap(); params.put(Conversation.PARAM_MESSAGE_QUERY_MSGID, msgId); params.put(Conversation.PARAM_MESSAGE_QUERY_TIMESTAMP, timestamp); params.put(Conversation.PARAM_MESSAGE_QUERY_STARTCLOSED, startClosed); params.put(Conversation.PARAM_MESSAGE_QUERY_TO_MSGID, toMsgId); params.put(Conversation.PARAM_MESSAGE_QUERY_TO_TIMESTAMP, toTimestamp); params.put(Conversation.PARAM_MESSAGE_QUERY_TOCLOSED, toClosed); params.put(Conversation.PARAM_MESSAGE_QUERY_DIRECT, direction.getCode()); params.put(Conversation.PARAM_MESSAGE_QUERY_LIMIT, limit); params.put(Conversation.PARAM_MESSAGE_QUERY_TYPE, 0); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_MESSAGE_QUERY, cb); if (!ret && null != cb) { cb.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } private void queryMessagesFromCache(final String msgId, final long timestamp, final int limit, final AVIMMessagesQueryCallback callback) { if (null != callback) { storage.getMessages(msgId, timestamp, limit, conversationId, new AVIMMessageStorage.StorageQueryCallback() { @Override public void done(List messages, List breakpoints) { Collections.reverse(messages); callback.internalDone(messages, null); } }); } } /** * 获取最新的消息记录 * * @param limit * @param callback */ public void queryMessages(final int limit, final AVIMMessagesQueryCallback callback) { if (limit <= 0 || limit > 1000) { if (callback != null) { callback.internalDone(null, new AVException(new IllegalArgumentException( "limit should be in [1, 1000]"))); } } // 如果屏蔽了本地缓存则全部走网络 if (!AVIMClient.messageQueryCacheEnabled) { queryMessagesFromServer(null, 0, limit, null, 0, new AVIMMessagesQueryCallback() { @Override public void done(List messages, AVIMException e) { if (callback != null) { if (e != null) { callback.internalDone(e); } else { callback.internalDone(messages, null); } } } }); return; } if (!AVUtils.isConnected(AVOSCloud.applicationContext)) { queryMessagesFromCache(null, 0, limit, callback); } else { // 选择最后一条有 breakpoint 为 false 的消息做截断,因为是 true 的话,会造成两次查询。 // 在 queryMessages 还是遇到 breakpoint,再次查询了 long cacheMessageCount = storage.getMessageCount(conversationId); long toTimestamp = 0; String toMsgId = null; // 如果本地的缓存的量都不够的情况下,应该要去服务器查询,以免第一次查询的时候出现limit跟返回值不一致让用户认为聊天记录已经到头的问题 if (cacheMessageCount >= limit) { final AVIMMessage latestMessage = storage.getLatestMessageWithBreakpoint(conversationId, false); if (latestMessage != null) { toMsgId = latestMessage.getMessageId(); toTimestamp = latestMessage.getTimestamp(); } } // 去服务器查询最新消息,看是否在其它终端产生过消息。为省流量,服务器会截断至 toMsgId 、toTimestamp queryMessagesFromServer(null, 0, limit, toMsgId, toTimestamp, new AVIMMessagesQueryCallback() { @Override public void done(List messages, AVIMException e) { if (e != null) { // 如果遇到本地错误或者网络错误,直接返回缓存数据 if (e.getCode() == AVIMException.TIMEOUT || e.getCode() == 0 || e.getCode() == 3000) { queryMessagesFromCache(null, 0, limit, callback); } else { if (callback != null) { callback.internalDone(e); } } } else { if (AVUtils.isEmptyList(messages)) { // 这种情况就说明我们的本地消息缓存是最新的 } else { /* * 1.messages.size()<=limit && messages.contains(latestMessage) * 这种情况就说明在本地客户端退出后,该用户在其他客户端也产生了聊天记录而没有缓存到本地来,且产生了小于一页的聊天记录 * 2.messages==limit && !messages.contains(latestMessage) * 这种情况就说明在本地客户端退出后,该用户在其他客户端也产生了聊天记录而没有缓存到本地来,且产生了大于一页的聊天记录 */ processContinuousMessages(messages); } queryMessagesFromCache(null, 0, limit, callback); } } }); } } /** * 查询消息记录,上拉时使用。 * * @param msgId 消息id,从消息id开始向前查询 * @param timestamp 查询起始的时间戳,返回小于这个时间的记录。 * 客户端时间不可靠,请用 0 代替 System.currentTimeMillis() * @param limit 返回条数限制 * @param callback */ public void queryMessages(final String msgId, final long timestamp, final int limit, final AVIMMessagesQueryCallback callback) { if (AVUtils.isBlankString(msgId) && timestamp == 0) { this.queryMessages(limit, callback); return; } // 如果屏蔽了本地缓存则全部走网络 if (!AVIMClient.messageQueryCacheEnabled) { queryMessagesFromServer(msgId, timestamp, limit, null, 0, new AVIMMessagesQueryCallback() { @Override public void done(List messages, AVIMException e) { if (callback != null) { if (e != null) { callback.internalDone(e); } else { callback.internalDone(messages, null); } } } }); return; } // 先去本地缓存查询消息 storage.getMessage(msgId, timestamp, conversationId, new AVIMMessageStorage.StorageMessageCallback() { @Override public void done(final AVIMMessage indicatorMessage, final boolean isIndicateMessageBreakPoint) { if (indicatorMessage == null || isIndicateMessageBreakPoint) { String startMsgId = msgId; long startTS = timestamp; int requestLimit = limit; queryMessagesFromServer(startMsgId, startTS, requestLimit, null, 0, new AVIMMessagesQueryCallback() { @Override public void done(List messages, AVIMException e) { if (e != null) { callback.internalDone(e); } else { List cachedMsgs = new LinkedList(); if (indicatorMessage != null) { // add indicatorMessage to remove breakpoint. cachedMsgs.add(indicatorMessage); } if (messages != null) { cachedMsgs.addAll(messages); } processContinuousMessages(cachedMsgs); queryMessagesFromCache(msgId, timestamp, limit, callback); } } }); } else { // 本地缓存过而且不是breakPoint storage.getMessages(msgId, timestamp, limit, conversationId, new AVIMMessageStorage.StorageQueryCallback() { @Override public void done(List messages, List breakpoints) { processStorageQueryResult(messages, breakpoints, msgId, timestamp, limit, callback); } }); } } }); } /** * 根据指定的区间来查询历史消息,可以指定区间开闭、查询方向以及最大条目限制 * @param interval - 区间,由起止 AVIMMessageIntervalBound 组成 * @param direction - 查询方向,支持向前(AVIMMessageQueryDirection.AVIMMessageQueryDirectionFromNewToOld) * 或向后(AVIMMessageQueryDirection.AVIMMessageQueryDirectionFromOldToNew)查询 * @param limit - 结果最大条目限制 * @param callback - 结果回调函数 */ public void queryMessages(final AVIMMessageInterval interval, AVIMMessageQueryDirection direction, final int limit, final AVIMMessagesQueryCallback callback) { if (null == interval || limit < 0) { if (null != callback) { callback.internalDone(null, new AVException(new IllegalArgumentException("interval must not null, or limit must great than 0."))); } return; } String mid = null; long ts = 0; boolean startClosed = false; String tmid = null; long tts = 0; boolean endClosed = false; if (null != interval.startIntervalBound) { mid = interval.startIntervalBound.messageId; ts = interval.startIntervalBound.timestamp; startClosed = interval.startIntervalBound.closed; } if (null != interval.endIntervalBound) { tmid = interval.endIntervalBound.messageId; tts = interval.endIntervalBound.timestamp; endClosed = interval.endIntervalBound.closed; } queryMessagesFromServer(mid, ts, startClosed, tmid, tts, endClosed, direction, limit, callback); } /** * 获取本聊天室的最后一条消息 * * 如果AVIMClient.setMessageQueryCacheEnable(false)则强制走网络 * * 否则只会取本地的缓存而不走网络,所以在一定程度上缺乏实时性 * * @param callback */ public void getLastMessage(final AVIMSingleMessageQueryCallback callback) { if (AVIMClient.messageQueryCacheEnabled) { queryMessagesFromCache(null, 0, 1, new AVIMMessagesQueryCallback() { @Override public void done(List messages, AVIMException e) { processLastMessageResult(messages, e, callback); } }); } else { queryMessagesFromServer(null, 0, 1, null, 0, new AVIMMessagesQueryCallback() { @Override public void done(List messages, AVIMException e) { processLastMessageResult(messages, e, callback); } }); } } private void processLastMessageResult(List resultMessages, AVIMException e, AVIMSingleMessageQueryCallback callback) { if (e == null) { if (resultMessages != null && resultMessages.size() > 0) { callback.internalDone(resultMessages.get(0), null); } else { callback.done(null, null); } } else { callback.internalDone(null, e); } } /** * 若发现有足够的连续消息,则直接返回。否则去服务器查询消息,同时消除断点。 * */ private void processStorageQueryResult(List cachedMessages, List breakpoints, String originMsgId, long originMsgTS, int limit, final AVIMMessagesQueryCallback callback) { final List continuousMessages = new ArrayList(); int firstBreakPointIndex = -1; for (int index = 0; index < cachedMessages.size(); index++) { if (breakpoints.get(index)) { firstBreakPointIndex = index; break; } else { continuousMessages.add(cachedMessages.get(index)); } } final boolean connected = AVUtils.isConnected(AVOSCloud.applicationContext); // 如果只是最后一个消息是breakPoint,那还走啥网络 if (!connected || continuousMessages.size() >= limit/* - 1*/) { // in case of wifi is invalid, and thre query list contain breakpoint, the result is error. Collections.sort(continuousMessages, messageComparator); callback.internalDone(continuousMessages, null); } else { final int restCount; final AVIMMessage startMessage; if (!continuousMessages.isEmpty()) { // 这里是缓存里面没有breakPoint,但是limit不够的情况下 restCount = limit - continuousMessages.size(); startMessage = continuousMessages.get(continuousMessages.size() - 1); } else { startMessage = null; restCount = limit; } queryMessagesFromServer(startMessage == null ? originMsgId : startMessage.messageId, startMessage == null ? originMsgTS : startMessage.timestamp, restCount, null, 0, new AVIMMessagesQueryCallback() { @Override public void done(List serverMessages, AVIMException e) { if (e != null) { // 不管如何,先返回缓存里面已有的有效数据 if (continuousMessages.size() > 0) { callback.internalDone(continuousMessages, null); } else { callback.internalDone(e); } } else { if (serverMessages == null) { serverMessages = new ArrayList(); } continuousMessages.addAll(serverMessages); processContinuousMessages(continuousMessages); callback.internalDone(continuousMessages, null); } } }); } } /** * 获取当前对话的所有角色信息 * @param offset 查询结果的起始点 * @param limit 查询结果集上限 * @param callback 结果回调函数 */ public void getAllMemberInfo(int offset, int limit, final AVIMConversationMemberQueryCallback callback) { QueryConditions conditions = new QueryConditions(); conditions.addWhereItem("cid", QueryOperation.EQUAL_OP, this.conversationId); conditions.setSkip(offset); conditions.setLimit(limit); queryMemberInfo(conditions, callback); } /** * 获取对话内指定成员的角色信息 * @param memberId 成员的 clientid * @param callback 结果回调函数 */ public void getMemberInfo(final String memberId, final AVIMConversationMemberQueryCallback callback) { QueryConditions conditions = new QueryConditions(); conditions.addWhereItem("cid", QueryOperation.EQUAL_OP, this.conversationId); conditions.addWhereItem("peerId", QueryOperation.EQUAL_OP, memberId); queryMemberInfo(conditions, callback); } /** * 更新成员的角色信息 * @param memberId 成员的 client id * @param role 角色 * @param callback 结果回调函数 */ public void updateMemberRole(final String memberId, final ConversationMemberRole role, final AVIMConversationCallback callback) { AVIMConversationMemberInfo info = new AVIMConversationMemberInfo(this.conversationId, memberId, role); Map params = new HashMap(); params.put(Conversation.PARAM_CONVERSATION_MEMBER_DETAILS, info.getUpdateAttrs()); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_PROMOTE_MEMBER, callback, null); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } private void updateNickName(final String nickname, final AVIMConversationCallback callback) { ; } private void updateMemberComment(final String memberComment, final AVIMConversationCallback callback) { ; } private void queryMemberInfo(final QueryConditions queryConditions, final AVIMConversationMemberQueryCallback callback) { if (null == queryConditions || null == callback) { return; } client.queryConversationMemberInfo(queryConditions, callback); } /** * 将部分成员禁言 * @param memberIds 成员列表 * @param callback 结果回调函数 */ public void muteMembers(final List memberIds, final AVIMOperationPartiallySucceededCallback callback) { if (null == memberIds || memberIds.size() < 1) { if (null != callback) { callback.done(new AVIMException(new IllegalArgumentException("memberIds is null")), null, null); } return; } Map params = new HashMap(); params.put(Conversation.PARAM_CONVERSATION_MEMBER, memberIds); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_MUTE_MEMBER, callback, null); if (!ret && null != callback) { callback.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 将部分成员解除禁言 * @param memberIds 成员列表 * @param callback 结果回调函数 */ public void unmuteMembers(final List memberIds, final AVIMOperationPartiallySucceededCallback callback) { if (null == memberIds || memberIds.size() < 1) { if (null != callback) { callback.done(new AVIMException(new IllegalArgumentException("memberIds is null")), null, null); } return; } Map params = new HashMap(); params.put(Conversation.PARAM_CONVERSATION_MEMBER, memberIds); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_UNMUTE_MEMBER, callback, null); if (!ret && null != callback) { callback.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 查询被禁言的成员列表 * @param offset 查询结果的起始点 * @param limit 查询结果集上限 * @param callback 结果回调函数 */ public void queryMutedMembers(int offset, int limit, final AVIMConversationSimpleResultCallback callback) { if (null == callback) { return; } else if (offset < 0 || limit > 100) { callback.internalDone(null, new AVIMException(new IllegalArgumentException("offset/limit is illegal."))); return; } Map params = new HashMap(); params.put(Conversation.QUERY_PARAM_LIMIT, limit); params.put(Conversation.QUERY_PARAM_OFFSET, offset); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_MUTED_MEMBER_QUERY, callback, null); if (!ret) { callback.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 将部分成员加入黑名单 * @param memberIds 成员列表 * @param callback 结果回调函数 */ public void blockMembers(final List memberIds, final AVIMOperationPartiallySucceededCallback callback) { if (null == memberIds || memberIds.size() < 1) { if (null != callback) { callback.done(new AVIMException(new IllegalArgumentException("memberIds is null")), null, null); } return; } Map params = new HashMap(); params.put(Conversation.PARAM_CONVERSATION_MEMBER, memberIds); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_BLOCK_MEMBER, callback, null); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 将部分成员从黑名单移出来 * @param memberIds 成员列表 * @param callback 结果回调函数 */ public void unblockMembers(final List memberIds, final AVIMOperationPartiallySucceededCallback callback) { if (null == memberIds || memberIds.size() < 1) { if (null != callback) { callback.done(new AVIMException(new IllegalArgumentException("memberIds is null")), null, null); } return; } Map params = new HashMap(); params.put(Conversation.PARAM_CONVERSATION_MEMBER, memberIds); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_UNBLOCK_MEMBER, callback, null); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 查询黑名单的成员列表 * @param offset 查询结果的起始点 * @param limit 查询结果集上限 * @param callback 结果回调函数 */ public void queryBlockedMembers(int offset, int limit, final AVIMConversationSimpleResultCallback callback) { if (null == callback) { return; } else if (offset < 0 || limit > 100) { callback.internalDone(null, new AVIMException(new IllegalArgumentException("offset/limit is illegal."))); return; } Map params = new HashMap(); params.put(Conversation.QUERY_PARAM_LIMIT, limit); params.put(Conversation.QUERY_PARAM_OFFSET, offset); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_BLOCKED_MEMBER_QUERY, callback, null); if (!ret) { callback.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 查询成员数量 * @param callback */ public void getMemberCount(AVIMConversationMemberCountCallback callback) { if (null == callback) { return; } boolean ret = sendCMDToPushService(null, AVIMOperation.CONVERSATION_MEMBER_COUNT_QUERY, callback); if (!ret) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 在聊天对话中间增加新的参与者 * * @param friendsList * @param callback * @since 3.0 */ public void addMembers(final List friendsList, final AVIMConversationCallback callback) { AVException membersCheckException = AVIMClient.validateNonEmptyConversationMembers(friendsList); if (membersCheckException != null) { if (callback != null) { callback.internalDone(null, membersCheckException); } return; } Map params = new HashMap(); params.put(Conversation.PARAM_CONVERSATION_MEMBER, friendsList); boolean ret = this.sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_ADD_MEMBER, callback, new OperationCompleteCallback() { @Override public void onComplete() { members.addAll(friendsList); storage.insertConversations(Arrays.asList(AVIMConversation.this)); } @Override public void onFailure() {} }); if (!ret && null != callback) { callback.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 在聊天记录中间剔除参与者 * * @param friendsList * @param callback * @since 3.0 */ public void kickMembers(final List friendsList, final AVIMConversationCallback callback) { AVException membersCheckException = AVIMClient.validateNonEmptyConversationMembers(friendsList); if (membersCheckException != null) { if (callback != null) { callback.internalDone(null, membersCheckException); } return; } Map params = new HashMap(); params.put(Conversation.PARAM_CONVERSATION_MEMBER, friendsList); boolean ret = this.sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_RM_MEMBER, callback, new OperationCompleteCallback() { @Override public void onComplete() { members.removeAll(friendsList); storage.insertConversations(Arrays.asList(AVIMConversation.this)); } @Override public void onFailure() {} }); if (!ret && null != callback) { callback.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 获取conversation当前的参与者 * * @return * @since 3.0 */ public List getMembers() { List allList = new ArrayList(); allList.addAll(members); return Collections.unmodifiableList(allList); } /** * 静音,客户端拒绝收到服务器端的离线推送通知 * * @param callback */ public void mute(final AVIMConversationCallback callback) { boolean ret = this.sendCMDToPushService(null, AVIMOperation.CONVERSATION_MUTE, callback, null); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 取消静音,客户端取消静音设置 * * @param callback */ public void unmute(final AVIMConversationCallback callback) { boolean ret = this.sendCMDToPushService(null, AVIMOperation.CONVERSATION_UNMUTE, callback, null); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } protected void setMembers(List m) { members.clear(); if (m != null) { members.addAll(m); } } /** * get the latest readAt timestamp * @return */ public long getLastReadAt() { return lastReadAt; } /** * get the latest deliveredAt timestamp * @return */ public long getLastDeliveredAt() { if (lastReadAt > lastDeliveredAt) { // 既然已读,肯定已经送到了 return lastReadAt; } return lastDeliveredAt; } void setLastReadAt(long timeStamp, boolean saveToLocal) { if (timeStamp > lastReadAt) { lastReadAt = timeStamp; if (saveToLocal) { storage.updateConversationTimes(this); } } } void setLastDeliveredAt(long timeStamp, boolean saveToLocal) { if (timeStamp > lastDeliveredAt) { lastDeliveredAt = timeStamp; if (saveToLocal) { storage.updateConversationTimes(this); } } } /** * 退出当前的聊天对话 * * @param callback * @since 3.0 */ public void quit(final AVIMConversationCallback callback) { boolean ret = this.sendCMDToPushService(null, AVIMOperation.CONVERSATION_QUIT, callback, new OperationCompleteCallback() { @Override public void onComplete() { storage.deleteConversationData(conversationId); members.remove(client.getClientId()); } @Override public void onFailure() { } }); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * Add a key-value pair to this conversation * @param key Keys must be alphanumerical plus underscore, and start with a letter. * @param value Values may be numerical, String, JSONObject, JSONArray, JSONObject.NULL, or other * AVObjects. value may not be null. */ public void set(String key, Object value) { if (!AVUtils.isBlankString(key) && null != value) { pendingInstanceData.put(key, value); } } /** * Access a value * @param key * @return */ public Object get(String key) { if (!AVUtils.isBlankString(key)) { if (pendingInstanceData.containsKey(key)) { return pendingInstanceData.get(key); } if (instanceData.containsKey(key)) { return instanceData.get(key); } } return null; } /** * 获取当前聊天对话的属性 * * @return * @since 3.0 */ @Deprecated public Object getAttribute(String key) { Object value; if (pendingAttributes.containsKey(key)) { value = pendingAttributes.get(key); } else { value = attributes.get(key); } return value; } @Deprecated public void setAttribute(String key, Object value) { if (!AVUtils.isBlankContent(key)) { // 以往的 sdk 支持 setAttribute("attr.key", "attrValue") 这种格式,这里兼容一下 if (key.startsWith(ATTR_PERFIX)) { this.pendingAttributes.put(key.substring(ATTR_PERFIX.length()), value); } else { this.pendingAttributes.put(key, value); } } } /** * 设置当前聊天对话的属性 * * @param attr * @since 3.0 */ @Deprecated public void setAttributes(Map attr) { pendingAttributes.clear(); pendingAttributes.putAll(attr); } /** * 设置当前聊天对话的属性,仅用于初始化时 * 因为 attr 涉及到本地缓存,所以初始化时与主动调用 setAttributes 行为不同 * @param attr */ void setAttributesForInit(Map attr) { this.attributes.clear(); if (attr != null) { this.attributes.putAll(attr); } } /** * 获取conversation的名字 * * @return */ public String getName() { return (String) getAttribute(Conversation.NAME); } public void setName(String name) { pendingAttributes.put(Conversation.NAME, name); } /** * 获取最新一条消息的时间 * * @return */ public Date getLastMessageAt() { AVIMMessage lastMessage = getLastMessage(); if (null != lastMessage) { setLastMessageAt(new Date(lastMessage.getReceiptTimestamp())); } return lastMessageAt; } void setLastMessageAt(Date messageTime) { if (null != messageTime && (null == lastMessageAt || messageTime.after(this.lastMessageAt))) { this.lastMessageAt = messageTime; } } /** * 获取最新一条消息 * * @return */ public AVIMMessage getLastMessage() { if (AVIMClient.messageQueryCacheEnabled && !isSyncLastMessage) { setLastMessage(getLastMessageFromLocal()); } return lastMessage; } private AVIMMessage getLastMessageFromLocal() { if (AVIMClient.messageQueryCacheEnabled) { AVIMMessage lastMessageInLocal = storage.getLatestMessage(getConversationId()); isSyncLastMessage = true; return lastMessageInLocal; } return null; } /** * lastMessage 的来源: * 1. sqlite conversation 表中的 lastMessage,conversation query 后存储下来的 * 2. sqlite message 表中存储的最新的消息 * 3. 实时通讯推送过来的消息也要及时更新 lastMessage * @param lastMessage */ void setLastMessage(AVIMMessage lastMessage) { if (null != lastMessage) { if (null == this.lastMessage) { this.lastMessage = lastMessage; } else { if(this.lastMessage.getTimestamp() <= lastMessage.getTimestamp()) { this.lastMessage = lastMessage; } else { LogUtil.log.d("lastMessage timestamp is invalid. existed=" + this.lastMessage.getTimestamp() + ", newValue=" + lastMessage.getTimestamp()); } } } } void increaseUnreadCount(int num, boolean mentioned) { unreadMessagesCount = getUnreadMessagesCount() + num; if (mentioned) { unreadMessagesMentioned = mentioned; } } void updateUnreadCountAndMessage(AVIMMessage lastMessage, int unreadCount, boolean mentioned) { if (null != lastMessage) { setLastMessage(lastMessage); storage.insertMessage(lastMessage, true); } if (unreadMessagesCount != unreadCount) { unreadMessagesCount = unreadCount; unreadMessagesMentioned = mentioned; storage.updateConversationUreadCount(conversationId, unreadMessagesCount, mentioned); } } /** * 获取当前未读消息数量 * @return */ public int getUnreadMessagesCount() { return unreadMessagesCount; } /** * 判断当前未读消息中是否有提及当前用户的消息存在。 * @return */ public boolean unreadMessagesMentioned() { return unreadMessagesMentioned; } /** * 清除未读消息 */ public void read() { if (!isTransient) { AVIMMessage lastMessage = getLastMessage(); Map params = new HashMap(); if (null != lastMessage) { params.put(Conversation.PARAM_MESSAGE_QUERY_MSGID, lastMessage.getMessageId()); params.put(Conversation.PARAM_MESSAGE_QUERY_TIMESTAMP, lastMessage.getTimestamp()); } this.sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_READ, null, null); } } /** * 获取Conversation的创建时间 * * @return */ public Date getCreatedAt() { return AVUtils.dateFromString(createdAt); } void setCreatedAt(String createdAt) { this.createdAt = createdAt; } /** * 获取Conversation的更新时间 * * @return */ public Date getUpdatedAt() { return AVUtils.dateFromString(updatedAt); } void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; } public void fetchReceiptTimestamps(AVIMConversationCallback callback) { boolean ret = sendCMDToPushService(null, AVIMOperation.CONVERSATION_FETCH_RECEIPT_TIME, callback, null); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } /** * 更新当前对话的属性至服务器端 * * @param callback * @since 3.0 */ public void updateInfoInBackground(AVIMConversationCallback callback) { if (!pendingAttributes.isEmpty() || !pendingInstanceData.isEmpty()) { Map attributesMap = new HashMap<>(); if (!pendingAttributes.isEmpty()) { final Map pendingAttrMap = processAttributes(pendingAttributes, false); if (null != pendingAttrMap) { attributesMap.putAll(pendingAttrMap); } } if (!pendingInstanceData.isEmpty()) { attributesMap.putAll(pendingInstanceData); } Map params = new HashMap(); if (!attributesMap.isEmpty()) { params.put(Conversation.PARAM_CONVERSATION_ATTRIBUTE, attributesMap); } boolean ret = this.sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_UPDATE, callback, new OperationCompleteCallback() { @Override public void onComplete() { attributes.putAll(pendingAttributes); pendingAttributes.clear(); instanceData.putAll(pendingInstanceData); pendingInstanceData.clear(); storage.insertConversations(Arrays.asList(AVIMConversation.this)); } @Override public void onFailure() { } }); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } else { callback.internalDone(null); } } /** * 从服务器同步对话的属性 * * @param callback */ public void fetchInfoInBackground(final AVIMConversationCallback callback) { if (AVUtils.isBlankString(conversationId)) { if (callback != null) { callback.internalDone(null, new AVException(AVException.INVALID_QUERY, "ConversationId is empty")); } else { LogUtil.avlog.e("ConversationId is empty"); } return; } Map params = getFetchRequestParams(); boolean ret = sendCMDToPushService(JSON.toJSONString(params), AVIMOperation.CONVERSATION_QUERY, callback); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } public Map getFetchRequestParams() { Map params = new HashMap(); if (conversationId.startsWith(Conversation.TEMPCONV_ID_PREFIX)) { params.put(Conversation.QUERY_PARAM_TEMPCONV, conversationId); } else { Map whereMap = new HashMap(); whereMap.put("objectId", conversationId); params.put(Conversation.QUERY_PARAM_WHERE, whereMap); } return params; } /** * 加入当前聊天对话 * * @param callback */ public void join(AVIMConversationCallback callback) { boolean ret = this.sendCMDToPushService(null, AVIMOperation.CONVERSATION_JOIN, callback, new OperationCompleteCallback() { @Override public void onComplete() { members.add(client.getClientId()); } @Override public void onFailure() { } }); if (!ret && null != callback) { callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background.")); } } public boolean isTransient() { return isTransient; } void setTransientForInit(boolean isTransient) { this.isTransient = isTransient; } /** * 超时的时间间隔设置为一个小时,即 fetch 操作并且返回了错误,则一个小时内 sdk 不再进行调用 fetch */ int FETCH_TIME_INTERVEL = 3600 * 1000; /** * 最近的 sdk 调用的 fetch 操作的时间 */ long latestConversationFetch = 0; /** * 判断当前 Conversation 是否有效,因为 AVIMConversation 为客户端创建,有可能因为没有同步造成数据丢失 * 可以根据此函数来判断,如果无效,则需要调用 fetchInfoInBackground 同步数据 * 如果 fetchInfoInBackground 出错(比如因为 acl 问题造成 Forbidden to find by class permissions ), * 客户端就会在收到消息后一直做 fetch 操作,所以这里加了一个判断,如果在 FETCH_TIME_INTERVEL 内有业务类型的 * error code 返回,则不在请求 */ public boolean isShouldFetch() { return null == getCreatedAt() || (System.currentTimeMillis() - latestConversationFetch > FETCH_TIME_INTERVEL); } public void setMustFetch() { latestConversationFetch = 0; } private void processContinuousMessages(List messages) { if (null != messages && !messages.isEmpty()) { Collections.sort(messages, messageComparator); setLastMessage(messages.get(messages.size() - 1)); storage.insertContinuousMessages(messages, conversationId); } } void updateLocalMessage(AVIMMessage message) { storage.updateMessageForPatch(message); } private boolean sendParcelToPushService(final PushServiceParcel pushServiceParcel, final AVIMOperation operation, AVCallback callback) { final int requestId = AVUtils.getNextIMRequestId(); Intent i = new Intent(AVOSCloud.applicationContext, PushService.class); i.setAction(Conversation.AV_CONVERSATION_PARCEL_ACTION); i.putExtra(Conversation.INTENT_KEY_DATA, pushServiceParcel); i.putExtra(Conversation.INTENT_KEY_CLIENT, client.clientId); i.putExtra(Conversation.INTENT_KEY_CONVERSATION, this.conversationId); i.putExtra(Conversation.INTENT_KEY_CONV_TYPE, this.getType()); i.putExtra(Conversation.INTENT_KEY_REQUESTID, requestId); i.putExtra(Conversation.INTENT_KEY_OPERATION, operation.getCode()); try { AVOSCloud.applicationContext.startService(IntentUtil.setupIntentFlags(i)); if (callback != null) { LocalBroadcastManager.getInstance(AVOSCloud.applicationContext).registerReceiver( new AVIMBaseBroadcastReceiver(callback) { @Override public void execute(Intent intent, Throwable error) { if (null == error) { long patchTime = intent.getLongExtra(Conversation.PARAM_MESSAGE_PATCH_TIME, 0); if (operation.equals(AVIMOperation.CONVERSATION_RECALL_MESSAGE)) { onMessageRecalled(pushServiceParcel, patchTime, callback); } else { onMessageUpdated(pushServiceParcel, patchTime, callback); } } else { callback.internalDone(new AVException(error)); } } }, new IntentFilter(operation.getOperation() + requestId)); } return true; } catch (Exception ex) { LogUtil.avlog.e("failed to startService. cause: " + ex.getMessage()); return false; } } private void onMessageRecalled(PushServiceParcel pushServiceParcel, long patchTime, AVCallback callback) { AVIMMessage oldMessage = pushServiceParcel.getRecallMessage(); AVIMMessage recalledMessage = new AVIMRecalledMessage(); copyMessageWithoutContent(oldMessage, recalledMessage); recalledMessage.setUpdateAt(patchTime); recalledMessage.setMessageStatus(AVIMMessage.AVIMMessageStatus.AVIMMessageStatusRecalled); updateLocalMessage(recalledMessage); callback.internalDone(recalledMessage, null); } private void onMessageUpdated(PushServiceParcel pushServiceParcel, long patchTime, AVCallback callback) { AVIMMessage oldMessage = pushServiceParcel.getOldMessage(); AVIMMessage newMessage = pushServiceParcel.getNewMessage(); copyMessageWithoutContent(oldMessage, newMessage); newMessage.setUpdateAt(patchTime); updateLocalMessage(newMessage); callback.internalDone(newMessage, null); } private void copyMessageWithoutContent(AVIMMessage oldMessage, AVIMMessage newMessage) { newMessage.setMessageId(oldMessage.getMessageId()); newMessage.setConversationId(oldMessage.getConversationId()); newMessage.setFrom(oldMessage.getFrom()); newMessage.setDeliveredAt(oldMessage.getDeliveredAt()); newMessage.setReadAt(oldMessage.getReadAt()); newMessage.setTimestamp(oldMessage.getTimestamp()); newMessage.setMessageStatus(oldMessage.getMessageStatus()); newMessage.setMessageIOType(oldMessage.getMessageIOType()); } private boolean sendCMDToPushService(String dataInString, final AVIMOperation operation, final AVCallback callback, final OperationCompleteCallback occ) { return sendCMDToPushService(dataInString, null, null, operation, callback, occ); } protected boolean sendCMDToPushService(String dataInString, final AVIMOperation operation, AVCallback callback) { return sendCMDToPushService(dataInString, null, null, operation, callback, null); } private boolean sendNonSideEffectCommand(String dataInString, final AVIMOperation operation, AVCallback callback) { if (null == callback) { return true; } final int requestId = AVUtils.getNextIMRequestId(); Intent i = new Intent(AVOSCloud.applicationContext, PushService.class); i.setAction(Conversation.AV_CONVERSATION_INTENT_ACTION); if (!AVUtils.isBlankString(dataInString)) { i.putExtra(Conversation.INTENT_KEY_DATA, dataInString); } i.putExtra(Conversation.INTENT_KEY_CLIENT, client.clientId); i.putExtra(Conversation.INTENT_KEY_CONVERSATION, this.conversationId); i.putExtra(Conversation.INTENT_KEY_CONV_TYPE, this.getType()); i.putExtra(Conversation.INTENT_KEY_OPERATION, operation.getCode()); i.putExtra(Conversation.INTENT_KEY_REQUESTID, requestId); try { AVOSCloud.applicationContext.startService(IntentUtil.setupIntentFlags(i)); LocalBroadcastManager.getInstance(AVOSCloud.applicationContext).registerReceiver( new AVIMBaseBroadcastReceiver(callback) { @Override public void execute(Intent intent, Throwable ex) { // 处理历史消息查询 if (operation.getCode() == AVIMOperation.CONVERSATION_MESSAGE_QUERY.getCode()) { List historyMessages = intent.getParcelableArrayListExtra(Conversation.callbackHistoryMessages); if (ex != null) { callback.internalDone(null, new AVIMException(ex)); } else { if (historyMessages == null) { historyMessages = Collections.EMPTY_LIST; } callback.internalDone(historyMessages, null); } return; } } }, new IntentFilter(operation.getOperation() + requestId)); return true; } catch (Exception ex) { LogUtil.avlog.e("failed to startService. cause: " + ex.getMessage()); return false; } } private boolean sendCMDToPushService(String dataInString, final AVIMMessage message, final AVIMMessageOption messageOption, final AVIMOperation operation, final AVCallback callback, final OperationCompleteCallback occ) { final int requestId = AVUtils.getNextIMRequestId(); Intent i = new Intent(AVOSCloud.applicationContext, PushService.class); i.setAction(Conversation.AV_CONVERSATION_INTENT_ACTION); if (!AVUtils.isBlankString(dataInString)) { i.putExtra(Conversation.INTENT_KEY_DATA, dataInString); } if (message != null) { i.putExtra(Conversation.INTENT_KEY_DATA, message); if (null != messageOption) { i.putExtra(Conversation.INTENT_KEY_MESSAGE_OPTION, messageOption); // 消息等级仅针对聊天室有效,如非聊天室,此处打印 log 提示 if (!isTransient && null != messageOption.getPriority() && AVOSCloud.isDebugLogEnabled()) { LogUtil.avlog.d("Message priority is invalid in transient conversation"); } } } i.putExtra(Conversation.INTENT_KEY_CLIENT, client.clientId); i.putExtra(Conversation.INTENT_KEY_CONVERSATION, this.conversationId); i.putExtra(Conversation.INTENT_KEY_CONV_TYPE, this.getType()); i.putExtra(Conversation.INTENT_KEY_OPERATION, operation.getCode()); i.putExtra(Conversation.INTENT_KEY_REQUESTID, requestId); try { AVOSCloud.applicationContext.startService(IntentUtil.setupIntentFlags(i)); if (callback != null) { LocalBroadcastManager.getInstance(AVOSCloud.applicationContext).registerReceiver( new AVIMBaseBroadcastReceiver(callback) { @Override public void execute(Intent intent, Throwable error) { // 处理退出命令时 if (operation.getCode() == AVIMOperation.CONVERSATION_QUIT.getCode()) { client.removeConversationCache(AVIMConversation.this); } // 处理side effect调用 if (error == null && occ != null) { occ.onComplete(); } else if (error != null && occ != null) { occ.onFailure(); } // 消息命令 if (message != null) { if (error == null) { // 处理发送成功的消息 long timestamp = intent.getExtras().getLong(Conversation.callbackMessageTimeStamp, -1); String messageId = intent.getStringExtra(Conversation.callbackMessageId); message.setMessageId(messageId); message.setMessageStatus(AVIMMessage.AVIMMessageStatus.AVIMMessageStatusSent); if (timestamp != -1) { message.setTimestamp(timestamp); } if ((null == messageOption || !messageOption.isTransient()) && AVIMClient.messageQueryCacheEnabled) { setLastMessage(message); storage.insertMessage(message, false); } else { LogUtil.avlog.d("skip inserting into local storage."); } AVIMConversation.this.lastMessageAt = new Date(timestamp); storage.updateConversationLastMessageAt(AVIMConversation.this); } else { message.setMessageStatus(AVIMMessage.AVIMMessageStatus.AVIMMessageStatusFailed); } } // 处理历史消息查询 if (operation.getCode() == AVIMOperation.CONVERSATION_MESSAGE_QUERY.getCode()) { List historyMessages = intent.getParcelableArrayListExtra(Conversation.callbackHistoryMessages); if (error != null) { // modify by jwfing[2017/8/23] not return exception to external caller. callback.internalDone(null, new AVIMException(AVIMException.TIMEOUT, AVException.CONNECTION_FAILED, error.getMessage())); } else { setLastReadAt(intent.getLongExtra(Conversation.callbackReadAt, 0L), false); setLastDeliveredAt(intent.getLongExtra(Conversation.callbackDeliveredAt, 0L), false); storage.updateConversationTimes(AVIMConversation.this); if (historyMessages == null) { historyMessages = Collections.EMPTY_LIST; } callback.internalDone(historyMessages, null); } return; } // 刷新Conversation 更新时间 if (operation.getCode() == AVIMOperation.CONVERSATION_UPDATE.getCode()) { if (intent.getExtras().containsKey(Conversation.callbackUpdatedAt)) { String updatedAt = intent.getExtras().getString(Conversation.callbackUpdatedAt); AVIMConversation.this.updatedAt = updatedAt; } } // 处理成员数量查询 if (operation.getCode() == AVIMOperation.CONVERSATION_MEMBER_COUNT_QUERY.getCode()) { int memberCount = intent.getIntExtra(Conversation.callbackMemberCount, 0); callback.internalDone(memberCount, AVIMException.wrapperAVException(error)); return; } if (operation.getCode() == AVIMOperation.CONVERSATION_FETCH_RECEIPT_TIME.getCode()) { setLastReadAt(intent.getLongExtra(Conversation.callbackReadAt, 0L), false); setLastDeliveredAt(intent.getLongExtra(Conversation.callbackDeliveredAt, 0L), false); storage.updateConversationTimes(AVIMConversation.this); callback.internalDone(null, AVIMException.wrapperAVException(error)); return; } // 处理对成员禁言/拉黑系操作 if (operation.getCode() == AVIMOperation.CONVERSATION_MUTE_MEMBER.getCode() || operation.getCode() == AVIMOperation.CONVERSATION_UNMUTE_MEMBER.getCode() || operation.getCode() == AVIMOperation.CONVERSATION_BLOCK_MEMBER.getCode() || operation.getCode() == AVIMOperation.CONVERSATION_UNBLOCK_MEMBER.getCode()) { String[] allowedList = intent.getStringArrayExtra(Conversation.callbackConvMemberMuted_SUCC); ArrayList failedList = intent.getParcelableArrayListExtra(Conversation.callbackConvMemberMuted_FAIL); Map result = new HashMap<>(); result.put(Conversation.callbackConvMemberMuted_SUCC, allowedList); result.put(Conversation.callbackConvMemberMuted_FAIL, failedList); callback.internalDone(result, AVIMException.wrapperAVException(error)); return; } // 处理被禁言成员列表查询 if (operation.getCode() == AVIMOperation.CONVERSATION_MUTED_MEMBER_QUERY.getCode() || operation.getCode() == AVIMOperation.CONVERSATION_BLOCKED_MEMBER_QUERY.getCode()) { String[] result = intent.getStringArrayExtra(Conversation.callbackData); callback.internalDone(null != result ? Arrays.asList(result) : null, AVIMException.wrapperAVException(error)); return; } if (operation.getCode() == AVIMOperation.CONVERSATION_QUERY.getCode()) { // for fetchInfoInBackground() method. if (null != error) { callback.internalDone(null, AVIMException.wrapperAVException(error)); } else { Exception exception = processQueryResult(intent.getExtras().getSerializable(Conversation.callbackData)); callback.internalDone(null, AVIMException.wrapperAVException(exception)); } return; } callback.internalDone(null, AVIMException.wrapperAVException(error)); } }, new IntentFilter(operation.getOperation() + requestId)); } } catch (Exception ex) { LogUtil.avlog.e("failed to startService. cause: " + ex.getMessage()); return false; } return true; } /** * 处理 query 的返回结果 * @param serializable * @return */ public Exception processQueryResult(Serializable serializable) { if (null != serializable) { try { String result = (String)serializable; JSONArray jsonArray = JSON.parseArray(String.valueOf(result)); if (null != jsonArray && !jsonArray.isEmpty()) { JSONObject jsonObject = jsonArray.getJSONObject(0); updateConversation(this, jsonObject); client.mergeConversationCache(this, true, null); storage.insertConversations(Arrays.asList(this)); latestConversationFetch = System.currentTimeMillis(); } else { // not found conversation return new AVIMException(9100, "Conversation not found"); } } catch (Exception e) { return e; } } else { return new AVIMException(9100, "Conversation not found"); } return null; } /** * parse AVIMConversation from jsonObject * @param client * @param jsonObj * @return */ public static AVIMConversation parseFromJson(AVIMClient client, JSONObject jsonObj) { if (null == jsonObj || null == client) { return null; } String conversationId = jsonObj.getString(AVObject.OBJECT_ID); if (AVUtils.isBlankContent(conversationId)) { return null; } boolean systemConv = false; boolean transientConv = false; boolean tempConv = false; if (jsonObj.containsKey(Conversation.SYSTEM)) { systemConv = jsonObj.getBoolean(Conversation.SYSTEM); } if (jsonObj.containsKey(Conversation.TRANSIENT)) { transientConv = jsonObj.getBoolean(Conversation.TRANSIENT); } if (jsonObj.containsKey(Conversation.TEMPORARY)) { tempConv = jsonObj.getBoolean(Conversation.TEMPORARY); } AVIMConversation originConv = null; if (systemConv) { originConv = new AVIMServiceConversation(client, conversationId); } else if (tempConv) { originConv = new AVIMTemporaryConversation(client, conversationId); } else if (transientConv) { originConv = new AVIMChatRoom(client, conversationId); } else { originConv = new AVIMConversation(client, conversationId); } originConv.latestConversationFetch = System.currentTimeMillis(); return updateConversation(originConv, jsonObj); } public static void mergeConversationFromJsonObject(AVIMConversation conversation, JSONObject jsonObj) { if (null == conversation || null == jsonObj) { return; } // Notice: cannot update deleted attr. HashMap attributes = new HashMap(); if (jsonObj.containsKey(Conversation.NAME)) { attributes.put(Conversation.NAME, jsonObj.getString(Conversation.NAME)); } if (jsonObj.containsKey(Conversation.ATTRIBUTE)) { JSONObject moreAttributes = jsonObj.getJSONObject(Conversation.ATTRIBUTE); if (moreAttributes != null) { Map moreAttributesMap = JSON.toJavaObject(moreAttributes, Map.class); attributes.putAll(moreAttributesMap); } } conversation.attributes.putAll(attributes); Set keySet = jsonObj.keySet(); if (!keySet.isEmpty()) { for (String key : keySet) { if (!Arrays.asList(Conversation.CONVERSATION_COLUMNS).contains(key)) { conversation.instanceData.put(key, jsonObj.get(key)); } } } // conversation.latestConversationFetch = System.currentTimeMillis(); } static AVIMConversation updateConversation(AVIMConversation conversation, JSONObject jsonObj) { if (null == jsonObj || null == conversation) { return conversation; } String conversationId = jsonObj.getString(AVObject.OBJECT_ID); List m = jsonObj.getObject(Conversation.MEMBERS, List.class); conversation.setMembers(m); conversation.setCreator(jsonObj.getString(Conversation.CREATOR)); HashMap attributes = new HashMap(); if (jsonObj.containsKey(Conversation.NAME)) { attributes.put(Conversation.NAME, jsonObj.getString(Conversation.NAME)); } if (jsonObj.containsKey(Conversation.ATTRIBUTE)) { JSONObject moreAttributes = jsonObj.getJSONObject(Conversation.ATTRIBUTE); if (moreAttributes != null) { Map moreAttributesMap = JSON.toJavaObject(moreAttributes, Map.class); attributes.putAll(moreAttributesMap); } } conversation.setAttributesForInit(attributes); conversation.instanceData.clear(); Set keySet = jsonObj.keySet(); if (!keySet.isEmpty()) { for (String key : keySet) { if (!Arrays.asList(Conversation.CONVERSATION_COLUMNS).contains(key)) { conversation.instanceData.put(key, jsonObj.get(key)); } } } if (jsonObj.containsKey(Conversation.SYSTEM)) { conversation.instanceData.put(Conversation.SYSTEM, jsonObj.get(Conversation.SYSTEM)); } if (jsonObj.containsKey(Conversation.MUTE)) { conversation.instanceData.put(Conversation.MUTE, jsonObj.get(Conversation.MUTE)); } if (jsonObj.containsKey(AVObject.CREATED_AT)) { conversation.setCreatedAt(jsonObj.getString(AVObject.CREATED_AT)); } if (jsonObj.containsKey(AVObject.UPDATED_AT)) { conversation.setUpdatedAt(jsonObj.getString(AVObject.UPDATED_AT)); } AVIMMessage message = AVIMTypedMessage.parseMessage(conversationId, jsonObj); conversation.setLastMessage(message); if (jsonObj.containsKey(Conversation.LAST_MESSAGE_AT)) { conversation.setLastMessageAt(AVUtils.dateFromMap(jsonObj.getObject(Conversation.LAST_MESSAGE_AT, Map.class))); } if (jsonObj.containsKey(Conversation.TRANSIENT)) { conversation.isTransient = jsonObj.getBoolean(Conversation.TRANSIENT); } return conversation; } /** * 处理 AVIMConversation attr 列 * 因为 sdk 支持增量更新与覆盖更新,而增量更新与覆盖更新需要的结构不一样,所以这里处理一下 * 具体格式可参照下边的注释,注意,两种格式不能同时存在,否则 server 会报错 * @param attributes * @param isCovered * @return */ static Map processAttributes(Map attributes, boolean isCovered) { if (isCovered) { return processAttributesForCovering(attributes); } else { return processAttributesForIncremental(attributes); } } /** * 增量更新 attributes * 这里处理完的格式应该类似为 {"attr.key1":"value2", "attr.key2":"value2"} * @param attributes * @return */ static Map processAttributesForIncremental(Map attributes) { Map attributeMap = new HashMap<>(); if (attributes.containsKey(Conversation.NAME)) { attributeMap.put(Conversation.NAME, attributes.get(Conversation.NAME)); } for (String k : attributes.keySet()) { if (!Arrays.asList(Conversation.CONVERSATION_COLUMNS).contains(k)) { attributeMap.put(ATTR_PERFIX + k, attributes.get(k)); } } if (attributeMap.isEmpty()) { return null; } return attributeMap; } /** * 覆盖更新 attributes * 这里处理完的格式应该类似为 {"attr":{"key1":"value1","key2":"value2"}} * @param attributes * @return */ static JSONObject processAttributesForCovering(Map attributes) { HashMap attributeMap = new HashMap(); if (attributes.containsKey(Conversation.NAME)) { attributeMap.put(Conversation.NAME, attributes.get(Conversation.NAME)); } Map innerAttribute = new HashMap(); for (String k : attributes.keySet()) { if (!Arrays.asList(Conversation.CONVERSATION_COLUMNS).contains(k)) { innerAttribute.put(k, attributes.get(k)); } } if (!innerAttribute.isEmpty()) { attributeMap.put(Conversation.ATTRIBUTE, innerAttribute); } if (attributeMap.isEmpty()) { return null; } return new JSONObject(attributeMap); } static Comparator messageComparator = new Comparator() { @Override public int compare(AVIMMessage m1, AVIMMessage m2) { if (m1.getTimestamp() < m2.getTimestamp()) { return -1; } else if (m1.getTimestamp() > m2.getTimestamp()) { return 1; } else { return m1.messageId.compareTo(m2.messageId); } } }; interface OperationCompleteCallback { void onComplete(); void onFailure(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy