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

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

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.avos.avoscloud.AVException;
import com.avos.avoscloud.AVIMClientParcel;
import com.avos.avoscloud.AVOSCloud;
import com.avos.avoscloud.AVPowerfulUtils;
import com.avos.avoscloud.AVQuery;
import com.avos.avoscloud.AVRequestParams;
import com.avos.avoscloud.AVResponse;
import com.avos.avoscloud.AVRuntimeException;
import com.avos.avoscloud.AVSession;
import com.avos.avoscloud.AVUser;
import com.avos.avoscloud.AVUtils;
import com.avos.avoscloud.GenericObjectCallback;
import com.avos.avoscloud.IntentUtil;
import com.avos.avoscloud.LogUtil;
import com.avos.avoscloud.PaasClient;
import com.avos.avoscloud.PushService;
import com.avos.avoscloud.QueryConditions;
import com.avos.avoscloud.SignatureFactory;
import com.avos.avoscloud.im.v2.Conversation.AVIMOperation;
import com.avos.avoscloud.im.v2.callback.AVIMClientCallback;
import com.avos.avoscloud.im.v2.callback.AVIMClientStatusCallback;
import com.avos.avoscloud.im.v2.callback.AVIMConversationCreatedCallback;
import com.avos.avoscloud.im.v2.callback.AVIMConversationMemberQueryCallback;
import com.avos.avoscloud.im.v2.callback.AVIMConversationQueryCallback;
import com.avos.avoscloud.im.v2.callback.AVIMOnlineClientsCallback;
import com.avos.avoscloud.im.v2.conversation.AVIMConversationMemberInfo;
import com.avos.avoscloud.utils.StringUtils;

/**
 * 实时聊天的实例类,一个实例表示一个用户的登录
 *
 * 每个用户的登录都是一个单例
 * 
 */
public class AVIMClient {

  String clientId;
  AVIMMessageStorage storage;
  private String tag;
  private String userSessionToken;
  static ConcurrentHashMap clients =
      new ConcurrentHashMap();
  private ConcurrentHashMap conversationCache =
      new ConcurrentHashMap();
  volatile boolean isConversationSync = false;

  private static boolean isAutoOpen = true;
  private static int REALTIME_TOKEN_WINDOW_INSECOND = 5 * 60;

  public SignatureFactory getSignatureFactory() {
    return AVIMOptions.getGlobalOptions().getSignatureFactory();
  }

  /**
   * 设置 AVIMClient 通用的签名生成工厂
   * 
   * @param factory
   * @since 3.0
   */

  public static void setSignatureFactory(SignatureFactory factory) {
    AVIMOptions.getGlobalOptions().setSignatureFactory(factory);
  }

  /**
   * 设置实时通信的超时时间
   *
   * 默认为15s
   *
   * @param timeoutInSecs 设置超时时间
   */

  public static void setTimeoutInSecs(int timeoutInSecs) {
    AVIMOptions.getGlobalOptions().setTimeoutInSecs(timeoutInSecs);
  }

  /**
   * 设置实时通信是否要在 App 重新启动后自动登录
   * @param isAuto
   */
  public static void setAutoOpen(boolean isAuto) {
    isAutoOpen = isAuto;
  }

  /**
   * 实时通信是否要在 App 重新启动后自动登录
   */
  public static boolean isAutoOpen() {
    return isAutoOpen;
  }

  /**
   * 实时通信的 sessiontoken 信息
   */
  private String realtimeSessionToken = null;
  private long realtimeSessionTokenExpired = 0l;

  private AVIMClient(String clientId) {
    this.clientId = clientId;
    storage = AVIMMessageStorage.getInstance(clientId);
  }

  /**
   * 获取一个聊天客户端实例
   * 
   * @param clientId 当前客户端登录账号的id
   * @return
   * @since 3.0
   */
  public static AVIMClient getInstance(String clientId) {
    if (AVUtils.isBlankString(clientId)) {
      throw new IllegalArgumentException("clientId cannot be null.");
    }

    AVIMClient client = clients.get(clientId);
    if (client != null) {
      return client;
    } else {
      client = new AVIMClient(clientId);
      AVIMClient elderClient = clients.putIfAbsent(clientId, client);
      return elderClient == null ? client : elderClient;
    }
  }

