package com.netease.nim.demo.rts.activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.netease.nim.demo.R;
import com.netease.nim.demo.rts.ActionTypeEnum;
import com.netease.nim.demo.rts.doodle.DoodleView;
import com.netease.nim.demo.rts.doodle.SupportActionType;
import com.netease.nim.demo.rts.doodle.TransactionCenter;
import com.netease.nim.demo.rts.doodle.action.MyPath;
import com.netease.nim.demo.session.extension.RTSAttachment;
import com.netease.nim.uikit.cache.NimUserInfoCache;
import com.netease.nim.uikit.common.activity.UI;
import com.netease.nim.uikit.common.ui.dialog.EasyAlertDialog;
import com.netease.nim.uikit.common.ui.dialog.EasyAlertDialogHelper;
import com.netease.nim.uikit.common.ui.imageview.HeadImageView;
import com.netease.nim.uikit.common.util.sys.ScreenUtil;
import com.netease.nim.uikit.model.ToolBarOptions;
import com.netease.nim.uikit.session.helper.MessageListPanelHelper;
import com.netease.nimlib.sdk.NIMClient;
import com.netease.nimlib.sdk.Observer;
import com.netease.nimlib.sdk.StatusCode;
import com.netease.nimlib.sdk.auth.AuthServiceObserver;
import com.netease.nimlib.sdk.auth.ClientType;
import com.netease.nimlib.sdk.msg.MessageBuilder;
import com.netease.nimlib.sdk.msg.MsgService;
import com.netease.nimlib.sdk.msg.constant.MsgDirectionEnum;
import com.netease.nimlib.sdk.msg.constant.MsgStatusEnum;
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.msg.model.IMMessage;
import com.netease.nimlib.sdk.rts.RTSCallback;
import com.netease.nimlib.sdk.rts.RTSChannelStateObserver;
import com.netease.nimlib.sdk.rts.RTSManager;
import com.netease.nimlib.sdk.rts.constant.RTSEventType;
import com.netease.nimlib.sdk.rts.constant.RTSTimeOutEvent;
import com.netease.nimlib.sdk.rts.constant.RTSTunnelType;
import com.netease.nimlib.sdk.rts.model.RTSCalleeAckEvent;
import com.netease.nimlib.sdk.rts.model.RTSCommonEvent;
import com.netease.nimlib.sdk.rts.model.RTSControlEvent;
import com.netease.nimlib.sdk.rts.model.RTSData;
import com.netease.nimlib.sdk.rts.model.RTSNotifyOption;
import com.netease.nimlib.sdk.rts.model.RTSOnlineAckEvent;
import com.netease.nimlib.sdk.rts.model.RTSOptions;
import com.netease.nimlib.sdk.rts.model.RTSTunData;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* 发起/接受会话界面
* <p/>
* Created by huangjun on 2015/7/27.
*/
public class RTSActivity extends UI implements View.OnClickListener {
public static final int FROM_BROADCAST_RECEIVER = 0; // 来自广播
public static final int FROM_INTERNAL = 1; // 来自发起方
private static final String KEY_RTS_DATA = "KEY_RTS_DATA";
private static final String KEY_INCOMING = "KEY_INCOMING";
private static final String KEY_SOURCE = "KEY_SOURCE";
private static final String KEY_UID = "KEY_UID";
// data
private boolean isIncoming = false;
private String account; // 对方帐号
private String sessionId; // 会话的唯一标识
private RTSData sessionInfo; // 本次会话的信息
private boolean audioOpen = false; // 语音默认
private boolean finishFlag = false; // 结束标记,避免多次回调onFinish
private static boolean needFinish = true; // Activity销毁后,从最近任务列表恢复,则finish
private static boolean isBusy = false;
// 发起会话布局
private View startSessionLayout;
private TextView sessionStepText;
private HeadImageView headImage;
private TextView nameText;
private View calleeAckLayout;
private Button acceptBtn;
private Button rejectBtn;
private Button endSessionBtn;
private Button audioSwitchBtn;
// 白板布局
private View sessionLayout;
private DoodleView doodleView;
private Button backBtn;
private Button clearBtn;
public static void incomingSession(Context context, RTSData data, int source) {
if (isBusy) {
RTSManager.getInstance().close(data.getLocalSessionId(), null);
Toast.makeText(context, "close session", Toast.LENGTH_SHORT).show();
return;
}
needFinish = false;
Intent intent = new Intent();
intent.setClass(context, RTSActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(KEY_RTS_DATA, data);
intent.putExtra(KEY_INCOMING, true);
intent.putExtra(KEY_SOURCE, source);
context.startActivity(intent);
}
public static void startSession(Context context, String account, int source) {
needFinish = false;
Intent intent = new Intent();
intent.setClass(context, RTSActivity.class);
intent.putExtra(KEY_UID, account);
intent.putExtra(KEY_INCOMING, false);
intent.putExtra(KEY_SOURCE, source);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (needFinish) {
finish();
return;
}
isBusy = true;
setContentView(R.layout.rts_activity);
ToolBarOptions options = new ToolBarOptions();
options.isNeedNavigate = false;
setToolBar(R.id.toolbar, options);
isIncoming = getIntent().getBooleanExtra(KEY_INCOMING, false);
findViews();
initActionBarButton();
if (isIncoming) {
incoming();
registerInComingObserver(true);
} else {
outgoing();
registerOutgoingObserver(true);
}
initAudioSwitch();
registerCommonObserver(true);
//放到所有UI的基类里面注册,所有的UI实现onKickOut接口
NIMClient.getService(AuthServiceObserver.class).observeOnlineStatus(userStatusObserver, true);
}
private void initActionBarButton() {
TextView closeSessionBtn = findView(R.id.action_bar_right_clickable_textview);
closeSessionBtn.setText(R.string.close);
closeSessionBtn.setBackgroundResource(R.drawable.nim_message_button_bottom_send_selector);
closeSessionBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EasyAlertDialogHelper.OnDialogActionListener listener = new EasyAlertDialogHelper.OnDialogActionListener() {
@Override
public void doCancelAction() {
}
@Override
public void doOkAction() {
endSession(); // 挂断
}
};
final EasyAlertDialog dialog = EasyAlertDialogHelper.createOkCancelDiolag(RTSActivity.this,
getString(R.string.end_session_tip_head),
getString(R.string.end_session_tip_content),
getString(R.string.ok), getString(R.string.cancel), true, listener);
dialog.show();
}
});
}
@Override
public void onBackPressed() {
}
@Override
protected boolean displayHomeAsUpEnabled() {
return false;
}
@Override
protected void onPostResume() {
super.onPostResume();
// 这里需要重绘
doodleView.onResume();
}
@Override
protected void onDestroy() {
if (doodleView != null) {
doodleView.end();
}
super.onDestroy();
NIMClient.getService(AuthServiceObserver.class).observeOnlineStatus(userStatusObserver, false);
registerInComingObserver(false);
registerOutgoingObserver(false);
registerCommonObserver(false);
needFinish = true;
isBusy = false;
}
Observer<StatusCode> userStatusObserver = new Observer<StatusCode>() {
@Override
public void onEvent(StatusCode code) {
if (code.wontAutoLogin()) {
finish();
}
}
};
private void findViews() {
startSessionLayout = findViewById(R.id.start_session_layout);
sessionLayout = findViewById(R.id.session_layout);
headImage = (HeadImageView) findViewById(R.id.head_image);
sessionStepText = (TextView) findViewById(R.id.session_step_text);
nameText = (TextView) findViewById(R.id.name);
calleeAckLayout = findViewById(R.id.callee_ack_layout);
acceptBtn = (Button) findViewById(R.id.accept);
rejectBtn = (Button) findViewById(R.id.reject);
endSessionBtn = (Button) findViewById(R.id.end_session);
doodleView = (DoodleView) findViewById(R.id.doodle_view);
backBtn = (Button) findViewById(R.id.doodle_back);
clearBtn = (Button) findViewById(R.id.doodle_clear);
audioSwitchBtn = (Button) findViewById(R.id.audio_switch);
acceptBtn.setOnClickListener(this);
rejectBtn.setOnClickListener(this);
endSessionBtn.setOnClickListener(this);
audioSwitchBtn.setOnClickListener(this);
backBtn.setOnClickListener(this);
clearBtn.setOnClickListener(this);
float screenWidth = ScreenUtil.screenWidth * 1.0f;
ViewGroup.LayoutParams params = doodleView.getLayoutParams();
params.width = ((int) (screenWidth / 100)) * 100; // 比屏幕小的100的整数
params.height = params.width; // 保证宽高比为1:1
doodleView.setLayoutParams(params);
}
private void incoming() {
sessionInfo = (RTSData) getIntent().getSerializableExtra(KEY_RTS_DATA);
account = sessionInfo.getAccount();
sessionId = sessionInfo.getLocalSessionId();
Toast.makeText(RTSActivity.this, "incoming session, extra=" + sessionInfo.getExtra(),
Toast.LENGTH_SHORT)
.show();
initIncomingSessionViews();
}
private void outgoing() {
account = getIntent().getStringExtra(KEY_UID);
initStartSessionViews();
startSession();
}
private void initStartSessionViews() {
initAccountInfoView();
sessionStepText.setText(R.string.start_session);
calleeAckLayout.setVisibility(View.GONE);
endSessionBtn.setVisibility(View.VISIBLE);
startSessionLayout.setVisibility(View.VISIBLE);
}
private void initIncomingSessionViews() {
initAccountInfoView();
sessionStepText.setText(R.string.receive_session);
calleeAckLayout.setVisibility(View.VISIBLE);
endSessionBtn.setVisibility(View.GONE);
startSessionLayout.setVisibility(View.VISIBLE);
}
private void initAccountInfoView() {
nameText.setText(NimUserInfoCache.getInstance().getUserDisplayName(account));
headImage.loadBuddyAvatar(account);
}
private void registerOutgoingObserver(boolean register) {
RTSManager.getInstance().observeCalleeAckNotification(sessionId, calleeAckEventObserver, register);
}
private void registerInComingObserver(boolean register) {
RTSManager.getInstance().observeOnlineAckNotification(sessionId, onlineAckObserver, register);
}
private void registerCommonObserver(boolean register) {
RTSManager.getInstance().observeChannelState(sessionId, channelStateObserver, register);
RTSManager.getInstance().observeHangUpNotification(sessionId, endSessionObserver, register);
RTSManager.getInstance().observeReceiveData(sessionId, receiveDataObserver, register);
RTSManager.getInstance().observeTimeoutNotification(sessionId, timeoutObserver, register);
RTSManager.getInstance().observeControlNotification(sessionId, controlObserver, register);
}
/**
* 主叫方监听被叫方的接受or拒绝会话的响应
*/
private Observer<RTSCalleeAckEvent> calleeAckEventObserver = new Observer<RTSCalleeAckEvent>() {
@Override
public void onEvent(RTSCalleeAckEvent rtsCalleeAckEvent) {
if (rtsCalleeAckEvent.getEvent() == RTSEventType.CALLEE_ACK_AGREE) {
// 判断SDK自动开启通道是否成功
if (!rtsCalleeAckEvent.isTunReady()) {
Toast.makeText(RTSActivity.this, "通道开启失败!请查看LOG", Toast.LENGTH_SHORT).show();
return;
}
acceptView(); // 进入会话界面
} else if (rtsCalleeAckEvent.getEvent() == RTSEventType.CALLEE_ACK_REJECT) {
Toast.makeText(RTSActivity.this, R.string.callee_reject, Toast.LENGTH_SHORT).show();
onFinish(false);
}
}
};
/**
* 监听对方挂断
*/
private Observer<RTSCommonEvent> endSessionObserver = new Observer<RTSCommonEvent>() {
@Override
public void onEvent(RTSCommonEvent rtsCommonEvent) {
Toast.makeText(RTSActivity.this, R.string.target_has_end_session, Toast.LENGTH_SHORT).show();
onFinish(false);
}
};
/**
* 监听收到对方发送的通道数据
*/
private Observer<RTSTunData> receiveDataObserver = new Observer<RTSTunData>() {
@Override
public void onEvent(RTSTunData rtsTunData) {
String data = "[parse bytes error]";
try {
data = new String(rtsTunData.getData(), 0, rtsTunData.getLength(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
TransactionCenter.getInstance().onReceive(sessionId, data);
}
};
/**
* 被叫方监听在线其他端的接听响应
*/
private Observer<RTSOnlineAckEvent> onlineAckObserver = new Observer<RTSOnlineAckEvent>() {
@Override
public void onEvent(RTSOnlineAckEvent rtsOnlineAckEvent) {
if (rtsOnlineAckEvent.getClientType() != ClientType.Android) {
String client = null;
switch (rtsOnlineAckEvent.getClientType()) {
case ClientType.Web:
client = "Web";
break;
case ClientType.Windows:
client = "Windows";
break;
default:
break;
}
if (client != null) {
String option = rtsOnlineAckEvent.getEvent() == RTSEventType.CALLEE_ONLINE_CLIENT_ACK_AGREE ?
"接受" : "拒绝";
Toast.makeText(RTSActivity.this, "白板演示已在" + client + "端被" + option, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(RTSActivity.this, "白板演示已在其他端处理", Toast.LENGTH_SHORT).show();
}
onFinish();
}
}
};
/**
* 监听控制消息
*/
private Observer<RTSControlEvent> controlObserver = new Observer<RTSControlEvent>() {
@Override
public void onEvent(RTSControlEvent rtsControlEvent) {
Toast.makeText(RTSActivity.this, rtsControlEvent.getCommandInfo(), Toast.LENGTH_SHORT).show();
}
};
/**
* 监听当前会话的状态
*/
private RTSChannelStateObserver channelStateObserver = new RTSChannelStateObserver() {
@Override
public void onConnectResult(String sessionId, RTSTunnelType tunType, long channelId, int code, String file) {
Toast.makeText(RTSActivity.this, "onConnectResult, tunType=" + tunType.toString() +
", channelId=" + channelId +
", code=" + code + ", file=" + file, Toast.LENGTH_SHORT).show();
}
@Override
public void onChannelEstablished(String sessionId, RTSTunnelType tunType) {
Toast.makeText(RTSActivity.this, "onCallEstablished,tunType=" + tunType.toString(), Toast
.LENGTH_SHORT).show();
if (tunType == RTSTunnelType.AUDIO) {
RTSManager.getInstance().setSpeaker(sessionId, true); // 默认开启扬声器
}
}
@Override
public void onUserJoin(String sessionId, RTSTunnelType tunType, String account) {
}
@Override
public void onUserLeave(String sessionId, RTSTunnelType tunType, String account, int event) {
}
@Override
public void onDisconnectServer(String sessionId, RTSTunnelType tunType) {
Toast.makeText(RTSActivity.this, "onDisconnectServer, tunType=" + tunType.toString(), Toast
.LENGTH_SHORT).show();
if (tunType == RTSTunnelType.DATA) {
// 如果数据通道断了,那么关闭会话
Toast.makeText(RTSActivity.this, "TCP通道断开,自动结束会话", Toast.LENGTH_SHORT).show();
endSession();
} else if (tunType == RTSTunnelType.AUDIO) {
// 如果音频通道断了,那么UI变换
if (audioOpen) {
audioSwitch();
}
}
}
@Override
public void onError(String sessionId, RTSTunnelType tunType, int code) {
Toast.makeText(RTSActivity.this, "onError, tunType=" + tunType.toString() + ", error=" + code,
Toast.LENGTH_LONG).show();
endSession();
}
@Override
public void onNetworkStatusChange(String sessionId, RTSTunnelType tunType, int value) {
// 网络信号强弱
}
};
private Observer<RTSTimeOutEvent> timeoutObserver = new Observer<RTSTimeOutEvent>() {
@Override
public void onEvent(RTSTimeOutEvent rtsTimeOutEvent) {
Toast.makeText(RTSActivity.this,
(rtsTimeOutEvent == RTSTimeOutEvent.OUTGOING_TIMEOUT) ? getString(R.string.callee_ack_timeout) :
"超时未处理,自动结束", Toast.LENGTH_SHORT).show();
onFinish();
}
};
private void startSession() {
List<RTSTunnelType> types = new ArrayList<>(1);
types.add(RTSTunnelType.AUDIO);
types.add(RTSTunnelType.DATA);
String pushContent = account + "发起一个会话";
String extra = "extra_data";
RTSOptions options = new RTSOptions().setRecordAudioTun(false)
.setRecordDataTun(true);
RTSNotifyOption notifyOption = new RTSNotifyOption();
notifyOption.apnsContent = pushContent;
notifyOption.extendMessage = extra;
sessionId = RTSManager.getInstance().start(account, types, options, notifyOption, new RTSCallback<RTSData>() {
@Override
public void onSuccess(RTSData rtsData) {
RTSAttachment attachment = new RTSAttachment((byte) 0);
IMMessage msg = MessageBuilder.createCustomMessage(account, SessionTypeEnum.P2P, attachment.getContent(), attachment);
MessageListPanelHelper.getInstance().notifyAddMessage(msg); // 界面上add一条
NIMClient.getService(MsgService.class).sendMessage(msg, false); // 发送给对方
}
@Override
public void onFailed(int code) {
if (code == 11001) {
Toast.makeText(RTSActivity.this, "无可送达的被叫方", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(RTSActivity.this, "发起会话失败,code=" + code, Toast.LENGTH_SHORT).show();
}
onFinish();
}
@Override
public void onException(Throwable exception) {
Toast.makeText(RTSActivity.this, "发起会话异常,e=" + exception.toString(), Toast.LENGTH_SHORT).show();
onFinish();
}
});
if (sessionId == null) {
Toast.makeText(RTSActivity.this, "发起会话失败!", Toast.LENGTH_SHORT).show();
onFinish();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.accept:
acceptSession();
break;
case R.id.reject:
endSession();
break;
case R.id.end_session:
endSession();
break;
case R.id.doodle_back:
doodleBack();
break;
case R.id.doodle_clear:
clear();
break;
case R.id.audio_switch:
audioSwitch();
break;
default:
break;
}
}
private void acceptSession() {
RTSOptions options = new RTSOptions().setRecordAudioTun(false).setRecordDataTun(true);
RTSManager.getInstance().accept(sessionId, options, new RTSCallback<Boolean>() {
@Override
public void onSuccess(Boolean success) {
// 判断开启通道是否成功
if (success) {
acceptView();
} else {
Toast.makeText(RTSActivity.this, "通道开启失败!请查看LOG", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailed(int code) {
if (code == -1) {
Toast.makeText(RTSActivity.this, "接受会话失败,音频通道同时只能有一个会话开启", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(RTSActivity.this, "接受会话失败, code=" + code, Toast.LENGTH_SHORT).show();
}
onFinish();
}
@Override
public void onException(Throwable exception) {
Toast.makeText(RTSActivity.this, "接受会话异常, e=" + exception.toString(), Toast.LENGTH_SHORT).show();
onFinish();
}
});
}
private void acceptView() {
startSessionLayout.setVisibility(View.GONE);
sessionLayout.setVisibility(View.VISIBLE);
initDoodleView();
}
private void endSession() {
RTSManager.getInstance().close(sessionId, new RTSCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
}
@Override
public void onFailed(int code) {
Toast.makeText(RTSActivity.this, "挂断请求错误,code:" + code, Toast.LENGTH_SHORT).show();
}
@Override
public void onException(Throwable exception) {
}
});
onFinish();
}
private void onFinish() {
onFinish(true);
}
private void onFinish(boolean selfFinish) {
if (finishFlag) {
return;
}
finishFlag = true;
RTSAttachment attachment = new RTSAttachment((byte) 1);
IMMessage msg = MessageBuilder.createCustomMessage(account, SessionTypeEnum.P2P, attachment.getContent(), attachment);
if (!selfFinish) {
// 被结束会话,在这里模拟一条接收的消息
msg.setFromAccount(account);
msg.setDirect(MsgDirectionEnum.In);
}
msg.setStatus(MsgStatusEnum.success);
NIMClient.getService(MsgService.class).saveMessageToLocal(msg, true);
finish();
}
/**
* ***************************** 画板 ***********************************
*/
private void initDoodleView() {
// add support ActionType
SupportActionType.getInstance().addSupportActionType(ActionTypeEnum.Path.getValue(), MyPath.class);
doodleView.init(sessionId, account, DoodleView.Mode.BOTH, Color.WHITE, this);
doodleView.setPaintSize(10);
doodleView.setPaintType(ActionTypeEnum.Path.getValue());
// adjust paint offset
new Handler(getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
Rect frame = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
Log.i("Doodle", "statusBarHeight =" + statusBarHeight);
int marginTop = doodleView.getTop();
Log.i("Doodle", "doodleView marginTop =" + marginTop);
int marginLeft = doodleView.getLeft();
Log.i("Doodle", "doodleView marginLeft =" + marginLeft);
float offsetX = marginLeft;
float offsetY = statusBarHeight + marginTop;
doodleView.setPaintOffset(offsetX, offsetY);
Log.i("Doodle", "client1 offsetX = " + offsetX + ", offsetY = " + offsetY);
}
}, 50);
}
/**
* 撤销一步
*/
private void doodleBack() {
doodleView.paintBack();
}
/**
* 清屏
*/
private void clear() {
doodleView.clear();
}
/**
* 语音开关
*/
private void audioSwitch() {
audioOpen = !audioOpen;
RTSManager.getInstance().setMute(sessionId, !audioOpen);
audioSwitchBtn.setBackgroundResource(audioOpen ? R.drawable.icon_audio_open : R.drawable.icon_audio_close);
// 通过控制协议通知对方
String content = "对方静音" + (audioOpen ? "关闭" : "开启");
RTSManager.getInstance().sendControlCommand(sessionId, content, new RTSCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
String tip = "静音" + (audioOpen ? "关闭" : "开启");
Toast.makeText(RTSActivity.this, tip, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed(int code) {
Toast.makeText(RTSActivity.this, "控制协议发送失败, code =" + code, Toast.LENGTH_SHORT).show();
}
@Override
public void onException(Throwable exception) {
Toast.makeText(RTSActivity.this, "控制协议发送异常, e=" + exception.toString(), Toast.LENGTH_SHORT).show();
}
});
}
/**
* 初始化语音开关(默认关闭)
*/
private void initAudioSwitch() {
RTSManager.getInstance().setMute(sessionId, true);
audioSwitchBtn.setBackgroundResource(R.drawable.icon_audio_close);
}
}