  /**
   * 返回同时使用的聊天实例数量
   *
   * @return
   * @since 4.6
   */
  public static int getClientsCount() {
    return clients.size();
  }

  /**
   * 返回默认使用的 clientId(仅在只有一个 clientId 的时候有效)
   *
   * @return
   * @since 4.6
   */
  public static String getDefaultClient() {
    if (getClientsCount() == 1) {
      return clients.keys().nextElement();
    }
    return "";
  }

  /**
   * 获取聊天客户端实例,并且标记这个客户端实例的tag
   * 
   * @param clientId 当前客户端登录账号的id
   * @param tag 用于标注客户端,以支持单点登录功能
   * @return
   */
  public static AVIMClient getInstance(String clientId, String tag) {
    AVIMClient client = getInstance(clientId);
    client.tag = tag;
    return client;
  }

  /**
   * Get the AVIMClient that is instantiated by AVUser
   * @param user the related AVUser
   * @return
   */
  public static AVIMClient getInstance(AVUser user) {
    if (null == user) {
      throw new IllegalArgumentException("user cannot be null.");
    }

    String clientId = user.getObjectId();
    String sessionToken = user.getSessionToken();
    if (AVUtils.isBlankString(clientId) || AVUtils.isBlankString(sessionToken)) {
      throw new IllegalArgumentException("user must login first.");
    }

    AVIMClient client = getInstance(clientId);
    client.userSessionToken = sessionToken;
    return client;
  }

  /**
   * Get the AVIMClient that is instantiated by AVUser and tag
   * @param user the related AVUser
   * @param tag the related tag,used for single login
   * @return
   */
  public static AVIMClient getInstance(AVUser user, String tag) {
    AVIMClient client = getInstance(user);
    client.tag = tag;
    return client;
  }

  public void updateRealtimeSessionToken(String sessionToken, long expireInSec) {
    this.realtimeSessionToken = sessionToken;
    this.realtimeSessionTokenExpired = expireInSec;
  }

  public String getRealtimeSessionToken() {
    return this.realtimeSessionToken;
  }

  boolean realtimeSessionTokenExpired() {
    long now = System.currentTimeMillis()/1000;
    return (now + REALTIME_TOKEN_WINDOW_INSECOND) >= this.realtimeSessionTokenExpired;
  }

  /**
   * 连接服务器
   * 
   * @param callback 回调函数
   * @since 3.0
   */
  public void open(final AVIMClientCallback callback) {
    open(null, callback);
  }

  /**
   * 连接服务器
   * @param option 登陆选项
   * @param callback 回调函数
   */
  public void open(AVIMClientOpenOption option, final AVIMClientCallback callback) {

    AVIMClientParcel parcel = new AVIMClientParcel();
    parcel.setClientTag(tag);
    parcel.setSessionToken(userSessionToken);
    if (null != option) {
      parcel.setReconnection(option.isReconnect());
    } else {
      parcel.setReconnection(false);
    }
    BroadcastReceiver receiver = null;

    if (callback != null) {
      receiver = new AVIMBaseBroadcastReceiver(callback) {
        @Override
        public void execute(Intent intent, Throwable error) {
          callback.internalDone(AVIMClient.this, AVIMException.wrapperAVException(error));
        }
      };
    }
    boolean ret = sendClientCommand(parcel, receiver, AVIMOperation.CLIENT_OPEN);
    if (!ret && null != callback) {
      callback.internalDone(AVIMClient.this, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background."));
    }
  }

  /**
   * 获取client列表中间当前为在线的client
   *
   * 每次查询最多20个client对象,超出部分会被忽略
   * 
   * @param clients 需要检查的client列表
   * @param callback 结果回调函数
   */
  public void getOnlineClients(List clients, final AVIMOnlineClientsCallback callback) {
    Map params = new HashMap();
    params.put(Conversation.PARAM_ONLINE_CLIENTS, clients);

    BroadcastReceiver receiver = null;

    if (callback != null) {
      receiver = new AVIMBaseBroadcastReceiver(callback) {
        @Override
        public void execute(Intent intent, Throwable error) {
          if (error != null) {
            callback.internalDone(null, AVIMException.wrapperAVException(error));
          } else {
            List onlineClients =
                intent.getStringArrayListExtra(Conversation.callbackOnlineClients);
            callback.internalDone(onlineClients, null);
          }
        }
      };
    }
    boolean ret = this.sendClientCMDToPushService(JSON.toJSONString(params), receiver,
        AVIMOperation.CLIENT_ONLINE_QUERY);
    if (!ret && null != callback) {
      callback.internalDone(new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background."));
    }
  }

  /**
   * 获取当前用户的 clientId
   * @return 返回clientId
   */
  public String getClientId() {
    return this.clientId;
  }

  /**
   * 创建一个聊天对话
   * 
   * @param conversationMembers 对话参与者
   * @param attributes 对话属性
   * @param callback   结果回调函数
   * @since 3.0
   */

  public void createConversation(final List conversationMembers,
      final Map attributes, final AVIMConversationCreatedCallback callback) {
    this.createConversation(conversationMembers, null, attributes, false, callback);
  }

  /**
   * 创建一个聊天对话
   *
   * @param conversationMembers 对话参与者
   * @param name       对话名字
   * @param attributes 对话属性
   * @param callback   结果回调函数
   * @since 3.0
   */
  public void createConversation(final List conversationMembers, String name,
      final Map attributes, final AVIMConversationCreatedCallback callback) {
    this.createConversation(conversationMembers, name, attributes, false, callback);
  }

  /**
   * 创建一个聊天对话
   *
   * @param members 对话参与者
   * @param attributes 对话的额外属性
   * @param isTransient 是否为暂态对话
   * @param callback  结果回调函数
   */
  public void createConversation(final List members, final String name,
                                 final Map attributes, final boolean isTransient,
                                 final AVIMConversationCreatedCallback callback) {
    this.createConversation(members, name, attributes, isTransient, true, callback);
  }

  /**
   * 创建或查询一个已有的开放聊天室
   *
   * @param conversationMembers 聊天室成员(ignored)
   * @param name 聊天室名字
   * @param attributes 聊天室的额外属性
   * @param isUnique 如果已经存在符合条件的会话,是否返回已有聊天室(ignored)
   * @param callback  结果回调函数
   * @since 4.6
   */
  public void createChatRoom(final List conversationMembers, String name,
                             final Map attributes, final boolean isUnique, final AVIMConversationCreatedCallback callback) {
    this.createConversation(conversationMembers, name, attributes, true, isUnique, callback);
  }

  /**
   * 创建一个服务号(原系统对话)
   * 注意:客户端不支持,所以这个方法总会抛出 RuntimeException
   * @param name 聊天室名字
   * @param attributes 聊天室的额外属性
   * @param callback  结果回调函数
   *
   * @since 4.6
   */
  private void createServiceConversation(String name, final Map attributes,
                                        final AVIMConversationCreatedCallback callback) {
    // Todo
    throw new AVRuntimeException("can't invoke createServiceConversation within SDK.", new UnsupportedOperationException());
  }

  /**
   * 创建一个临时对话(默认有效期:3 天)
   *
   * @param conversationMembers 对话成员
   * @param callback  结果回调函数
   * @since 4.6
   */
  public void createTemporaryConversation(final List conversationMembers,
                                          final AVIMConversationCreatedCallback callback) {
    this.createTemporaryConversation(conversationMembers, 86400*3, callback);
  }

  /**
   * 创建一个临时对话(默认有效期:3 天)
   *
   * @param conversationMembers 对话成员
   * @param ttl 有效期间,以秒为单位。例如一天有效期,那么 ttl = 86400
   * @param callback  结果回调函数
   * @since 4.6
   */
  public void createTemporaryConversation(final List conversationMembers, int ttl,
                                          final AVIMConversationCreatedCallback callback) {
    this.createConversation(conversationMembers, null, null, false, true, true, ttl, callback);
  }

  /**
   * 创建或查询一个已有 conversation
   *
   * @param members 对话的成员
   * @param name 对话的名字
   * @param attributes 对话的额外属性
   * @param isTransient 是否是暂态会话
   * @param isUnique 如果已经存在符合条件的会话,是否返回已有回话
   *                 为 false 时,则一直为创建新的回话
   *                 为 true 时,则先查询,如果已有符合条件的回话,则返回已有的,否则,创建新的并返回
   *                 为 true 时,仅 members 为有效查询条件
   * @param callback 结果回调函数
   */
  public void createConversation(final List members, final String name,
      final Map attributes, final boolean isTransient, final boolean isUnique,
      final AVIMConversationCreatedCallback callback) {
    this.createConversation(members, name, attributes, isTransient, isUnique, false, 0, callback);
  }

  private void createConversation(final List members, final String name,
                                  final Map attributes, final boolean isTransient, final boolean isUnique,
                                  final boolean isTemp, int tempTTL, final AVIMConversationCreatedCallback callback) {
    try {
      AVUtils.ensureElementsNotNull(members, AVSession.ERROR_INVALID_SESSION_ID);
    } catch (Exception e) {
      if (callback != null) {
        callback.internalDone(null, AVIMException.wrapperAVException(e));
      }
      return;
    }

    final HashMap conversationAttributes = new HashMap();
    if (attributes != null) {
      conversationAttributes.putAll(attributes);
    }
    if (!AVUtils.isBlankString(name)) {
      conversationAttributes.put(Conversation.NAME, name);
    }
    final List conversationMembers = new ArrayList();
    conversationMembers.addAll(members);
    if (!conversationMembers.contains(clientId)) {
      conversationMembers.add(clientId);
    }
    Map params = new HashMap();
    params.put(Conversation.PARAM_CONVERSATION_MEMBER, conversationMembers);
    params.put(Conversation.PARAM_CONVERSATION_ISUNIQUE, ((isTemp || isTransient)? false : isUnique));
    params.put(Conversation.PARAM_CONVERSATION_ISTRANSIENT, isTransient);
    params.put(Conversation.PARAM_CONVERSATION_ISTEMPORARY, isTemp);
    if (isTemp) {
      params.put(Conversation.PARAM_CONVERSATION_TEMPORARY_TTL, tempTTL);
    }
    if (conversationAttributes.size() > 0) {
      Map assembledAttributes = AVIMConversation.processAttributes(conversationAttributes, true);
      if (assembledAttributes != null) {
        params.put(Conversation.PARAM_CONVERSATION_ATTRIBUTE, assembledAttributes);
      }
    }

    BroadcastReceiver receiver = null;
    if (callback != null) {
      receiver = new AVIMBaseBroadcastReceiver(callback) {
        @Override
        public void execute(Intent intent, Throwable error) {
          String conversationId =
              intent.getExtras().getString(Conversation.callbackConversationKey);
          String createdAt = intent.getExtras().getString(Conversation.callbackCreatedAt);
          int tempTTLFromServer = intent.getExtras().getInt(Conversation.callbackTemporaryTTL, 0);
          AVIMConversation conversation = null;
          if (error == null) {
            conversation = getConversation(conversationId, isTransient, isTemp);
            conversation.setMembers(conversationMembers);
            conversation.setAttributesForInit(conversationAttributes);
            conversation.setTransientForInit(isTransient);
            conversation.setConversationId(conversationId);
            conversation.setCreator(clientId);
            conversation.setCreatedAt(createdAt);
            conversation.setUpdatedAt(createdAt);
            conversation.setTemporary(isTemp);
            conversation.setTemporaryExpiredat(System.currentTimeMillis()/1000 + tempTTLFromServer);
            storage.insertConversations(Arrays.asList(conversation));
          }
          callback.internalDone(conversation, AVIMException.wrapperAVException(error));
        }
      };
    }
    boolean ret = this.sendClientCMDToPushService(JSON.toJSONString(params), receiver,
        AVIMOperation.CONVERSATION_CREATION);
    if (!ret && null != callback) {
      callback.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background."));
    }
  }

  /**
   * 获取某个特定的聊天对话
   * 
   * @param conversationId 对应的是_Conversation表中的objectId
   * @return
   * @since 3.0
   */
  public AVIMConversation getConversation(String conversationId) {
    if (StringUtils.isBlankString(conversationId)) {
      return null;
    }
    return this.getConversation(conversationId, false, conversationId.startsWith(Conversation.TEMPCONV_ID_PREFIX));
  }

  /**
   * 获取某个特定的聊天对话
   *
   * @param conversationId 对话 id
   * @param convType       对话类型,可以参考 Conversation 里面的宏定义
   *                         普通对话:            CONV_TYPE_NORMAL = 1;
   *                         开放聊天室(暂态对话):CONV_TYPE_TRANSIENT = 2;
   *                         服务号(原系统对话):  CONV_TYPE_SYSTEM = 3;
   *                         临时对话:            CONV_TYPE_TEMPORARY = 4;
   * @return
   * @since 4.6
   */
  public AVIMConversation getConversation(String conversationId, int convType) {
    AVIMConversation result = null;
    switch (convType) {
      case Conversation.CONV_TYPE_SYSTEM:
        result = getServiceConversation(conversationId);
        break;
      case Conversation.CONV_TYPE_TEMPORARY:
        result = getTemporaryConversation(conversationId);
        break;
      case Conversation.CONV_TYPE_TRANSIENT:
        result = getChatRoom(conversationId);
        break;
      default:
        result = getConversation(conversationId);
        break;
    }
    return result;
  }

  /**
   * 获取一个开放聊天室
   *
   * @param conversationId 聊天室 id
   * @return
   * @since 4.6
   */
  public AVIMConversation getChatRoom(String conversationId) {
    return this.getConversation(conversationId, true, false);
  }

  /**
   * 获取一个服务号(系统对话)
   *
   * @param conversationId 对应的服务号 id
   * @return
   * @since 4.6
   */
  public AVIMConversation getServiceConversation(String conversationId) {
    return this.getConversation(conversationId, false, false, true);
  }

  /**
   * 获取一个临时对话
   *
   * @param conversationId 临时对话的 id
   * @return
   * @since 4.6
   */
  public AVIMConversation getTemporaryConversation(String conversationId) {
    return this.getConversation(conversationId, false, true);
  }

  /**
   * 获取某个特定的聊天对话
   *
   * @param conversationId 对话 id
   * @param isTransient 是否为 chatroom(暂态聊天室)
   * @param isTemporary 是否为 临时对话
   * @return
   * @since 4.6
   */
  public AVIMConversation getConversation(String conversationId, boolean isTransient, boolean isTemporary) {
    return this.getConversation(conversationId, isTransient, isTemporary, false);
  }

  private AVIMConversation getConversation(String conversationId, boolean isTransient, boolean isTemporary, boolean isSystem) {
    if (!isConversationSync) {
      syncConversationCache();
    }

    AVIMConversation conversation = conversationCache.get(conversationId);
    if (conversation != null) {
      return conversation;
    } else if (AVUtils.isBlankString(conversationId)) {
      LogUtil.log.w("conversationId is null");
      return null;
    } else {
      conversation = null;
      if (isSystem) {
        conversation = new AVIMServiceConversation(this, conversationId);
      } else if (isTemporary || conversationId.startsWith(Conversation.TEMPCONV_ID_PREFIX)) {
        conversation = new AVIMTemporaryConversation(this, conversationId);
      } else if (isTransient) {
        conversation = new AVIMChatRoom(this, conversationId);
      } else {
        conversation = new AVIMConversation(this, conversationId);
      }
      AVIMConversation elderConversation =
          conversationCache.putIfAbsent(conversationId, conversation);
      return elderConversation == null ? conversation : elderConversation;
    }
  }

  /**
   * 获取AVIMConversationQuery对象,以此来查询conversation
   * @deprecated 由于历史原因,AVIMConversationQuery 只能检索 _Conversation 表中 attr 列中的属性,
   * 而不能完整检索 _Conversation 表的其他自定义属性,所以这里废弃,推荐使用 {@link #getConversationsQuery()}
   * @return
   */
  public AVIMConversationQuery getQuery() {
    return new AVIMConversationQuery(this);
  }

  /**
   * 获取 AVIMConversationsQuery 对象,以此来查询 conversation
   * @return
   */
  public AVIMConversationsQuery getConversationsQuery() {
    return new AVIMConversationsQuery(this);
  }

  /**
   * 获取服务号的查询对象
   * 开发者拿到这个对象之后,就可以像 AVIMConversationsQuery 以前的接口一样对目标属性(如名字)等进行查询。
   * @return
   */
  public AVIMConversationsQuery getServiceConversationQuery() {
    AVIMConversationsQuery query = new AVIMConversationsQuery(this);
    query.whereEqualTo("sys", true);
    return query;
  }

  /**
   * 获取临时对话的查询对象
   * 开发者拿到这个对象之后,就可以像 AVIMConversationsQuery 以前的接口一样对目标属性(如名字)等进行查询。
   * @return
   */
  private AVIMConversationsQuery getTemporaryConversationQuery() {
    throw new UnsupportedOperationException("only conversationId query is allowed, please invoke #getTemporaryConversaton with conversationId.");
  }

  /**
   * 获取开放聊天室的查询对象
   * 开发者拿到这个对象之后,就可以像 AVIMConversationsQuery 以前的接口一样对目标属性(如名字)等进行查询。
   * @return
   */
  public AVIMConversationsQuery getChatRoomQuery() {
    AVIMConversationsQuery query = new AVIMConversationsQuery(this);
    query.whereEqualTo("tr", true);
    return query;
  }

  /**
   * 查询当前用户已经订阅的服务号
   *
   * @param limit     查询结果上限
   * @param callback  结果回调函数
   */
  private void querySubscribedServiceConversations(int limit, final AVIMConversationQueryCallback callback) {
    querySubscribedServiceConversations(null, 0, limit, callback);
  }

  /**
   * 查询当前用户已经订阅的服务号
   *
   * @param startConversationId         查询起始服务号 id
   * @param startConversationTimestamp  查询起始服务号被订阅的时间
   * @param limit                       查询结果上限
   * @param callback                    结果回调函数
   */
  private void querySubscribedServiceConversations(String startConversationId, long startConversationTimestamp,
                                                  int limit, final AVIMConversationQueryCallback callback) {
    ;
  }

  static AVIMClientEventHandler clientEventHandler;

  /**
   * 设置AVIMClient的事件处理单元,
   * 
   * 包括Client断开链接和重连成功事件
   * 
   * @param handler
   */
  public static void setClientEventHandler(AVIMClientEventHandler handler) {
    clientEventHandler = handler;
  }

  protected static AVIMClientEventHandler getClientEventHandler() {
    return clientEventHandler;
  }

  /**
   * 设置离线消息推送模式
   * @param isOnlyCount true 为仅推送离线消息数量,false 为推送离线消息
   * @deprecated Please use {@link #setUnreadNotificationEnabled(boolean)}
   */
  public static void setOfflineMessagePush(boolean isOnlyCount) {
    setUnreadNotificationEnabled(isOnlyCount);
  }

  /**
   * Set the mode of offline message push
   * @param enabled if the value is true,the number of unread messages will be pushed when the client open.
   *                if the value is false, the unread messages will be pushed when the client open.
   */
  public static void setUnreadNotificationEnabled(boolean enabled) {
    AVSession.setUnreadNotificationEnabled(enabled);
  }

  /**
   * 注销当前的聊天客户端链接
   * 
   * @param callback
   * @since 3.0
   */
  public void close(final AVIMClientCallback callback) {
    BroadcastReceiver receiver = null;
    if (callback != null) {
      receiver = new AVIMBaseBroadcastReceiver(callback) {
        @Override
        public void execute(Intent intent, Throwable error) {
          callback.internalDone(AVIMClient.this, AVIMException.wrapperAVException(error));
          AVIMClient.this.close();
        }
      };
    }
    boolean ret = this.sendClientCMDToPushService(null, receiver, AVIMOperation.CLIENT_DISCONNECT);
    if (!ret && null != callback) {
      callback.internalDone(AVIMClient.this, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background."));
    }
  }

  /**
   * Local close to clean up
   */
  protected void close() {
    clients.remove(clientId);
    conversationCache.clear();
    storage.deleteClientData();
  }

  void queryConversationMemberInfo(final QueryConditions queryConditions, final AVIMConversationMemberQueryCallback cb) {
    if (null == queryConditions || null == cb) {
      return;
    }
    if (!realtimeSessionTokenExpired()) {
      queryConvMemberThroughNetwork(queryConditions, cb);
    } else {
      // refresh realtime session token.
      LogUtil.log.d("realtime session token expired, start to refresh...");
      BroadcastReceiver receiver = null;
      receiver = new AVIMBaseBroadcastReceiver(null) {
        @Override
        public void execute(Intent intent, Throwable error) {
          if (null != error) {
            LogUtil.log.e("failed to refresh realtime session token. cause: " + error.getMessage());
            cb.internalDone(null, AVIMException.wrapperAVException(error));
          } else {
            queryConvMemberThroughNetwork(queryConditions, cb);
          }
        }
      };
      boolean ret = this.sendClientCMDToPushService(null, receiver, AVIMOperation.CLIENT_REFRESH_TOKEN);
      if (!ret && null != cb) {
        cb.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background."));
      }
    }
  }

  private void queryConvMemberThroughNetwork(final QueryConditions queryConditions, final AVIMConversationMemberQueryCallback callback) {
    String queryPath = AVPowerfulUtils.getEndpoint("_ConversationMemberInfo");

    queryConditions.assembleParameters();
    Map queryParams = queryConditions.getParameters();
    queryParams.put("client_id", this.clientId);
    AVRequestParams params = new AVRequestParams(queryParams);

    Map additionalHeader = new HashMap<>();
    additionalHeader.put("X-LC-IM-Session-Token", this.getRealtimeSessionToken());

    PaasClient.storageInstance().getObject(queryPath, params, false, additionalHeader, new GenericObjectCallback() {
      @Override
      public void onSuccess(String content, AVException e) {
        try {
          List result = processResults(content);
          if (callback != null) {
            callback.internalDone(result, null);
          }
        } catch (Exception ex) {
          LogUtil.log.e("failed to parse ConversationMemberInfo result, cause: " + ex.getMessage());
          if (callback != null) {
            callback.internalDone(null, new AVIMException(ex));
          }
        }
      }

      @Override
      public void onFailure(Throwable error, String content) {
        LogUtil.log.e("failed to fetch ConversationMemberInfo, cause: " + error.getMessage());
        if (callback != null) {
          callback.internalDone(null, new AVIMException(content, error));
        }
      }
    }, AVQuery.CachePolicy.NETWORK_ONLY, 86400000);
  }

  private List processResults(String content) throws Exception {
    if (AVUtils.isBlankContent(content)) {
      return Collections.emptyList();
    }
    AVResponse resp = new AVResponse();
    resp = JSON.parseObject(content, resp.getClass());

    List result = new LinkedList();
    for (Map item : resp.results) {
      if (item != null && !item.isEmpty()) {
        AVIMConversationMemberInfo object = AVIMConversationMemberInfo.createInstance(item);
        result.add(object);
      }
    }
    return result;
  }


  /**
   * 当前client的状态
   */
  public enum AVIMClientStatus {
    /**
     * 当前client尚未open,或者已经close
     */
    AVIMClientStatusNone(110),
    /**
     * 当前client已经打开,连接正常
     */
    AVIMClientStatusOpened(111),
    /**
     * 当前client由于网络因素导致的连接中断
     */
    AVIMClientStatusPaused(120);

    int code;

    AVIMClientStatus(int code) {
      this.code = code;
    }

    public int getCode() {
      return code;
    }

    static AVIMClientStatus getClientStatus(int code) {
      switch (code) {
        case 110:
          return AVIMClientStatusNone;
        case 111:
          return AVIMClientStatusOpened;
        case 120:
          return AVIMClientStatusPaused;
        default:
          return null;
      }
    };
  }

  public void getClientStatus(final AVIMClientStatusCallback callback) {
    BroadcastReceiver receiver = null;
    if (callback != null) {
      receiver = new AVIMBaseBroadcastReceiver(callback) {
        @Override
        public void execute(Intent intent, Throwable error) {
          AVIMClientStatus status = null;
          if (intent.getExtras() != null
              && intent.getExtras().containsKey(Conversation.callbackClientStatus)) {
            status =
                AVIMClientStatus.getClientStatus(intent.getExtras().getInt(
                    Conversation.callbackClientStatus));
          }
          callback.internalDone(status, AVIMException.wrapperAVException(error));
        }
      };
    }
    boolean ret = this.sendClientCMDToPushService(null, receiver, AVIMOperation.CLIENT_STATUS);
    if (!ret && null != callback) {
      callback.internalDone(AVIMClientStatus.AVIMClientStatusNone,
          new AVException(AVException.OPERATION_FORBIDDEN, "could't start service in background."));
    }
  }

  protected boolean sendClientCommand(AVIMClientParcel parcel, BroadcastReceiver receiver, AVIMOperation operation) {
    int requestId = AVUtils.getNextIMRequestId();

    if (receiver != null) {
      LocalBroadcastManager.getInstance(AVOSCloud.applicationContext).registerReceiver(receiver,
        new IntentFilter(operation.getOperation() + requestId));
    }
    Intent i = new Intent(AVOSCloud.applicationContext, PushService.class);
    i.setAction(Conversation.AV_CONVERSATION_INTENT_ACTION);
    i.putExtra(Conversation.INTENT_KEY_CLIENT_PARCEL, parcel);
    i.putExtra(Conversation.INTENT_KEY_CLIENT, clientId);
    i.putExtra(Conversation.INTENT_KEY_REQUESTID, requestId);
    i.putExtra(Conversation.INTENT_KEY_OPERATION, operation.getCode());
    try {
      AVOSCloud.applicationContext.startService(IntentUtil.setupIntentFlags(i));
    } catch (Exception ex) {
      LogUtil.avlog.e("failed to startService. cause: " + ex.getMessage());
      return false;
    }
    return true;
  }

  protected boolean sendClientCMDToPushService(String dataAsString, BroadcastReceiver receiver,
      AVIMOperation operation) {

    int requestId = AVUtils.getNextIMRequestId();

    if (receiver != null) {
      LocalBroadcastManager.getInstance(AVOSCloud.applicationContext).registerReceiver(receiver,
          new IntentFilter(operation.getOperation() + requestId));
    }
    Intent i = new Intent(AVOSCloud.applicationContext, PushService.class);
    i.setAction(Conversation.AV_CONVERSATION_INTENT_ACTION);
    if (!AVUtils.isBlankString(dataAsString)) {
      i.putExtra(Conversation.INTENT_KEY_DATA, dataAsString);
    }

    i.putExtra(Conversation.INTENT_KEY_CLIENT, clientId);
    i.putExtra(Conversation.INTENT_KEY_REQUESTID, requestId);
    i.putExtra(Conversation.INTENT_KEY_OPERATION, operation.getCode());
    try {
      AVOSCloud.applicationContext.startService(IntentUtil.setupIntentFlags(i));
    } catch (Exception ex) {
      LogUtil.avlog.e("failed to startService. cause: " + ex.getMessage());
      return false;
    }
    return true;
  }

  static AVException validateNonEmptyConversationMembers(List members) {
    if (members == null || members.isEmpty()) {
      return new AVException(AVException.UNKNOWN,
          "Conversation can't be created with empty members");
    }
    try {
      AVUtils.ensureElementsNotNull(members, AVSession.ERROR_INVALID_SESSION_ID);
    } catch (Exception e) {
      return new AVException(e);
    }
    return null;
  }

  /**
   * 同步 sqlite 中数据到 conversationCache,只需要同步一次就可以
   */
  private void syncConversationCache() {
    try {
      List cachedConversations = storage.getAllCachedConversations();
      for (AVIMConversation conversation : cachedConversations) {
        conversationCache.put(conversation.getConversationId(), conversation);
      }
    } catch (Exception ex) {
      LogUtil.avlog.d("failed to initialize cached conversations. cause:" + ex.getMessage());
    }
    isConversationSync = true;
  }

  protected void removeConversationCache(AVIMConversation conversation) {
    conversationCache.remove(conversation.getConversationId());
  }

  AVIMConversation mergeConversationCache(AVIMConversation allNewConversation, boolean forceReplace, JSONObject deltaObject) {
    if (null == allNewConversation || StringUtils.isBlankString(allNewConversation.getConversationId())) {
      return null;
    }
    String convId = allNewConversation.getConversationId();
    if (forceReplace) {
      this.conversationCache.put(convId, allNewConversation);
      return allNewConversation;
    } else {
      AVIMConversation origin = this.conversationCache.get(convId);
      if (null == origin) {
        this.conversationCache.put(convId, allNewConversation);
        origin = allNewConversation;
      } else {
        // update cache object again.
        origin = AVIMConversation.updateConversation(origin, deltaObject);
      }
      return origin;
    }
  }

  @Override
  public boolean equals(Object object) {
    if (object == null) {
      return false;
    }
    AVIMClient anotherClient = (AVIMClient) object;
    if (clientId == null) {
      return anotherClient.clientId == null;
    }
    return this.clientId.equals(anotherClient.clientId);
  }

  static boolean messageQueryCacheEnabled = true;

  /**
   * 控制是否打开历史消息查询的本地缓存功能
   * 
   * @param enable
   */
  public static void setMessageQueryCacheEnable(boolean enable) {
    messageQueryCacheEnabled = enable;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy