diff --git a/OneToOne/build.gradle b/OneToOne/build.gradle index a2ce2f631..cae71a630 100644 --- a/OneToOne/build.gradle +++ b/OneToOne/build.gradle @@ -174,6 +174,7 @@ dependencies { api project(path:':TabLayout') api project(path:':ViewPager2Delegate') + api project(path:':callkit')//// 音视频通话能力 UI 组件 //implementation 'com.github.angcyo.DslTablayout:TabLayout:3.5.5' //可选 //implementation 'com.github.angcyo.DslTablayout:ViewPager2Delegate:3.5.5' @@ -182,7 +183,7 @@ dependencies { implementation 'com.google.android.exoplayer:exoplayer:2.18.5' implementation 'com.google.android.exoplayer:exoplayer-core:2.18.5@aar' - implementation 'cn.rongcloud.sdk:call_kit:5.5.0' // 音视频通话能力 UI 组件 + // implementation 'cn.rongcloud.sdk:call_kit:5.5.0' // 音视频通话能力 UI 组件 implementation 'com.github.luqiming666:SwipeRecyclerView:1.4.8'//支持侧滑删除 implementation 'com.google.android.material:material:1.6.1' implementation 'com.blankj:utilcode:1.30.0'//獲取uuid diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activity/DiamondExchangeActivity.java b/OneToOne/src/main/java/com/shayu/onetoone/activity/DiamondExchangeActivity.java index 95c58cd2a..0eb8f4ba9 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/activity/DiamondExchangeActivity.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/activity/DiamondExchangeActivity.java @@ -33,7 +33,7 @@ import java.util.List; public class DiamondExchangeActivity extends AbsOTOActivity { private RecyclerView diamondExchangeList; private DiamondExchangeAdapter exchangeAdapter; - private TextView title, totalConvertibility; + private TextView title, totalConvertibility,totalConvertibilityDes; private String type = "yuanbao"; private EditText diamondExchangeInput; @@ -63,6 +63,7 @@ public class DiamondExchangeActivity extends AbsOTOActivity { title = findViewById(R.id.title); diamondExchangeInput = findViewById(R.id.diamond_exchange_input); totalConvertibility = findViewById(R.id.total_convertibility); + totalConvertibilityDes = findViewById(R.id.total_convertibility_des); diamondExchangeList.addItemDecoration(new ItemDecoration(mContext, Color.parseColor("#ffffff"), 10, 2)); diamondExchangeList.setLayoutManager(new GridLayoutManager(mContext, 3)); exchangeAdapter = new DiamondExchangeAdapter(); @@ -114,15 +115,15 @@ public class DiamondExchangeActivity extends AbsOTOActivity { if (data.size() > 0) { if ((data.size() - 1) > index && index > 0) { data.get(index).setSelect(true); - totalConvertibility.setText(String.valueOf(data.get(0).getNum())); + totalConvertibility.setText(String.valueOf(data.get(0).getSum())); number = data.get(index).getNum(); } else { if (TextUtils.isEmpty(diamondExchangeInput.getText().toString())) { data.get(0).setSelect(true); - totalConvertibility.setText(String.valueOf(data.get(0).getNum())); + totalConvertibility.setText(String.valueOf(data.get(0).getSum())); number = data.get(0).getNum(); } else { - totalConvertibility.setText(String.valueOf(data.get(0).getNum())); + totalConvertibility.setText(String.valueOf(data.get(0).getSum())); for (int i = 0; i < data.size(); i++) { data.get(i).setSelect(false); } @@ -188,7 +189,7 @@ public class DiamondExchangeActivity extends AbsOTOActivity { public void onSuccess(List data) { if (data.size() > 0) { data.get(0).setSelect(true); - totalConvertibility.setText(String.valueOf(data.get(0).getNum())); + totalConvertibility.setText(String.valueOf(data.get(0).getSum())); number = data.get(0).getNum(); exchangeAdapter.addData(data); } diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activity/ExchangeRecordActivity.java b/OneToOne/src/main/java/com/shayu/onetoone/activity/ExchangeRecordActivity.java index b9d2805db..c1d301509 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/activity/ExchangeRecordActivity.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/activity/ExchangeRecordActivity.java @@ -83,7 +83,7 @@ public class ExchangeRecordActivity extends AbsOTOActivity { private void initData() { OTONetManager.getInstance(mContext). - getExchangeRecord(type, "3", "2", page, new HttpCallback>() { + getExchangeRecord("10,11", "3", "2", page, new HttpCallback>() { @Override public void onSuccess(List data) { if (page != 1 && data.isEmpty()) { diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/ChatMessageFragment.java b/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/ChatMessageFragment.java index 91d659f45..4f85cc12c 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/ChatMessageFragment.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/ChatMessageFragment.java @@ -552,7 +552,13 @@ public class ChatMessageFragment extends AbsConversationFragment { ViewClicksAntiShake.clicksAntiShake(home, new ViewClicksAntiShake.ViewClicksCallBack() { @Override public void onViewClicks() { - UserManager.toHomePage(targetId); + UserManager.toHomePage(targetId,true); + } + }); + ViewClicksAntiShake.clicksAntiShake(avatar, new ViewClicksAntiShake.ViewClicksCallBack() { + @Override + public void onViewClicks() { + UserManager.toHomePage(targetId,true); } }); follow.setOnClickListener(v -> { diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/MessageInteractiveFragment.java b/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/MessageInteractiveFragment.java index 83d84e74e..7e510512b 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/MessageInteractiveFragment.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/MessageInteractiveFragment.java @@ -43,7 +43,7 @@ public class MessageInteractiveFragment extends AbsConversationFragment { @Override public void main() { setTitle(noticeBean.getTitle()); - mList.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, true)); + mList.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); initData(); } diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/MsgMessageFragment.java b/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/MsgMessageFragment.java index 3a97e658c..d61d2acc6 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/MsgMessageFragment.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/activity/fragments/message/MsgMessageFragment.java @@ -375,8 +375,8 @@ public class MsgMessageFragment extends BaseFragment implements BaseAdapter.OnIt this.mRefreshLayout.setOnRefreshListener(new OnRefreshListener() { public void onRefresh(@NonNull RefreshLayout refreshLayout) { initSystemNotice(); - MsgMessageFragment.this.onConversationListRefresh(refreshLayout); updateUserInfo(); + MsgMessageFragment.this.onConversationListRefresh(refreshLayout); } }); this.mRefreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() { diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activity/message/CallAudioActivity.java b/OneToOne/src/main/java/com/shayu/onetoone/activity/message/CallAudioActivity.java index 5c75fbd41..cd490b71e 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/activity/message/CallAudioActivity.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/activity/message/CallAudioActivity.java @@ -2,6 +2,7 @@ package com.shayu.onetoone.activity.message; import android.Manifest; import android.app.Dialog; +import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; @@ -42,6 +43,7 @@ import com.shayu.onetoone.manager.OTONetManager; import com.shayu.onetoone.manager.RouteManager; import com.shayu.onetoone.utils.ConversationUtils; import com.shayu.onetoone.utils.HtmlUrlUtils; +import com.yunbao.common.activity.MyWalletActivity; import com.yunbao.common.glide.ImgLoader; import com.yunbao.common.http.base.HttpCallback; import com.yunbao.common.interfaces.OnItemClickListener; @@ -345,8 +347,8 @@ public class CallAudioActivity extends AbsOTOActivity implements View.OnClickLis finish(); if (toPay) { Log.e(TAG, "调起支付界面"); - // RouteManager.forwardWebViewActivity(null, "https://www.baidu.com"); - RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext,false,HtmlUrlUtils.URL_PAY_COIN)); + // RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext,false,HtmlUrlUtils.URL_PAY_COIN)); + mContext.startActivity(new Intent(mContext, MyWalletActivity.class).putExtra("p", 1)); } if (toChatView) { ConversationUtils.startConversation(mContext, targetId); diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activity/message/CallVideoActivity.java b/OneToOne/src/main/java/com/shayu/onetoone/activity/message/CallVideoActivity.java index 67ab90b9f..eeafaacfb 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/activity/message/CallVideoActivity.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/activity/message/CallVideoActivity.java @@ -3,6 +3,7 @@ package com.shayu.onetoone.activity.message; import android.Manifest; import android.annotation.SuppressLint; import android.app.Dialog; +import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; @@ -44,6 +45,7 @@ import com.shayu.onetoone.manager.OTONetManager; import com.shayu.onetoone.manager.RouteManager; import com.shayu.onetoone.utils.ConversationUtils; import com.shayu.onetoone.utils.HtmlUrlUtils; +import com.yunbao.common.activity.MyWalletActivity; import com.yunbao.common.glide.ImgLoader; import com.yunbao.common.http.base.HttpCallback; import com.yunbao.common.interfaces.OnItemClickListener; @@ -455,7 +457,8 @@ public class CallVideoActivity extends AbsOTOActivity { } }).show(); if (toPay) { - RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext, false, HtmlUrlUtils.URL_PAY_COIN)); + // RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext, false, HtmlUrlUtils.URL_PAY_COIN)); + mContext.startActivity(new Intent(mContext, MyWalletActivity.class).putExtra("p", 1)); } if (toChatView) { ConversationUtils.startConversation(mContext, targetId); diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activity/message/ChatActivity.java b/OneToOne/src/main/java/com/shayu/onetoone/activity/message/ChatActivity.java index 15627883f..7a6f3eaf5 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/activity/message/ChatActivity.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/activity/message/ChatActivity.java @@ -65,6 +65,11 @@ public class ChatActivity extends AbsOTOActivity { return R.layout.activity_msg_chat; } + @Override + protected boolean onCreate() { + return false; + } + @Override protected void main(Bundle savedInstanceState) { int type = getIntent().getIntExtra("model", 0); diff --git a/OneToOne/src/main/java/com/shayu/onetoone/adapter/MsgMessageRecyclerViewAdapter.java b/OneToOne/src/main/java/com/shayu/onetoone/adapter/MsgMessageRecyclerViewAdapter.java index 595678fa9..79aa714a9 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/adapter/MsgMessageRecyclerViewAdapter.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/adapter/MsgMessageRecyclerViewAdapter.java @@ -5,6 +5,7 @@ import android.text.SpannableString; import com.shayu.onetoone.R; import com.shayu.onetoone.bean.MessageChatAuthContent; import com.shayu.onetoone.bean.MessageChatGiftContent; +import com.shayu.onetoone.bean.MessageChatTipsContent; import com.yanzhenjie.recyclerview.SwipeRecyclerView; import com.yunbao.common.utils.WordUtil; @@ -75,6 +76,8 @@ public class MsgMessageRecyclerViewAdapter extends ConversationListAdapter { datum.mConversationContent = new SpannableString(WordUtil.getNewString(R.string.gift)); } else if (datum.mCore.getLatestMessage() instanceof MessageChatAuthContent) { datum.mConversationContent = new SpannableString(WordUtil.getNewString(R.string.authentication)); + }else if(datum.mCore.getLatestMessage() instanceof MessageChatTipsContent){ + datum.mConversationContent = new SpannableString(WordUtil.getNewString(R.string.system_tips)); } if (datum.mCore.isTop()) { top.add(datum); diff --git a/OneToOne/src/main/java/com/shayu/onetoone/bean/ExchangeModel.java b/OneToOne/src/main/java/com/shayu/onetoone/bean/ExchangeModel.java index a952a364c..2c760fbb0 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/bean/ExchangeModel.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/bean/ExchangeModel.java @@ -13,6 +13,9 @@ public class ExchangeModel extends BaseModel { private String title; @SerializedName("num") private String num; + @SerializedName("sum") + private String sum; + private boolean select = false; public boolean isSelect() { @@ -24,6 +27,14 @@ public class ExchangeModel extends BaseModel { return this; } + public String getSum() { + return sum; + } + + public void setSum(String sum) { + this.sum = sum; + } + public String getTop() { return top; } diff --git a/OneToOne/src/main/java/com/shayu/onetoone/dialog/GiftDialog.java b/OneToOne/src/main/java/com/shayu/onetoone/dialog/GiftDialog.java index b6cc3cecb..b0afcb7e2 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/dialog/GiftDialog.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/dialog/GiftDialog.java @@ -2,6 +2,7 @@ package com.shayu.onetoone.dialog; import android.app.Dialog; import android.content.Context; +import android.content.Intent; import android.widget.Button; import android.widget.TextView; @@ -28,6 +29,7 @@ import com.shayu.onetoone.view.MsgInputPanelForGift; import com.shayu.onetoone.widget.PagerConfig; import com.shayu.onetoone.widget.PagerGridLayoutManager; import com.shayu.onetoone.widget.PagerGridSnapHelper; +import com.yunbao.common.activity.MyWalletActivity; import com.yunbao.common.dialog.AbsDialogPopupWindow; import com.yunbao.common.http.base.HttpCallback; import com.yunbao.common.interfaces.OnItemClickListener; @@ -133,7 +135,8 @@ public class GiftDialog extends AbsDialogPopupWindow { ViewClicksAntiShake.clicksAntiShake(topUpBtn, new ViewClicksAntiShake.ViewClicksCallBack() { @Override public void onViewClicks() { - RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext, false, HtmlUrlUtils.URL_PAY_COIN)); + //RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext, false, HtmlUrlUtils.URL_PAY_COIN)); + mContext.startActivity(new Intent(mContext, MyWalletActivity.class).putExtra("p", 1)); } }); } diff --git a/OneToOne/src/main/java/com/shayu/onetoone/dialog/TipsDialog.java b/OneToOne/src/main/java/com/shayu/onetoone/dialog/TipsDialog.java index cc63672dc..6276024ce 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/dialog/TipsDialog.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/dialog/TipsDialog.java @@ -1,6 +1,7 @@ package com.shayu.onetoone.dialog; import android.content.Context; +import android.content.Intent; import android.view.View; import android.view.ViewGroup; import android.widget.Button; @@ -13,6 +14,7 @@ import com.shayu.onetoone.R; import com.shayu.onetoone.listener.OnDialogClickListener; import com.shayu.onetoone.manager.RouteManager; import com.shayu.onetoone.utils.HtmlUrlUtils; +import com.yunbao.common.activity.MyWalletActivity; import com.yunbao.common.dialog.AbsDialogCenterPopupWindow; import com.yunbao.common.utils.StringUtil; import com.yunbao.common.utils.WordUtil; @@ -113,7 +115,8 @@ public class TipsDialog extends AbsDialogCenterPopupWindow { if (!StringUtil.isEmpty(applyText)&&( applyText.equals(WordUtil.getNewString(R.string.money_apply)) || applyText.equals(WordUtil.getNewString(R.string.dialog_to_money_tip)))) { - RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext, false, HtmlUrlUtils.URL_PAY_COIN)); + // RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext, false, HtmlUrlUtils.URL_PAY_COIN)); + mContext.startActivity(new Intent(mContext, MyWalletActivity.class).putExtra("p", 1)); } if (onDialogClickListener != null) { onDialogClickListener.onApply(dialog); diff --git a/OneToOne/src/main/java/com/shayu/onetoone/provider/CustomConversationProvider.java b/OneToOne/src/main/java/com/shayu/onetoone/provider/CustomConversationProvider.java index 2159dfd8a..db1d5f959 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/provider/CustomConversationProvider.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/provider/CustomConversationProvider.java @@ -33,10 +33,10 @@ public class CustomConversationProvider extends BaseConversationProvider { try { if (json.getInteger("sex") == -1) { holder.getView(R.id.sex).setVisibility(View.GONE); - } else if (json.getInteger("sex") == 2) { - holder.setImageResource(R.id.sex, R.mipmap.ic_message_tab_woman); - } else { + } else if (json.getInteger("sex") == 1) { holder.setImageResource(R.id.sex, R.mipmap.ic_message_tab_man); + } else { + holder.setImageResource(R.id.sex, R.mipmap.ic_message_tab_woman); } } catch (Exception e) { e.printStackTrace(); diff --git a/OneToOne/src/main/java/com/shayu/onetoone/utils/UserManager.java b/OneToOne/src/main/java/com/shayu/onetoone/utils/UserManager.java index f19faef87..1cf3393fd 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/utils/UserManager.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/utils/UserManager.java @@ -120,6 +120,8 @@ public class UserManager { } public static void toHomePage(int userId) { + } + public static void toHomePage(String userId,boolean isGoto) { toHomePage(userId+""); } public static void toHomePage(String userId) { diff --git a/OneToOne/src/main/java/com/shayu/onetoone/view/MsgInputPanelForGift.java b/OneToOne/src/main/java/com/shayu/onetoone/view/MsgInputPanelForGift.java index 146671a52..14449893c 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/view/MsgInputPanelForGift.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/view/MsgInputPanelForGift.java @@ -1,6 +1,7 @@ package com.shayu.onetoone.view; import android.app.Dialog; +import android.content.Intent; import android.view.View; import android.widget.Button; import android.widget.TextView; @@ -27,6 +28,7 @@ import com.shayu.onetoone.utils.UserManager; import com.shayu.onetoone.widget.PagerConfig; import com.shayu.onetoone.widget.PagerGridLayoutManager; import com.shayu.onetoone.widget.PagerGridSnapHelper; +import com.yunbao.common.activity.MyWalletActivity; import com.yunbao.common.http.base.HttpCallback; import com.yunbao.common.interfaces.OnItemClickListener; import com.yunbao.common.manager.IMLoginManager; @@ -117,7 +119,8 @@ public class MsgInputPanelForGift extends AbsInputPanel { ViewClicksAntiShake.clicksAntiShake(topUpBtn, new ViewClicksAntiShake.ViewClicksCallBack() { @Override public void onViewClicks() { - RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext, false, HtmlUrlUtils.URL_PAY_COIN)); + //RouteManager.forwardWebViewActivity(null, HtmlUrlUtils.getPayUrl(mContext, false, HtmlUrlUtils.URL_PAY_COIN)); + mContext.startActivity(new Intent(mContext, MyWalletActivity.class).putExtra("p", 1)); } }); } diff --git a/OneToOne/src/main/res/layout/activity_diamond_exchange.xml b/OneToOne/src/main/res/layout/activity_diamond_exchange.xml index fc064d93f..8e4afbf71 100644 --- a/OneToOne/src/main/res/layout/activity_diamond_exchange.xml +++ b/OneToOne/src/main/res/layout/activity_diamond_exchange.xml @@ -84,6 +84,7 @@ android:textSize="22sp" /> + android:background="@color/rc_white_color" + android:layout_height="match_parent"> + android:visibility="gone" /> + android:visibility="gone" /> 前往充值 [禮物] [邀請認證] + [系統提示] 等待對方接受邀請… 連接成功 需要獲取您的權限 diff --git a/OneToOne/src/main/res/values-zh-rTW/strings.xml b/OneToOne/src/main/res/values-zh-rTW/strings.xml index 840783a81..8f071b808 100644 --- a/OneToOne/src/main/res/values-zh-rTW/strings.xml +++ b/OneToOne/src/main/res/values-zh-rTW/strings.xml @@ -97,6 +97,7 @@ 前往充值 [禮物] [邀請認證] + [系統提示] 等待對方接受邀請… 連接成功 需要獲取您的權限 diff --git a/OneToOne/src/main/res/values/strings.xml b/OneToOne/src/main/res/values/strings.xml index 9dda19ecb..24c226d3f 100644 --- a/OneToOne/src/main/res/values/strings.xml +++ b/OneToOne/src/main/res/values/strings.xml @@ -99,6 +99,7 @@ 前往充值 [禮物] [邀請認證] + [系統提示] 等待對方接受邀請… 連接成功 需要獲取您的權限 diff --git a/callkit/.gitignore b/callkit/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/callkit/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/callkit/README.md b/callkit/README.md new file mode 100644 index 000000000..9e305f4bc --- /dev/null +++ b/callkit/README.md @@ -0,0 +1,43 @@ +## callkit-android + +Open-source code of RongCloud VoIP Audio/Video UI. 融云音视频通话功能 UI 界面 SDK 开源代码。 + +## 适用场景 + +融云提供 CallKit 源码,是为方便开发者根据 App 风格对呼叫 UI 做个性化的修改,比如色调搭配,按钮位置等,都可以自由定制。 + +## 集成步骤 + +1. 先按照 Maven 导入或本地手动导入的方式,集成 CallLib、IMKit、IMLib 三个 CallKit 依赖库,并确保都是当时官网的最新版本,如下: + + ```groovy + dependencies { + implementation 'cn.rongcloud.sdk:call_lib:x.y.z' + implementation 'cn.rongcloud.sdk:im_kit:x.y.z' + implementation 'cn.rongcloud.sdk:im_lib:x.y.z' + } + ``` + + > CallKit 源码因为是开源的,融云不提供老版本的下载。用户配合 CallKit 所使用的 CallLib、IMKit、IMLib 版本应该也是官网此时的最新版本。 + +2. 进入工程目录,克隆 CallKit 源码: + + ```shell + cd + git clone https://github.com/rongcloud/callkit-android.git + ``` + +3. 在 `settings.gradle` 文件中,添加引用: + + ```groovy + include ':callkit-android' + ``` + +4. 在应用的 `build.gradle` 中,添加依赖: + + ```groovy + dependencies { + ... + implementation project(':callkit-android') + } + ``` \ No newline at end of file diff --git a/callkit/build.gradle b/callkit/build.gradle new file mode 100644 index 000000000..6a36469dd --- /dev/null +++ b/callkit/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 33 + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 31 + versionName "5.6.5" + } + + sourceSets { + main { + manifest.srcFile 'src/main/AndroidManifest.xml' + jniLibs.srcDirs = ['libs'] + } + } +} + +dependencies { + api fileTree(dir: 'libs', include: ['*.jar']) + api project(path: ':common') + + implementation 'androidx.media:media:1.2.1' + implementation 'com.github.bumptech.glide:glide:4.9.0' +} diff --git a/callkit/proguard-rules.pro b/callkit/proguard-rules.pro new file mode 100644 index 000000000..2b3c220dd --- /dev/null +++ b/callkit/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/jiangecho/apps/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/callkit/src/main/AndroidManifest.xml b/callkit/src/main/AndroidManifest.xml new file mode 100644 index 000000000..56c5e116c --- /dev/null +++ b/callkit/src/main/AndroidManifest.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/java/io/rong/callkit/AudioPlugin.java b/callkit/src/main/java/io/rong/callkit/AudioPlugin.java new file mode 100644 index 000000000..b45cea7fe --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/AudioPlugin.java @@ -0,0 +1,201 @@ +package io.rong.callkit; + +import static io.rong.callkit.BaseCallActivity.REQUEST_CODE_ADD_MEMBER; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import io.rong.callkit.util.CallKitUtils; +import io.rong.callkit.util.RongCallPermissionUtil; +import io.rong.callkit.util.permission.PermissionType; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import io.rong.common.RLog; +import io.rong.imkit.conversation.extension.RongExtension; +import io.rong.imkit.conversation.extension.component.plugin.IPluginModule; +import io.rong.imkit.conversation.extension.component.plugin.IPluginRequestPermissionResultCallback; +import io.rong.imlib.IRongCoreCallback; +import io.rong.imlib.IRongCoreEnum; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.discussion.base.RongDiscussionClient; +import io.rong.imlib.discussion.model.Discussion; +import io.rong.imlib.model.Conversation; +import java.util.ArrayList; + +/** Created by weiqinxiao on 16/8/16. */ +public class AudioPlugin implements IPluginModule, IPluginRequestPermissionResultCallback { + private static final String TAG = "AudioPlugin"; + private ArrayList allMembers; + private Context context; + private static final int REQEUST_CODE_RECORD_AUDIO_PERMISSION = 101; + + private Conversation.ConversationType conversationType; + private String targetId; + + @Override + public Drawable obtainDrawable(Context context) { + return context.getResources().getDrawable(R.drawable.rc_ic_phone_selector); + } + + @Override + public String obtainTitle(Context context) { + return context.getString(R.string.rc_voip_audio); + } + + @Override + public void onClick(Fragment currentFragment, RongExtension extension, int index) { + context = currentFragment.getActivity().getApplicationContext(); + conversationType = extension.getConversationType(); + targetId = extension.getTargetId(); + Log.i(TAG, "---- targetId==" + targetId); + + PermissionType[] audioCallPermissions = + RongCallPermissionUtil.getAudioCallPermissions(context); + String[] permissions = new String[audioCallPermissions.length]; + for (int i = 0; i < audioCallPermissions.length; i++) { + permissions[i] = audioCallPermissions[i].getPermissionName(); + } + + if (RongCallPermissionUtil.checkPermissions(currentFragment.getActivity(), permissions)) { + Log.i(TAG, "---- startAudioActivity ----"); + startAudioActivity(currentFragment, extension); + } else { + Log.i(TAG, "---- requestPermissionForPluginResult ----"); + extension.requestPermissionForPluginResult( + permissions, REQEUST_CODE_RECORD_AUDIO_PERMISSION, this); + } + } + + private void startAudioActivity(Fragment currentFragment, final RongExtension extension) { + if (context == null) { + return; + } + RongCallSession profile = RongCallClient.getInstance().getCallSession(); + if (profile != null && profile.getStartTime() > 0) { + Toast.makeText( + context, + profile.getMediaType() == RongCallCommon.CallMediaType.AUDIO + ? currentFragment.getString( + R.string.rc_voip_call_audio_start_fail) + : currentFragment.getString( + R.string.rc_voip_call_video_start_fail), + Toast.LENGTH_SHORT) + .show(); + return; + } + if (!CallKitUtils.isNetworkAvailable(context)) { + Toast.makeText( + context, + currentFragment.getString(R.string.rc_voip_call_network_error), + Toast.LENGTH_SHORT) + .show(); + return; + } + + if (conversationType.equals(Conversation.ConversationType.PRIVATE)) { + Intent intent = new Intent(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEAUDIO); + intent.putExtra("conversationType", conversationType.getName().toLowerCase()); + Log.i( + TAG, + "---- conversationType.getName().toLowerCase() =-" + + conversationType.getName().toLowerCase()); + intent.putExtra("targetId", targetId); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + Log.i(TAG, "---- callAction=" + RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Log.i(TAG, "getPackageName===" + context.getPackageName()); + intent.setPackage(context.getPackageName()); + context.startActivity(intent); + } else if (conversationType.equals(Conversation.ConversationType.DISCUSSION)) { + RongDiscussionClient.getInstance() + .getDiscussion( + targetId, + new IRongCoreCallback.ResultCallback() { + @Override + public void onSuccess(Discussion discussion) { + Intent intent = + new Intent(context, CallSelectMemberActivity.class); + allMembers = (ArrayList) discussion.getMemberIdList(); + intent.putStringArrayListExtra("allMembers", allMembers); + intent.putExtra( + "conversationType", conversationType.getValue()); + String myId = RongIMClient.getInstance().getCurrentUserId(); + ArrayList invited = new ArrayList<>(); + invited.add(myId); + intent.putStringArrayListExtra("invitedMembers", invited); + intent.putExtra( + "mediaType", + RongCallCommon.CallMediaType.AUDIO.getValue()); + extension.startActivityForPluginResult( + intent, 110, AudioPlugin.this); + } + + @Override + public void onError(IRongCoreEnum.CoreErrorCode e) { + RLog.d(TAG, "get discussion errorCode = " + e.getValue()); + } + }); + } else if (conversationType.equals(Conversation.ConversationType.GROUP)) { + Intent intent = new Intent(context, CallSelectMemberActivity.class); + String myId = RongIMClient.getInstance().getCurrentUserId(); + ArrayList invited = new ArrayList<>(); + invited.add(myId); + intent.putStringArrayListExtra("invitedMembers", invited); + intent.putExtra("conversationType", conversationType.getValue()); + intent.putExtra("groupId", targetId); + intent.putExtra("mediaType", RongCallCommon.CallMediaType.AUDIO.getValue()); + extension.startActivityForPluginResult(intent, 110, this); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK) { + return; + } + + if (requestCode == REQUEST_CODE_ADD_MEMBER) { + if (resultCode == Activity.RESULT_OK) { + if (data.getBooleanExtra("remote_hangup", false)) { + RLog.d(TAG, "Remote exit, end the call."); + return; + } + } + } + Intent intent = new Intent(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIAUDIO); + ArrayList userIds = data.getStringArrayListExtra("invited"); + ArrayList observers = data.getStringArrayListExtra("observers"); + userIds.add(RongIMClient.getInstance().getCurrentUserId()); + intent.putExtra("conversationType", conversationType.getName().toLowerCase()); + intent.putExtra("targetId", targetId); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.putStringArrayListExtra("invitedUsers", userIds); + intent.putStringArrayListExtra("observers", observers); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setPackage(context.getPackageName()); + context.startActivity(intent); + } + + @Override + public boolean onRequestPermissionResult( + Fragment fragment, + RongExtension extension, + int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults) { + Context context = fragment.getContext(); + if (RongCallPermissionUtil.checkPermissions(context, permissions)) { + startAudioActivity(fragment, extension); + } else { + RongCallPermissionUtil.showRequestPermissionFailedAlter( + context, permissions, grantResults); + } + return true; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/BaseCallActivity.java b/callkit/src/main/java/io/rong/callkit/BaseCallActivity.java new file mode 100644 index 000000000..ae881831f --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/BaseCallActivity.java @@ -0,0 +1,862 @@ +package io.rong.callkit; + +import static io.rong.callkit.CallFloatBoxView.showFB; +import static io.rong.callkit.util.CallKitUtils.isDial; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Handler; +import android.os.PowerManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; +import androidx.annotation.NonNull; +import cn.rongcloud.rtc.api.RCRTCAudioRouteManager; +import cn.rongcloud.rtc.api.callback.IRCRTCAudioRouteListener; +import cn.rongcloud.rtc.api.stream.RCRTCVideoStreamConfig.Builder; +import cn.rongcloud.rtc.audioroute.RCAudioRouteType; +import cn.rongcloud.rtc.base.RCRTCParamsType.RCRTCVideoFps; +import cn.rongcloud.rtc.base.RCRTCParamsType.RCRTCVideoResolution; +import cn.rongcloud.rtc.utils.FinLog; +import io.rong.callkit.util.BluetoothUtil; +import io.rong.callkit.util.CallKitUtils; +import io.rong.callkit.util.CallRingingUtil; +import io.rong.callkit.util.HeadsetInfo; +import io.rong.callkit.util.RingingMode; +import io.rong.callkit.util.RongCallPermissionUtil; +import io.rong.calllib.IRongCallListener; +import io.rong.calllib.PublishCallBack; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallCommon.CallMediaType; +import io.rong.calllib.RongCallSession; +import io.rong.common.RLog; +import io.rong.imkit.manager.AudioPlayManager; +import io.rong.imkit.manager.AudioRecordManager; +import io.rong.imkit.notification.NotificationUtil; +import io.rong.imkit.userinfo.RongUserInfoManager; +import io.rong.imkit.userinfo.model.GroupUserInfo; +import io.rong.imlib.model.Group; +import io.rong.imlib.model.UserInfo; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +/** Created by weiqinxiao on 16/3/9. */ +public class BaseCallActivity extends BaseNoActionBarActivity + implements IRongCallListener, + PickupDetector.PickupDetectListener, + RongUserInfoManager.UserDataObserver { + + private static final String TAG = "BaseCallActivity"; + private static final String MEDIAPLAYERTAG = "MEDIAPLAYERTAG"; + private static final long DELAY_TIME = 1000; + static final int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 100; + static final int REQUEST_CODE_ADD_MEMBER = 110; + public final int REQUEST_CODE_ADD_MEMBER_NONE = 120; + static final int VOIP_MAX_NORMAL_COUNT = 6; + + private long time = 0; + private Runnable updateTimeRunnable; + + private boolean shouldRestoreFloat; + // 是否是请求开启悬浮窗权限的过程中 + private boolean checkingOverlaysPermission; + private boolean notRemindRequestFloatWindowPermissionAgain = false; + protected Handler handler; + /** 表示是否正在挂断 */ + protected boolean isFinishing; + + protected PickupDetector pickupDetector; + protected PowerManager powerManager; + protected PowerManager.WakeLock wakeLock; + protected PowerManager.WakeLock screenLock; + + // static final String[] VIDEO_CALL_PERMISSIONS = { + // Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA + // }; + // static final String[] AUDIO_CALL_PERMISSIONS = {Manifest.permission.RECORD_AUDIO}; + + public static final int CALL_NOTIFICATION_ID = 4000; + boolean isMuteCamera = false; + + /** + * 融云 SDK 默认麦克风、摄像头流唯一标识,和 RongCallClient#publishCustomVideoStream(tag, PublishCallBack) 方法中 tag + * 用法一致; 用户发布自定义视频流唯一标示,不允许带下划线,不能为 “RongCloudRTC”; + * + * @see RongCallClient#publishCustomVideoStream(String, PublishCallBack) + */ + public static final String RONG_TAG_CALL = "RongCloudRTC"; + + RelativeLayout.LayoutParams mLargeLayoutParams = + new RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + public void setShouldShowFloat(boolean ssf) { + CallKitUtils.shouldShowFloat = ssf; + } + + public void showShortToast(String text) { + Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); + } + + public void postRunnableDelay(Runnable runnable) { + handler.postDelayed(runnable, DELAY_TIME); + } + + private HeadsetPlugReceiver headsetPlugReceiver = null; + private AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener; + + public static final String EXTRA_BUNDLE_KEY_MUTECAMERA = "muteCamera"; + public static final String EXTRA_BUNDLE_KEY_MUTEMIC = "muteMIC"; + public static final String EXTRA_BUNDLE_KEY_LOCALVIEWUSERID = "localViewUserId"; + public static final String EXTRA_BUNDLE_KEY_CALLACTION = "callAction"; + public static final String EXTRA_BUNDLE_KEY_MEDIATYPE = "mediaType"; + public static final String EXTRA_BUNDLE_KEY_USER_TOP_NAME = "rc_voip_user_top_name"; + public static final String EXTRA_BUNDLE_KEY_USER_TOP_NAME_TAG = "rc_voip_user_top_name_tag"; + public static final String EXTRA_BUNDLE_KEY_USER_PROFILE_TAG_ORDER_TAG = + "extra_bundle_key_user_profile_tag_order_tag"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + RLog.d(TAG, "BaseCallActivity onCreate"); + audioVideoConfig(); + getWindow() + .setFlags( + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow() + .setFlags( + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getWindow() + .addFlags( + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + shouldRestoreFloat = true; + CallKitUtils.shouldShowFloat = false; + + createPowerManager(); + boolean isScreenOn = powerManager.isScreenOn(); + if (!isScreenOn) { + wakeLock.acquire(); + } + handler = new Handler(); + mLargeLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); + RongCallProxy.getInstance().setCallListener(this); + + AudioPlayManager.getInstance().stopPlay(); + AudioRecordManager.getInstance().destroyRecord(); + RongUserInfoManager.getInstance().addUserDataObserver(this); + + AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + if (am != null) { + onAudioFocusChangeListener = + new AudioManager.OnAudioFocusChangeListener() { + @Override + public void onAudioFocusChange(int focusChange) {} + }; + am.requestAudioFocus( + onAudioFocusChangeListener, + AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); + } + if (Build.VERSION.SDK_INT >= 31 + && getApplication().getApplicationInfo().targetSdkVersion >= 31) { + // Android 12 禁止了通过广播和服务跳板的方式启动 Activity,此代码是为了兼容之前的逻辑 + Intent intent = new Intent(); + intent.setAction(VoIPBroadcastReceiver.ACTION_CLEAR_VOIP_NOTIFICATION); + intent.setPackage(getPackageName()); + sendBroadcast(intent); + } + } + + @Override + protected void onStart() { + super.onStart(); + Intent intent = getIntent(); + Bundle bundle = intent.getBundleExtra("floatbox"); + if (shouldRestoreFloat && bundle != null) { + onRestoreFloatBox(bundle); + } + } + + public void callRinging(RingingMode mode) { + CallRingingUtil.getInstance().startRinging(this, mode); + } + + public void onIncomingCallRinging(RongCallSession callSession) { + CallRingingUtil.getInstance().startRinging(this, RingingMode.Incoming); + if (callSession != null) { + String callId = callSession.getCallId(); + if (!TextUtils.isEmpty(callId) + && callId.equals( + getIntent() + .getStringExtra( + RongIncomingCallService.KEY_NEED_AUTO_ANSWER))) { + RongCallClient.getInstance().acceptCall(callId); + } + } + } + + public void setupTime(final TextView timeView) { + try { + if (updateTimeRunnable != null) { + handler.removeCallbacks(updateTimeRunnable); + } + timeView.setVisibility(View.VISIBLE); + updateTimeRunnable = new UpdateTimeRunnable(timeView); + handler.post(updateTimeRunnable); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void cancelTime() { + if (handler != null && updateTimeRunnable != null) { + handler.removeCallbacks(updateTimeRunnable); + } + } + + public long getTime(long activeTime) { + long tmpTime = activeTime == 0 ? 0 : (System.currentTimeMillis() - activeTime) / 1000; + time = tmpTime == 0 ? time : tmpTime; + return time; + } + + @SuppressLint("MissingPermission") + protected void stopRing() { + CallRingingUtil.getInstance().stopRinging(); + } + + @Override + public void onCallIncoming(RongCallSession callSession, SurfaceView localVideo) { + CallKitUtils.shouldShowFloat = true; + CallKitUtils.isDial = false; + } + + @Override + public void onCallOutgoing(RongCallSession callProfile, SurfaceView localVideo) { + CallKitUtils.shouldShowFloat = true; + CallKitUtils.isDial = true; + } + + @Override + public void onRemoteUserRinging(String userId) {} + + @Override + public void onRemoteUserAccept(String userId, CallMediaType mediaType) {} + + @Override + public void onCallDisconnected( + RongCallSession callProfile, RongCallCommon.CallDisconnectedReason reason) { + stopForegroundService(); + if (RongCallKit.getCustomerHandlerListener() != null) { + RongCallKit.getCustomerHandlerListener().onCallDisconnected(callProfile, reason); + } + CallKitUtils.callConnected = false; + CallKitUtils.shouldShowFloat = false; + + toastDisconnectReason(reason); + + AudioPlayManager.getInstance().setInVoipMode(false); + stopRing(); + NotificationUtil.getInstance() + .clearNotification(this, BaseCallActivity.CALL_NOTIFICATION_ID); + RongCallProxy.getInstance().setCallListener(null); + } + + @Override + public void onRemoteUserJoined( + String userId, + RongCallCommon.CallMediaType mediaType, + int userType, + SurfaceView remoteVideo) { + CallKitUtils.isDial = false; + } + + @Override + public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) { + if (RongCallKit.getCustomerHandlerListener() != null) { + RongCallKit.getCustomerHandlerListener().onRemoteUserInvited(userId, mediaType); + } + } + + @Override + public void onRemoteUserLeft(String userId, RongCallCommon.CallDisconnectedReason reason) { + RLog.i( + TAG, + "onRemoteUserLeft userId :" + + userId + + ", CallDisconnectedReason :" + + reason.name()); + toastDisconnectReason(reason); + } + + @Override + public void onMediaTypeChanged( + String userId, RongCallCommon.CallMediaType mediaType, SurfaceView video) {} + + @Override + public void onError(RongCallCommon.CallErrorCode errorCode) { + AudioPlayManager.getInstance().setInVoipMode(false); + if (RongCallCommon.CallErrorCode.ENGINE_NOT_FOUND.getValue() == errorCode.getValue()) { + Toast.makeText( + this, + getResources().getString(R.string.rc_voip_engine_notfound), + Toast.LENGTH_SHORT) + .show(); + finish(); + } + } + + @Override + public void onCallConnected(RongCallSession callProfile, SurfaceView localVideo) { + registerAudioRouteTypeChange(); + if (RongCallKit.getCustomerHandlerListener() != null) { + RongCallKit.getCustomerHandlerListener().onCallConnected(callProfile, localVideo); + } + CallKitUtils.callConnected = true; + CallKitUtils.shouldShowFloat = true; + CallKitUtils.isDial = false; + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + if (audioManager != null) { + audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + } + AudioPlayManager.getInstance().setInVoipMode(true); + AudioRecordManager.getInstance().destroyRecord(); + } + + private void registerAudioRouteTypeChange() { + RCRTCAudioRouteManager.getInstance() + .setOnAudioRouteChangedListener( + new IRCRTCAudioRouteListener() { + @Override + public void onRouteChanged(RCAudioRouteType type) { + resetHandFreeStatus(type); + } + + @Override + public void onRouteSwitchFailed( + RCAudioRouteType fromType, RCAudioRouteType toType) {} + }); + } + + protected void resetHandFreeStatus(RCAudioRouteType type) {} + + @Override + protected void onStop() { + super.onStop(); + RLog.d(TAG, "BaseCallActivity onStop"); + if (CallKitUtils.shouldShowFloat && !checkingOverlaysPermission) { + showForegroundService(); + Bundle bundle = new Bundle(); + String action = onSaveFloatBoxState(bundle); + if (checkDrawOverlaysPermission(true)) { + if (action != null) { + bundle.putString("action", action); + showFB(getApplicationContext(), bundle); + if (!isFinishing()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + RLog.d(TAG, "BaseCallActivity onStop finishAndRemoveTask()"); + finishAndRemoveTask(); + } else { + RLog.d(TAG, "BaseCallActivity onStop finish()"); + finish(); + } + } + } + } else { + Toast.makeText( + this, + getString(R.string.rc_voip_float_window_not_allowed), + Toast.LENGTH_SHORT) + .show(); + } + } else if (checkingOverlaysPermission) { + showForegroundService(); + } + } + + private void showForegroundService() { + RongCallSession callSession = RongCallClient.getInstance().getCallSession(); + if (callSession == null) { + Log.e(TAG, "showForegroundService: RongCallSession is Null!"); + return; + } + + String content = + callSession.getMediaType().getValue() == CallMediaType.AUDIO.getValue() + ? getString(R.string.rc_audio_call_on_going) + : getString(R.string.rc_video_call_on_going); + String action = getIntent().getAction(); + String title = getString(R.string.rc_call_on_going); + Intent intent = new Intent(this, CallForegroundService.class); + intent.putExtra("content", content); + intent.putExtra("action", action); + intent.putExtra("title", title); + Bundle bundle = new Bundle(); + onSaveFloatBoxState(bundle); + bundle.putBoolean("isDial", isDial); + intent.putExtra("floatbox", bundle); + if (VERSION.SDK_INT >= VERSION_CODES.O) { + startForegroundService(intent); + } else { + startService(intent); + } + } + + private void stopForegroundService() { + try { + stopService(new Intent(this, CallForegroundService.class)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + protected void onResume() { + super.onResume(); + stopForegroundService(); + RLog.d(TAG, "BaseCallActivity onResume"); + try { + RongCallSession session = RongCallClient.getInstance().getCallSession(); + if (session != null) { + if (session.getMediaType() == RongCallCommon.CallMediaType.VIDEO && !isMuteCamera) { + RongCallClient.getInstance().startCapture(); + } + RongCallProxy.getInstance().setCallListener(this); + if (shouldRestoreFloat) { + CallFloatBoxView.hideFloatBox(); + NotificationUtil.getInstance() + .clearNotification(this, BaseCallActivity.CALL_NOTIFICATION_ID); + CallRingingUtil.getInstance().stopServiceButContinueRinging(this); + } + time = getTime(session.getActiveTime()); + shouldRestoreFloat = true; + if (time > 0) { + CallKitUtils.shouldShowFloat = true; + } + if (checkingOverlaysPermission) { + checkDrawOverlaysPermission(false); + checkingOverlaysPermission = false; + } + } + } catch (Exception e) { + e.printStackTrace(); + RLog.d(TAG, "BaseCallActivity onResume Error : " + e.getMessage()); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + shouldRestoreFloat = false; + if (RongCallKit.getCustomerHandlerListener() != null) { + List selectedUserIds = + RongCallKit.getCustomerHandlerListener() + .handleActivityResult(requestCode, resultCode, data); + if (selectedUserIds != null && selectedUserIds.size() > 0) onAddMember(selectedUserIds); + } + } + + @Override + protected void onDestroy() { + try { + RLog.d(TAG, "BaseCallActivity onDestroy"); + RongUserInfoManager.getInstance().removeUserDataObserver(this); + // RongUserInfoManager.getInstance().remove + handler.removeCallbacks(updateTimeRunnable); + CallRingingUtil.getInstance().stopRinging(); + + // 退出此页面后应设置成正常模式,否则按下音量键无法更改其他音频类型的音量 + AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + if (am != null) { + am.setMode(AudioManager.MODE_NORMAL); + if (onAudioFocusChangeListener != null) { + am.abandonAudioFocus(onAudioFocusChangeListener); + } + } + } catch (IllegalStateException e) { + e.printStackTrace(); + Log.i(MEDIAPLAYERTAG, "--- onDestroy IllegalStateException---"); + } + super.onDestroy(); + // unRegisterHeadsetplugReceiver(); + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); + } + + if (screenLock != null && screenLock.isHeld()) { + try { + screenLock.setReferenceCounted(false); + screenLock.release(); + } catch (Exception e) { + + } + } + } + + @Override + public void onRemoteCameraDisabled(String userId, boolean disabled) {} + + @Override + public void onRemoteMicrophoneDisabled(String userId, boolean disabled) {} + + @Override + public void onNetworkReceiveLost(String userId, int lossRate) {} + + @Override + public void onNetworkSendLost(int lossRate, int delay) {} + + @Override + public void onFirstRemoteVideoFrame(String userId, int height, int width) {} + + @Override + public void onFirstRemoteAudioFrame(String userId) {} + + @Override + public void onAudioLevelSend(String audioLevel) {} + + @Override + public void onAudioLevelReceive(HashMap audioLevel) {} + + private void toastDisconnectReason(RongCallCommon.CallDisconnectedReason reason) { + String text = null; + switch (reason) { + case CANCEL: + text = getString(R.string.rc_voip_mo_cancel); + break; + case REJECT: + text = getString(R.string.rc_voip_mo_reject); + break; + case NO_RESPONSE: + case BUSY_LINE: + text = getString(R.string.rc_voip_mo_no_response); + break; + case REMOTE_BUSY_LINE: + text = getString(R.string.rc_voip_mt_busy); + break; + case REMOTE_CANCEL: + text = getString(R.string.rc_voip_mt_cancel); + break; + case REMOTE_REJECT: + text = getString(R.string.rc_voip_mt_reject); + break; + case REMOTE_NO_RESPONSE: + text = getString(R.string.rc_voip_mt_no_response); + break; + case NETWORK_ERROR: + if (!CallKitUtils.isNetworkAvailable(this)) { + text = getString(R.string.rc_voip_call_network_error); + } else { + text = getString(R.string.rc_voip_call_terminalted); + } + break; + case REMOTE_HANGUP: + case HANGUP: + case INIT_VIDEO_ERROR: + text = getString(R.string.rc_voip_call_terminalted); + break; + case OTHER_DEVICE_HAD_ACCEPTED: + text = getString(R.string.rc_voip_call_other); + break; + } + if (text != null) { + showShortToast(text); + } + } + + public void onRemoteUserPublishVideoStream( + String userId, String streamId, String tag, SurfaceView surfaceView) {} + + @Override + public void onRemoteUserUnpublishVideoStream(String userId, String streamId, String tag) {} + + /** onStart时恢复浮窗 * */ + public void onRestoreFloatBox(Bundle bundle) { + isMuteCamera = bundle.getBoolean(EXTRA_BUNDLE_KEY_MUTECAMERA); + } + + protected void addMember(ArrayList currentMemberIds) { + // do your job to add more member + // after got your new member, call onAddMember + if (RongCallKit.getCustomerHandlerListener() != null) { + RongCallKit.getCustomerHandlerListener().addMember(this, currentMemberIds); + } + } + + protected void onAddMember(List newMemberIds) {} + + /** onPause时保存页面各状态数据 * */ + public String onSaveFloatBoxState(Bundle bundle) { + return null; + } + + @TargetApi(23) + boolean requestCallPermissions(RongCallCommon.CallMediaType type, int requestCode) { + // String[] permissions = null; + Log.i(TAG, "BaseActivty requestCallPermissions requestCode=" + requestCode); + // if (type.equals(RongCallCommon.CallMediaType.VIDEO) + // || type.equals(RongCallCommon.CallMediaType.AUDIO)) { + // permissions = CallKitUtils.getCallpermissions(); + // } + // boolean result = false; + // if (permissions != null) { + // boolean granted = CallKitUtils.checkPermissions(this, permissions); + // Log.i(TAG, "BaseActivty requestCallPermissions granted=" + granted); + // if (granted) { + // result = true; + // } else { + // PermissionCheckUtil.requestPermissions(this, permissions, requestCode); + // } + // } + + return RongCallPermissionUtil.checkAndRequestPermissionByCallType(this, type, requestCode); + } + + @Override + public void onUserUpdate(UserInfo info) {} + + @Override + public void onGroupUpdate(Group group) {} + + @Override + public void onGroupUserInfoUpdate(GroupUserInfo groupUserInfo) {} + + private class UpdateTimeRunnable implements Runnable { + private TextView timeView; + + public UpdateTimeRunnable(TextView timeView) { + this.timeView = timeView; + } + + @SuppressLint("DefaultLocale") + @Override + public void run() { + time++; + if (time >= 3600) { + timeView.setText( + String.format( + Locale.ROOT, + "%d:%02d:%02d", + time / 3600, + (time % 3600) / 60, + (time % 60))); + } else { + timeView.setText( + String.format(Locale.ROOT, "%02d:%02d", (time % 3600) / 60, (time % 60))); + } + handler.postDelayed(this, 1000); + } + } + + void onMinimizeClick(View view) { + if (checkDrawOverlaysPermission(true)) { + finish(); + } else { + Toast.makeText( + this, + getString(R.string.rc_voip_float_window_not_allowed), + Toast.LENGTH_SHORT) + .show(); + } + } + + private boolean checkDrawOverlaysPermission(boolean needOpenPermissionSetting) { + if (RongCallPermissionUtil.checkFloatWindowPermission(this)) { + checkingOverlaysPermission = false; + return true; + } else { + if (needOpenPermissionSetting + && !checkingOverlaysPermission + && !notRemindRequestFloatWindowPermissionAgain) { + RongCallPermissionUtil.requestFloatWindowNeedPermission( + this, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (DialogInterface.BUTTON_POSITIVE == which) { + checkingOverlaysPermission = true; + } else if (DialogInterface.BUTTON_NEGATIVE == which) { + checkingOverlaysPermission = false; + } else if (DialogInterface.BUTTON_NEUTRAL == which) { + checkingOverlaysPermission = false; + notRemindRequestFloatWindowPermissionAgain = true; + } + } + }); + } + return false; + } + } + + private void createPowerManager() { + if (powerManager == null) { + powerManager = (PowerManager) getSystemService(POWER_SERVICE); + wakeLock = + powerManager.newWakeLock( + PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK, + TAG); + wakeLock.setReferenceCounted(false); + screenLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); + screenLock.setReferenceCounted(false); + } + } + + protected void createPickupDetector() { + if (pickupDetector == null) { + pickupDetector = new PickupDetector(this); + } + } + + @Override + public void onPickupDetected(boolean isPickingUp) { + if (screenLock == null) { + RLog.d(TAG, "No PROXIMITY_SCREEN_OFF_WAKE_LOCK"); + return; + } + if (isPickingUp && !screenLock.isHeld()) { + screenLock.acquire(); + } + if (!isPickingUp && screenLock.isHeld()) { + try { + screenLock.setReferenceCounted(false); + screenLock.release(); + } catch (Exception e) { + + } + } + } + + @Override + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (!RongCallPermissionUtil.checkPermissions(this, permissions)) { + RongCallPermissionUtil.showRequestPermissionFailedAlter( + this, permissions, grantResults); + } + } + + /** outgoing (initView)incoming处注册 */ + public void regisHeadsetPlugReceiver() { + if (BluetoothUtil.isSupportBluetooth()) { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction("android.intent.action.HEADSET_PLUG"); + intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + headsetPlugReceiver = new HeadsetPlugReceiver(); + registerReceiver( + headsetPlugReceiver, + intentFilter, + this.getApplicationInfo().packageName + ".permission.RONG_ACCESS_RECEIVER", + null); + } + } + + /** onHangupBtnClick onDestory 处解绑 */ + public void unRegisterHeadsetplugReceiver() { + if (headsetPlugReceiver != null) { + unregisterReceiver(headsetPlugReceiver); + headsetPlugReceiver = null; + } + } + + /** + * 设置开始音视频参数配置信息
+ * 必须在{@link RongCallClient#startCall} 和 {@link RongCallClient#acceptCall(String)}之前设置
+ */ + public void audioVideoConfig() { + // RongRTCConfig.Builder configBuilder = new RongRTCConfig.Builder(); + // configBuilder.setVideoResolution(RCRTCVideoResolution.RESOLUTION_480_640); + // configBuilder.setVideoFps(RCRTCVideoFps.Fps_15); + // configBuilder.setMaxRate(1000); + // configBuilder.setMinRate(350); + // /* + // * 设置建立 Https 连接时,是否使用自签证书。 + // * 公有云用户无需调用此方法,私有云用户使用自签证书时调用此方法设置 + // */ + // // configBuilder.enableHttpsSelfCertificate(true); + // RongCallClient.getInstance().setRTCConfig(configBuilder); + + Builder builder = + Builder.create() + .setVideoResolution(RCRTCVideoResolution.RESOLUTION_480_640) + .setVideoFps(RCRTCVideoFps.Fps_15) + .setMaxRate(1000) + .setMinRate(350); + RongCallClient instance = RongCallClient.getInstance(); + if (instance != null) { + instance.setVideoConfig(builder); + } + } + + protected void onHeadsetPlugUpdate(HeadsetInfo headsetInfo) {} + + public class HeadsetPlugReceiver extends BroadcastReceiver { + + private final String TAG = HeadsetPlugReceiver.class.getSimpleName(); + // 动态注册了监听有线耳机之后 默认会调用一次有限耳机拔出 + public boolean FIRST_HEADSET_PLUG_RECEIVER = false; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + HeadsetInfo headsetInfo = null; + if ("android.intent.action.HEADSET_PLUG".equals(action)) { + int state = -1; + if (FIRST_HEADSET_PLUG_RECEIVER) { + if (intent.hasExtra("state")) { + state = intent.getIntExtra("state", -1); + } + if (state == 1) { + headsetInfo = new HeadsetInfo(true, HeadsetInfo.HeadsetType.WiredHeadset); + } else if (state == 0) { + headsetInfo = new HeadsetInfo(false, HeadsetInfo.HeadsetType.WiredHeadset); + } + } else { + FIRST_HEADSET_PLUG_RECEIVER = true; + } + } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + headsetInfo = new HeadsetInfo(false, HeadsetInfo.HeadsetType.BluetoothA2dp); + break; + case BluetoothProfile.STATE_CONNECTED: + headsetInfo = new HeadsetInfo(true, HeadsetInfo.HeadsetType.BluetoothA2dp); + break; + } + } + if (null != headsetInfo) { // onHandFreeButtonClick + onHeadsetPlugUpdate(headsetInfo); + } else { + FinLog.e(TAG, "HeadsetPlugReceiver headsetInfo=null !"); + } + } + } + // +} diff --git a/callkit/src/main/java/io/rong/callkit/BaseNoActionBarActivity.java b/callkit/src/main/java/io/rong/callkit/BaseNoActionBarActivity.java new file mode 100644 index 000000000..efe0f6791 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/BaseNoActionBarActivity.java @@ -0,0 +1,14 @@ +package io.rong.callkit; + +import android.app.Activity; +import android.content.Context; +import io.rong.imkit.utils.language.RongConfigurationManager; + +public class BaseNoActionBarActivity extends Activity { + @Override + protected void attachBaseContext(Context newBase) { + Context newContext = + RongConfigurationManager.getInstance().getConfigurationContext(newBase); + super.attachBaseContext(newContext); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/CallEndMessageItemProvider.java b/callkit/src/main/java/io/rong/callkit/CallEndMessageItemProvider.java new file mode 100644 index 000000000..bbc31f32c --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/CallEndMessageItemProvider.java @@ -0,0 +1,248 @@ +package io.rong.callkit; + +import static io.rong.calllib.RongCallCommon.CallDisconnectedReason.HANGUP; +import static io.rong.calllib.RongCallCommon.CallDisconnectedReason.OTHER_DEVICE_HAD_ACCEPTED; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; +import io.rong.callkit.util.CallKitUtils; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import io.rong.calllib.message.CallSTerminateMessage; +import io.rong.imkit.conversation.messgelist.provider.BaseMessageItemProvider; +import io.rong.imkit.model.UiMessage; +import io.rong.imkit.widget.adapter.IViewProviderListener; +import io.rong.imkit.widget.adapter.ViewHolder; +import io.rong.imlib.model.Message; +import io.rong.imlib.model.MessageContent; +import java.util.List; +import java.util.Locale; + +public class CallEndMessageItemProvider extends BaseMessageItemProvider { + @Override + protected io.rong.imkit.widget.adapter.ViewHolder onCreateMessageContentViewHolder( + ViewGroup parent, int viewType) { + View textView = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.rc_text_message_item, parent, false); + return new ViewHolder(parent.getContext(), textView); + } + + @Override + protected void bindMessageContentViewHolder( + ViewHolder holder, + ViewHolder parentHolder, + CallSTerminateMessage callSTerminateMessage, + UiMessage uiMessage, + int position, + List list, + IViewProviderListener listener) { + Message message = uiMessage.getMessage(); + final TextView view = holder.getView(io.rong.imkit.R.id.rc_text); + if (message.getMessageDirection() == Message.MessageDirection.SEND) { + view.setBackgroundResource(R.drawable.rc_ic_bubble_right); + } else { + view.setBackgroundResource(R.drawable.rc_ic_bubble_left); + } + + RongCallCommon.CallMediaType mediaType = callSTerminateMessage.getMediaType(); + String direction = callSTerminateMessage.getDirection(); + Drawable drawable = null; + + String msgContent = ""; + switch (callSTerminateMessage.getReason()) { + case CANCEL: + msgContent = view.getResources().getString(R.string.rc_voip_mo_cancel); + break; + case REJECT: + msgContent = view.getResources().getString(R.string.rc_voip_mo_reject); + break; + case NO_RESPONSE: + case BUSY_LINE: + msgContent = view.getResources().getString(R.string.rc_voip_mo_no_response); + break; + case REMOTE_BUSY_LINE: + msgContent = view.getResources().getString(R.string.rc_voip_mt_busy); + break; + case REMOTE_CANCEL: + msgContent = view.getResources().getString(R.string.rc_voip_mt_cancel); + break; + case REMOTE_REJECT: + msgContent = view.getResources().getString(R.string.rc_voip_mt_reject); + break; + case REMOTE_NO_RESPONSE: + msgContent = view.getResources().getString(R.string.rc_voip_mt_no_response); + break; + case NETWORK_ERROR: + case REMOTE_NETWORK_ERROR: + case INIT_VIDEO_ERROR: + msgContent = view.getResources().getString(R.string.rc_voip_call_interrupt); + break; + case OTHER_DEVICE_HAD_ACCEPTED: + msgContent = view.getResources().getString(R.string.rc_voip_call_other); + break; + case SERVICE_NOT_OPENED: + case REMOTE_ENGINE_UNSUPPORTED: + msgContent = view.getResources().getString(R.string.rc_voip_engine_notfound); + break; + case REJECTED_BY_BLACKLIST: + msgContent = + view.getResources().getString(R.string.rc_voip_mo_rejected_by_blocklist); + break; + default: + String mo_reject = view.getResources().getString(R.string.rc_voip_mo_reject); + String mt_reject = view.getResources().getString(R.string.rc_voip_mt_reject); + String extra = callSTerminateMessage.getExtra(); + String timeRegex = "([0-9]?[0-9]:)?([0-5][0-9]:)?([0-5][0-9])$"; + if (!TextUtils.isEmpty(extra)) { + boolean val = extra.matches(timeRegex); + if (val) { + msgContent = + view.getResources().getString(R.string.rc_voip_call_time_length); + msgContent += extra; + } else { + msgContent = + view.getResources().getString(R.string.rc_voip_call_time_length); + } + } else { + msgContent = + callSTerminateMessage.getReason() == HANGUP ? mo_reject : mt_reject; + } + break; + } + + view.setText(msgContent); + view.setCompoundDrawablePadding(15); + + if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) { + if (direction != null && direction.equals("MO")) { + drawable = view.getResources().getDrawable(R.drawable.rc_voip_video_right); + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + view.setCompoundDrawablesRelative(null, null, drawable, null); + view.setTextColor(view.getResources().getColor(R.color.rc_voip_color_right)); + } else { + drawable = view.getResources().getDrawable(R.drawable.rc_voip_video_left); + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + view.setCompoundDrawablesRelative(drawable, null, null, null); + view.setTextColor(view.getResources().getColor(R.color.rc_voip_color_left)); + } + } else { + if (direction != null && direction.equals("MO")) { + if (callSTerminateMessage.getReason().equals(HANGUP) + || callSTerminateMessage + .getReason() + .equals(RongCallCommon.CallDisconnectedReason.REMOTE_HANGUP)) { + drawable = + view.getResources() + .getDrawable(R.drawable.rc_voip_audio_right_connected); + } else { + drawable = + view.getResources().getDrawable(R.drawable.rc_voip_audio_right_cancel); + } + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + view.setCompoundDrawablesRelative(null, null, drawable, null); + view.setTextColor(view.getResources().getColor(R.color.rc_voip_color_right)); + } else { + if (callSTerminateMessage.getReason().equals(HANGUP) + || callSTerminateMessage + .getReason() + .equals(RongCallCommon.CallDisconnectedReason.REMOTE_HANGUP)) { + drawable = + view.getResources() + .getDrawable(R.drawable.rc_voip_audio_left_connected); + } else { + drawable = + view.getResources().getDrawable(R.drawable.rc_voip_audio_left_cancel); + } + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + view.setCompoundDrawablesRelative(drawable, null, null, null); + view.setTextColor(view.getResources().getColor(R.color.rc_voip_color_left)); + } + } + } + + @Override + protected boolean onItemClick( + ViewHolder holder, + CallSTerminateMessage callSTerminateMessage, + UiMessage uiMessage, + int position, + List list, + IViewProviderListener listener) { + if (callSTerminateMessage.getReason() == OTHER_DEVICE_HAD_ACCEPTED) { + return true; + } + Context context = holder.getContext(); + RongCallSession profile = RongCallClient.getInstance().getCallSession(); + if (profile != null && profile.getActiveTime() > 0) { + if (profile.getMediaType() == RongCallCommon.CallMediaType.AUDIO) { + Toast.makeText( + context, + context.getString(R.string.rc_voip_call_audio_start_fail), + Toast.LENGTH_SHORT) + .show(); + } else { + Toast.makeText( + context, + context.getString(R.string.rc_voip_call_video_start_fail), + Toast.LENGTH_SHORT) + .show(); + } + return true; + } + if (!CallKitUtils.isNetworkAvailable(context)) { + Toast.makeText( + context, + context.getString(R.string.rc_voip_call_network_error), + Toast.LENGTH_SHORT) + .show(); + return true; + } + RongCallCommon.CallMediaType mediaType = callSTerminateMessage.getMediaType(); + String action = null; + if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEVIDEO; + } else { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEAUDIO; + } + Intent intent = new Intent(action); + intent.setPackage(context.getPackageName()); + intent.putExtra( + "conversationType", + uiMessage.getMessage().getConversationType().getName().toLowerCase(Locale.US)); + intent.putExtra("targetId", uiMessage.getMessage().getTargetId()); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + context.startActivity(intent); + return true; + } + + @Override + protected boolean isMessageViewType(MessageContent messageContent) { + return messageContent instanceof CallSTerminateMessage; + } + + @Override + public Spannable getSummarySpannable( + Context context, CallSTerminateMessage callSTerminateMessage) { + RongCallCommon.CallMediaType mediaType = callSTerminateMessage.getMediaType(); + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + return new SpannableString(context.getString(R.string.rc_voip_message_audio)); + } else { + return new SpannableString(context.getString(R.string.rc_voip_message_video)); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/CallFloatBoxView.java b/callkit/src/main/java/io/rong/callkit/CallFloatBoxView.java new file mode 100644 index 000000000..683cc1061 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/CallFloatBoxView.java @@ -0,0 +1,1216 @@ +package io.rong.callkit; + +import static io.rong.callkit.util.CallKitUtils.isDial; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.media.AudioManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import io.rong.callkit.util.ActivityStartCheckUtils; +import io.rong.callkit.util.CallKitUtils; +import io.rong.calllib.CallUserProfile; +import io.rong.calllib.IRongCallListener; +import io.rong.calllib.ReportUtil; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallCommon.CallMediaType; +import io.rong.calllib.RongCallSession; +import io.rong.calllib.message.CallSTerminateMessage; +import io.rong.common.RLog; +import io.rong.imkit.IMCenter; +import io.rong.imkit.manager.AudioPlayManager; +import io.rong.imkit.notification.NotificationUtil; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.model.Conversation; +import io.rong.message.InformationNotificationMessage; +import java.util.HashMap; +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; + +/** Created by weiqinxiao on 16/3/17. */ +public class CallFloatBoxView { + private static Context mContext; + private static Timer timer; + private static long mTime; + private static View mView; + private static Boolean isShown = false; + private static WindowManager wm; + private static Bundle mBundle; + private static final String TAG = "CallFloatBoxView"; + private static TextView showFBCallTime = null; + private static FrameLayout remoteVideoContainer = null; + private static boolean activityResuming = false; + + public static void showFB(Context context, Bundle bundle) { + Log.i("audioTag", "CallKitUtils.isDial=" + CallKitUtils.isDial); + setExcludeFromRecents(context, true); + activityResuming = false; + if (CallKitUtils.isDial) { + CallFloatBoxView.showFloatBoxToCall(context, bundle); + } else { + CallFloatBoxView.showFloatBox(context, bundle); + } + } + + public static void showFloatBox(Context context, Bundle bundle) { + if (isShown) { + return; + } + mContext = context; + isShown = true; + RongCallSession session = RongCallClient.getInstance().getCallSession(); + long activeTime = session != null ? session.getActiveTime() : 0; + mTime = activeTime == 0 ? 0 : (System.currentTimeMillis() - activeTime) / 1000; + if (mTime > 0) { + setAudioMode(AudioManager.MODE_IN_COMMUNICATION); + } + mBundle = bundle; + wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + WindowManager.LayoutParams params = createLayoutParams(context); + RongCallCommon.CallMediaType mediaType = + RongCallCommon.CallMediaType.valueOf(bundle.getInt("mediaType")); + if (mediaType == RongCallCommon.CallMediaType.VIDEO + && session != null + && session.getConversationType() == Conversation.ConversationType.PRIVATE) { + SurfaceView remoteVideo = null; + for (CallUserProfile profile : session.getParticipantProfileList()) { + if (!TextUtils.equals( + profile.getUserId(), RongIMClient.getInstance().getCurrentUserId())) { + remoteVideo = profile.getVideoView(); + } + } + if (remoteVideo != null) { + ViewGroup parent = (ViewGroup) remoteVideo.getParent(); + if (parent != null) { + parent.removeView(remoteVideo); + } + Resources resources = mContext.getResources(); + params.width = resources.getDimensionPixelSize(R.dimen.callkit_dimen_size_60); + params.height = resources.getDimensionPixelSize(R.dimen.callkit_dimen_size_80); + remoteVideoContainer = new FrameLayout(mContext); + remoteVideoContainer.addView( + remoteVideo, + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + remoteVideoContainer.setOnTouchListener(createTouchListener()); + wm.addView(remoteVideoContainer, params); + } + } + if (remoteVideoContainer == null) { + mView = LayoutInflater.from(context).inflate(R.layout.rc_voip_float_box, null); + mView.setOnTouchListener(createTouchListener()); + wm.addView(mView, params); + TextView timeV = (TextView) mView.findViewById(R.id.rc_time); + setupTime(timeV); + ImageView mediaIconV = (ImageView) mView.findViewById(R.id.rc_voip_media_type); + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + mediaIconV.setImageResource(R.drawable.rc_voip_float_audio); + } else { + mediaIconV.setImageResource(R.drawable.rc_voip_float_video); + } + } else { + // 视频悬浮窗下,不需要UI显示时间,但是时间值也需要同步更新 + setupTime(null); + } + RongCallClient.getInstance() + .setVoIPCallListener( + new IRongCallListener() { + @Override + public void onCallIncoming( + RongCallSession callSession, SurfaceView localVideo) {} + + @Override + public void onCallOutgoing( + RongCallSession callInfo, SurfaceView localVideo) {} + + @Override + public void onRemoteUserRinging(String userId) {} + + @Override + public void onRemoteUserAccept( + String userId, CallMediaType mediaType) {} + + @Override + public void onCallDisconnected( + RongCallSession callProfile, + RongCallCommon.CallDisconnectedReason reason) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + callProfile, + "state|reason|desc", + "onCallDisconnected", + reason.getValue(), + TAG); + stopForegroundService(mContext); + setExcludeFromRecents(mContext, false); + String senderId; + String extra = ""; + senderId = callProfile.getInviterUserId(); + long activeTime = callProfile.getActiveTime(); + long tmpTime = + activeTime == 0 + ? 0 + : (System.currentTimeMillis() - activeTime) / 1000; + mTime = tmpTime == 0 ? mTime : tmpTime; + if (mTime >= 3600) { + extra = + String.format( + Locale.ROOT, + "%d:%02d:%02d", + mTime / 3600, + (mTime % 3600) / 60, + (mTime % 60)); + } else { + extra = + String.format( + Locale.ROOT, + "%02d:%02d", + (mTime % 3600) / 60, + (mTime % 60)); + } + if (!TextUtils.isEmpty(senderId)) { + switch (callProfile.getConversationType()) { + case PRIVATE: + CallSTerminateMessage callSTerminateMessage = + new CallSTerminateMessage(); + callSTerminateMessage.setReason(reason); + callSTerminateMessage.setMediaType( + callProfile.getMediaType()); + callSTerminateMessage.setExtra(extra); + long serverTime = + System.currentTimeMillis() + - RongIMClient.getInstance() + .getDeltaTime(); + if (senderId.equals(callProfile.getSelfUserId())) { + callSTerminateMessage.setDirection("MO"); + IMCenter.getInstance() + .insertOutgoingMessage( + Conversation.ConversationType + .PRIVATE, + callProfile.getTargetId(), + io.rong.imlib.model.Message + .SentStatus.SENT, + callSTerminateMessage, + serverTime, + null); + } else { + callSTerminateMessage.setDirection("MT"); + io.rong.imlib.model.Message.ReceivedStatus + receivedStatus = + new io.rong.imlib.model.Message + .ReceivedStatus(0); + IMCenter.getInstance() + .insertIncomingMessage( + Conversation.ConversationType + .PRIVATE, + callProfile.getTargetId(), + senderId, + receivedStatus, + callSTerminateMessage, + serverTime, + null); + } + break; + case GROUP: + InformationNotificationMessage + informationNotificationMessage; + serverTime = + System.currentTimeMillis() + - RongIMClient.getInstance() + .getDeltaTime(); + if (reason.equals( + RongCallCommon.CallDisconnectedReason + .NO_RESPONSE)) { + informationNotificationMessage = + InformationNotificationMessage.obtain( + mContext.getString( + R.string + .rc_voip_audio_no_response)); + } else { + informationNotificationMessage = + InformationNotificationMessage.obtain( + mContext.getString( + R.string + .rc_voip_audio_ended)); + } + + if (senderId.equals(callProfile.getSelfUserId())) { + IMCenter.getInstance() + .insertOutgoingMessage( + Conversation.ConversationType.GROUP, + callProfile.getTargetId(), + io.rong.imlib.model.Message + .SentStatus.SENT, + informationNotificationMessage, + serverTime, + null); + } else { + IMCenter.getInstance() + .insertIncomingMessage( + Conversation.ConversationType.GROUP, + callProfile.getTargetId(), + senderId, + CallKitUtils.getReceivedStatus( + reason), + informationNotificationMessage, + serverTime, + null); + } + break; + default: + break; + } + } + Toast.makeText( + mContext, + mContext.getString( + R.string.rc_voip_call_terminalted), + Toast.LENGTH_SHORT) + .show(); + + if (wm != null && mView != null && mView.isAttachedToWindow()) { + wm.removeView(mView); + mView = null; + } + if (wm != null + && remoteVideoContainer != null + && remoteVideoContainer.isAttachedToWindow()) { + wm.removeView(remoteVideoContainer); + remoteVideoContainer.setOnTouchListener(null); + remoteVideoContainer = null; + } + if (timer != null) { + timer.cancel(); + timer = null; + } + isShown = false; + mTime = 0; + setAudioMode(AudioManager.MODE_NORMAL); + AudioPlayManager.getInstance().setInVoipMode(false); + NotificationUtil.getInstance() + .clearNotification( + mContext, BaseCallActivity.CALL_NOTIFICATION_ID); + RongCallClient.getInstance() + .setVoIPCallListener(RongCallProxy.getInstance()); + } + + @Override + public void onRemoteUserJoined( + String userId, + RongCallCommon.CallMediaType mediaType, + int userType, + SurfaceView remoteVideo) { + CallKitUtils.isDial = false; + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserJoined", + TAG); + } + + @Override + public void onRemoteUserInvited( + String userId, RongCallCommon.CallMediaType mediaType) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserInvited", + TAG); + } + + @Override + public void onRemoteUserLeft( + String userId, RongCallCommon.CallDisconnectedReason reason) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserLeft", + TAG); + } + + @Override + public void onMediaTypeChanged( + String userId, + RongCallCommon.CallMediaType mediaType, + SurfaceView video) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + RongCallClient.getInstance().getCallSession(), + "state|desc", + "onMediaTypeChanged", + TAG); + if (mContext == null || !isShown || wm == null) { + Log.e( + TAG, + "set onMediaTypeChanged Failed CallFloatBoxView is Hiden"); + return; + } + WindowManager.LayoutParams params = createLayoutParams(mContext); + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + if (remoteVideoContainer != null) { + wm.removeView(remoteVideoContainer); + remoteVideoContainer = null; + } + if (mView == null) { + mView = + LayoutInflater.from(mContext) + .inflate(R.layout.rc_voip_float_box, null); + mView.setOnTouchListener(createTouchListener()); + wm.addView(mView, params); + TextView timeV = + (TextView) mView.findViewById(R.id.rc_time); + setupTime(timeV); + ImageView mediaIconV = + (ImageView) + mView.findViewById(R.id.rc_voip_media_type); + mediaIconV.setImageResource(R.drawable.rc_voip_float_audio); + } + } else if (RongCallClient.getInstance().getCallSession() != null) { + RongCallSession callSession = + RongCallClient.getInstance().getCallSession(); + if (callSession.getConversationType() + == Conversation.ConversationType.PRIVATE) { + if (mView != null) { + wm.removeView(mView); + mView = null; + } + SurfaceView remoteVideo = null; + for (CallUserProfile profile : + callSession.getParticipantProfileList()) { + if (!TextUtils.equals( + profile.getUserId(), + RongIMClient.getInstance() + .getCurrentUserId())) { + remoteVideo = profile.getVideoView(); + } + } + if (remoteVideo != null) { + ViewGroup parent = (ViewGroup) remoteVideo.getParent(); + if (parent != null) parent.removeView(remoteVideo); + Resources resources = mContext.getResources(); + params.width = + resources.getDimensionPixelSize( + R.dimen.callkit_dimen_size_60); + params.height = + resources.getDimensionPixelSize( + R.dimen.callkit_dimen_size_80); + remoteVideoContainer = new FrameLayout(mContext); + remoteVideoContainer.addView( + remoteVideo, + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + remoteVideoContainer.setOnTouchListener( + createTouchListener()); + wm.addView(remoteVideoContainer, params); + } + } else if (mView != null) { + ImageView mediaIconV = + (ImageView) + mView.findViewById(R.id.rc_voip_media_type); + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + mediaIconV.setImageResource( + R.drawable.rc_voip_float_audio); + } else { + mediaIconV.setImageResource( + R.drawable.rc_voip_float_video); + } + } + } + } + + @Override + public void onError(RongCallCommon.CallErrorCode errorCode) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + RongCallClient.getInstance().getCallSession(), + "code|state|desc", + errorCode.getValue(), + "onError", + TAG); + setAudioMode(AudioManager.MODE_NORMAL); + AudioPlayManager.getInstance().setInVoipMode(false); + } + + @Override + public void onCallConnected( + RongCallSession callInfo, SurfaceView localVideo) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + callInfo, + "state|desc", + "onCallConnected", + TAG); + CallKitUtils.isDial = false; + setAudioMode(AudioManager.MODE_IN_COMMUNICATION); + AudioPlayManager.getInstance().setInVoipMode(true); + } + + @Override + public void onRemoteCameraDisabled(String userId, boolean disabled) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|disabled|desc", + userId, + "onRemoteCameraDisabled", + disabled, + TAG); + } + + @Override + public void onRemoteMicrophoneDisabled( + String userId, boolean disabled) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|disabled|desc", + userId, + "onRemoteMicrophoneDisabled", + disabled, + TAG); + } + + @Override + public void onNetworkReceiveLost(String userId, int lossRate) {} + + @Override + public void onNetworkSendLost(int lossRate, int delay) {} + + @Override + public void onFirstRemoteVideoFrame( + String userId, int height, int width) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onFirstRemoteVideoFrame", + TAG); + } + + @Override + public void onFirstRemoteAudioFrame(String userId) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onFirstRemoteAudioFrame", + TAG); + } + + @Override + public void onAudioLevelSend(String audioLevel) {} + + public void onRemoteUserPublishVideoStream( + String userId, + String streamId, + String tag, + SurfaceView surfaceView) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|streamId|desc", + userId, + "onRemoteUserPublishVideoStream", + streamId, + TAG); + } + + @Override + public void onAudioLevelReceive(HashMap audioLevel) {} + + public void onRemoteUserUnpublishVideoStream( + String userId, String streamId, String tag) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|streamId|desc", + userId, + "onRemoteUserUnpublishVideoStream", + streamId, + TAG); + } + }); + } + + private static void stopForegroundService(Context context) { + if (context == null) { + return; + } + try { + context.stopService(new Intent(context, CallForegroundService.class)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static WindowManager.LayoutParams createLayoutParams(Context context) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + + int type; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < 24) { + type = WindowManager.LayoutParams.TYPE_TOAST; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + } else { + type = WindowManager.LayoutParams.TYPE_PHONE; + } + params.type = type; + params.flags = + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + params.format = PixelFormat.TRANSLUCENT; + params.width = ViewGroup.LayoutParams.WRAP_CONTENT; + params.height = ViewGroup.LayoutParams.WRAP_CONTENT; + params.gravity = Gravity.END | Gravity.CENTER_VERTICAL; + return params; + } + + private static View.OnTouchListener createTouchListener() { + return new View.OnTouchListener() { + float lastX, lastY; + int oldOffsetX, oldOffsetY; + int tag = 0; + + @Override + public boolean onTouch(View v, MotionEvent event) { + final int action = event.getAction(); + float x = event.getX(); + float y = event.getY(); + WindowManager.LayoutParams params = + (WindowManager.LayoutParams) v.getLayoutParams(); + if (params == null) { + return true; + } + if (tag == 0) { + oldOffsetX = params.x; + oldOffsetY = params.y; + } + if (action == MotionEvent.ACTION_DOWN) { + lastX = x; + lastY = y; + } else if (action == MotionEvent.ACTION_MOVE) { + // 减小偏移量,防止过度抖动 + params.x += (int) (x - lastX) / 3; + params.y += (int) (y - lastY) / 3; + tag = 1; + // if (mView != null) + // wm.updateViewLayout(mView, params); + // if (remoteVideoContainer != null) { + // wm.updateViewLayout(remoteVideoContainer, params); + // } + wm.updateViewLayout(v, params); + } else if (action == MotionEvent.ACTION_UP) { + int newOffsetX = params.x; + int newOffsetY = params.y; + if (Math.abs(oldOffsetX - newOffsetX) <= 20 + && Math.abs(oldOffsetY - newOffsetY) <= 20) { + if (!CallKitUtils.isFastDoubleClick()) { + onClickToResume(); + } + } else { + tag = 0; + } + } + return true; + } + }; + } + + public static void showFloatBoxToCall(Context context, Bundle bundle) { + if (isShown) { + return; + } + mContext = context; + isShown = true; + + mBundle = bundle; + wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + final WindowManager.LayoutParams params = createLayoutParams(context); + + mView = LayoutInflater.from(context).inflate(R.layout.rc_voip_float_box, null); + mView.setOnTouchListener( + new View.OnTouchListener() { + float lastX, lastY; + int oldOffsetX, oldOffsetY; + int tag = 0; + + @Override + public boolean onTouch(View v, MotionEvent event) { + final int action = event.getAction(); + float x = event.getX(); + float y = event.getY(); + if (tag == 0) { + oldOffsetX = params.x; + oldOffsetY = params.y; + } + if (action == MotionEvent.ACTION_DOWN) { + lastX = x; + lastY = y; + } else if (action == MotionEvent.ACTION_MOVE) { + // 减小偏移量,防止过度抖动 + params.x += (int) (x - lastX) / 3; + params.y += (int) (y - lastY) / 3; + tag = 1; + if (mView != null) wm.updateViewLayout(mView, params); + } else if (action == MotionEvent.ACTION_UP) { + int newOffsetX = params.x; + int newOffsetY = params.y; + if (Math.abs(oldOffsetX - newOffsetX) <= 20 + && Math.abs(oldOffsetY - newOffsetY) <= 20) { + if (!CallKitUtils.isFastDoubleClick()) { + onClickToResume(); + } + } else { + tag = 0; + } + } + return true; + } + }); + wm.addView(mView, params); + showFBCallTime = (TextView) mView.findViewById(R.id.rc_time); + showFBCallTime.setVisibility(View.GONE); + + ImageView mediaIconV = (ImageView) mView.findViewById(R.id.rc_voip_media_type); + RongCallCommon.CallMediaType mediaType = + RongCallCommon.CallMediaType.valueOf(bundle.getInt("mediaType")); + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + mediaIconV.setImageResource(R.drawable.rc_voip_float_audio); + } else { + mediaIconV.setImageResource(R.drawable.rc_voip_float_video); + } + RongCallClient.getInstance() + .setVoIPCallListener( + new IRongCallListener() { + @Override + public void onCallIncoming( + RongCallSession callSession, SurfaceView localVideo) {} + + @Override + public void onCallOutgoing( + RongCallSession callInfo, SurfaceView localVideo) {} + + @Override + public void onRemoteUserRinging(String userId) {} + + @Override + public void onRemoteUserAccept( + String userId, CallMediaType mediaType) {} + + @Override + public void onCallDisconnected( + RongCallSession callProfile, + RongCallCommon.CallDisconnectedReason reason) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + callProfile, + "state|reason|desc", + "onCallDisconnected", + reason.getValue(), + TAG); + stopForegroundService(mContext); + setExcludeFromRecents(mContext, false); + String senderId; + String extra = ""; + senderId = callProfile.getInviterUserId(); + long activeTime = callProfile.getActiveTime(); + long tmpTime = + activeTime == 0 + ? 0 + : (System.currentTimeMillis() - activeTime) / 1000; + mTime = tmpTime == 0 ? mTime : tmpTime; + if (mTime >= 3600) { + extra = + String.format( + Locale.ROOT, + "%d:%02d:%02d", + mTime / 3600, + (mTime % 3600) / 60, + (mTime % 60)); + } else { + extra = + String.format( + Locale.ROOT, + "%02d:%02d", + (mTime % 3600) / 60, + (mTime % 60)); + } + if (!TextUtils.isEmpty(senderId)) { + switch (callProfile.getConversationType()) { + case PRIVATE: + CallSTerminateMessage callSTerminateMessage = + new CallSTerminateMessage(); + callSTerminateMessage.setReason(reason); + callSTerminateMessage.setMediaType( + callProfile.getMediaType()); + callSTerminateMessage.setExtra(extra); + if (senderId.equals(callProfile.getSelfUserId())) { + callSTerminateMessage.setDirection("MO"); + IMCenter.getInstance() + .insertOutgoingMessage( + Conversation.ConversationType + .PRIVATE, + callProfile.getTargetId(), + io.rong.imlib.model.Message + .SentStatus.SENT, + callSTerminateMessage, + null); + } else { + callSTerminateMessage.setDirection("MT"); + io.rong.imlib.model.Message.ReceivedStatus + receivedStatus = + new io.rong.imlib.model.Message + .ReceivedStatus(0); + IMCenter.getInstance() + .insertIncomingMessage( + Conversation.ConversationType + .PRIVATE, + callProfile.getTargetId(), + senderId, + receivedStatus, + callSTerminateMessage, + null); + } + break; + case GROUP: + InformationNotificationMessage + informationNotificationMessage; + if (reason.equals( + RongCallCommon.CallDisconnectedReason + .NO_RESPONSE)) { + informationNotificationMessage = + InformationNotificationMessage.obtain( + mContext.getString( + R.string + .rc_voip_audio_no_response)); + } else { + informationNotificationMessage = + InformationNotificationMessage.obtain( + mContext.getString( + R.string + .rc_voip_audio_ended)); + } + + if (senderId.equals(callProfile.getSelfUserId())) { + IMCenter.getInstance() + .insertOutgoingMessage( + Conversation.ConversationType.GROUP, + callProfile.getTargetId(), + io.rong.imlib.model.Message + .SentStatus.SENT, + informationNotificationMessage, + null); + } else { + io.rong.imlib.model.Message.ReceivedStatus + receivedStatus = + new io.rong.imlib.model.Message + .ReceivedStatus(0); + IMCenter.getInstance() + .insertIncomingMessage( + Conversation.ConversationType.GROUP, + callProfile.getTargetId(), + senderId, + receivedStatus, + informationNotificationMessage, + null); + } + break; + default: + break; + } + } + Toast.makeText( + mContext, + mContext.getString( + R.string.rc_voip_call_terminalted), + Toast.LENGTH_SHORT) + .show(); + + if (wm != null && mView != null) { + wm.removeView(mView); + if (null != timer) { + timer.cancel(); + timer = null; + } + isShown = false; + mView = null; + mTime = 0; + } + setAudioMode(AudioManager.MODE_NORMAL); + AudioPlayManager.getInstance().setInVoipMode(false); + NotificationUtil.getInstance() + .clearNotification( + mContext, BaseCallActivity.CALL_NOTIFICATION_ID); + RongCallClient.getInstance() + .setVoIPCallListener(RongCallProxy.getInstance()); + } + + @Override + public void onRemoteUserLeft( + String userId, RongCallCommon.CallDisconnectedReason reason) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserLeft", + TAG); + } + + @Override + public void onMediaTypeChanged( + String userId, + RongCallCommon.CallMediaType mediaType, + SurfaceView video) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + RongCallClient.getInstance().getCallSession(), + "state|desc", + "onMediaTypeChanged", + TAG); + ImageView mediaIconV = + (ImageView) mView.findViewById(R.id.rc_voip_media_type); + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + mediaIconV.setImageResource(R.drawable.rc_voip_float_audio); + } else { + mediaIconV.setImageResource(R.drawable.rc_voip_float_video); + } + } + + @Override + public void onError(RongCallCommon.CallErrorCode errorCode) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + RongCallClient.getInstance().getCallSession(), + "code|state|desc", + errorCode.getValue(), + "onError", + TAG); + setAudioMode(AudioManager.MODE_NORMAL); + AudioPlayManager.getInstance().setInVoipMode(false); + } + + @Override + public void onCallConnected( + RongCallSession callInfo, SurfaceView localVideo) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + callInfo, + "state|desc", + "onCallConnected", + TAG); + if (CallKitUtils.isDial && isShown) { + CallFloatBoxView.showFloatBoxToCallTime(); + CallKitUtils.isDial = false; + } + AudioPlayManager.getInstance().setInVoipMode(true); + setAudioMode(AudioManager.MODE_IN_COMMUNICATION); + } + + @Override + public void onRemoteUserJoined( + String userId, + RongCallCommon.CallMediaType mediaType, + int userType, + SurfaceView remoteVideo) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserJoined", + TAG); + if (CallKitUtils.isDial && isShown) { + CallFloatBoxView.showFloatBoxToCallTime(); + CallKitUtils.isDial = false; + } + } + + @Override + public void onRemoteUserInvited( + String userId, RongCallCommon.CallMediaType mediaType) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserInvited", + TAG); + } + + @Override + public void onRemoteCameraDisabled(String userId, boolean disabled) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|disabled|desc", + userId, + "onRemoteCameraDisabled", + disabled, + TAG); + } + + @Override + public void onRemoteMicrophoneDisabled( + String userId, boolean disabled) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|disabled|desc", + userId, + "onRemoteMicrophoneDisabled", + disabled, + TAG); + } + + @Override + public void onNetworkReceiveLost(String userId, int lossRate) {} + + @Override + public void onNetworkSendLost(int lossRate, int delay) {} + + @Override + public void onFirstRemoteVideoFrame( + String userId, int height, int width) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onFirstRemoteVideoFrame", + TAG); + } + + @Override + public void onFirstRemoteAudioFrame(String userId) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onFirstRemoteAudioFrame", + TAG); + } + + @Override + public void onAudioLevelSend(String audioLevel) {} + + public void onRemoteUserPublishVideoStream( + String userId, + String streamId, + String tag, + SurfaceView surfaceView) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|streamId|desc", + userId, + "onRemoteUserPublishVideoStream", + streamId, + TAG); + } + + @Override + public void onAudioLevelReceive(HashMap audioLevel) {} + + public void onRemoteUserUnpublishVideoStream( + String userId, String streamId, String tag) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|streamId|desc", + userId, + "onRemoteUserUnpublishVideoStream", + streamId, + TAG); + } + }); + } + + /** * 调用showFloatBoxToCall 之后 调用该方法设置 */ + public static void showFloatBoxToCallTime() { + if (!isShown) { + return; + } + RongCallSession session = RongCallClient.getInstance().getCallSession(); + long activeTime = session != null ? session.getActiveTime() : 0; + mTime = activeTime == 0 ? 0 : (System.currentTimeMillis() - activeTime) / 1000; + // mView = LayoutInflater.from(context).inflate(R.layout.rc_voip_float_box, null); + // TextView timeV = (TextView) mView.findViewById(R.id.rc_time); + if (null != showFBCallTime) { + setupTime(showFBCallTime); + } + } + + public static void hideFloatBox() { + setExcludeFromRecents(mContext, false); + RongCallClient.getInstance().setVoIPCallListener(RongCallProxy.getInstance()); + if (isShown) { + if (mView != null) { + wm.removeView(mView); + } + mView = null; + if (remoteVideoContainer != null) { + wm.removeView(remoteVideoContainer); + } + remoteVideoContainer = null; + if (null != timer) { + timer.cancel(); + timer = null; + } + isShown = false; + mView = null; + mTime = 0; + mBundle = null; + showFBCallTime = null; + } + } + + public static Intent getResumeIntent() { + if (mBundle == null) { + return null; + } + mBundle.putBoolean("isDial", isDial); + RongCallClient.getInstance().setVoIPCallListener(RongCallProxy.getInstance()); + Intent intent = new Intent(mBundle.getString("action")); + intent.putExtra("floatbox", mBundle); + intent.setPackage(mContext.getPackageName()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("callAction", RongCallAction.ACTION_RESUME_CALL.getName()); + + return intent; + } + + public static void onClickToResume() { + // 当快速双击悬浮窗时,第一次点击之后会把mBundle置为空,第二次点击的时候出现NPE + if (mBundle == null) { + RLog.d(TAG, "onClickToResume mBundle is null"); + return; + } + if (activityResuming) { + return; + } + activityResuming = true; + boolean muteCamera = mBundle.getBoolean("muteCamera"); + if (mBundle.getInt("mediaType") == RongCallCommon.CallMediaType.VIDEO.getValue() + && !isDial + && !muteCamera) { + RLog.d(TAG, "onClickToResume setEnableLocalVideo(true)"); + RongCallClient.getInstance().setEnableLocalVideo(true); + } + mBundle.putBoolean("isDial", isDial); + RongCallClient.getInstance().setVoIPCallListener(RongCallProxy.getInstance()); + Intent intent = new Intent(mBundle.getString("action")); + intent.setPackage(mContext.getPackageName()); + intent.putExtra("floatbox", mBundle); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("callAction", RongCallAction.ACTION_RESUME_CALL.getName()); + + ActivityStartCheckUtils.getInstance() + .startActivity( + mContext, + intent, + BaseCallActivity.class.getSimpleName(), + new ActivityStartCheckUtils.ActivityStartResultCallback() { + @Override + public void onStartActivityResult(boolean isActivityStarted) { + activityResuming = false; + if (isActivityStarted) { + mBundle = null; + } else { + Toast.makeText( + mContext, + mContext.getString( + R.string + .rc_background_start_actvity_deny), + Toast.LENGTH_SHORT) + .show(); + } + } + }); + } + + private static void setupTime(final TextView timeView) { + final Handler handler = new Handler(Looper.getMainLooper()); + if (timer != null) { + timer.cancel(); + timer = null; + } + TimerTask task = + new TimerTask() { + @Override + public void run() { + handler.post( + new Runnable() { + @Override + public void run() { + mTime++; + if (timeView != null) { + if (mTime >= 3600) { + timeView.setText( + String.format( + Locale.ROOT, + "%d:%02d:%02d", + mTime / 3600, + (mTime % 3600) / 60, + (mTime % 60))); + timeView.setVisibility(View.VISIBLE); + } else { + timeView.setText( + String.format( + Locale.ROOT, + "%02d:%02d", + (mTime % 3600) / 60, + (mTime % 60))); + timeView.setVisibility(View.VISIBLE); + } + } + } + }); + } + }; + + timer = new Timer(); + timer.schedule(task, 0, 1000); + } + + private static void setAudioMode(int mode) { + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + if (audioManager != null) { + audioManager.setMode(mode); + } + } + + /** + * 设置app是否现在在最近列表中, + * + * @param appContext + * @param excluded + */ + private static void setExcludeFromRecents(Context appContext, boolean excluded) { + if (appContext == null) return; + ActivityManager manager = + (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + for (ActivityManager.AppTask task : manager.getAppTasks()) { + task.setExcludeFromRecents(excluded); + } + } + } + + public static boolean isCallFloatBoxShown() { + return isShown; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/CallForegroundService.java b/callkit/src/main/java/io/rong/callkit/CallForegroundService.java new file mode 100644 index 000000000..1189c3147 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/CallForegroundService.java @@ -0,0 +1,124 @@ +package io.rong.callkit; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.graphics.Color; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.IBinder; +import android.util.Log; +import androidx.annotation.Nullable; +import io.rong.callkit.util.CallRingingUtil; +import io.rong.push.notification.RongNotificationInterface; +import io.rong.push.notification.RongNotificationInterface.SoundType; + +public class CallForegroundService extends Service { + + private static final String TAG = "CallForegroundService"; + + @Override + public void onCreate() { + super.onCreate(); + } + + private void showNotification( + String title, String content, PendingIntent pendingIntent, int notificationId) { + Log.d(TAG, "showNotification: "); + NotificationManager notificationManager = + (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + String channelId = CallRingingUtil.getInstance().getNotificationChannelId(); + Notification notification = + RongNotificationInterface.createNotification( + getApplicationContext(), + title, + pendingIntent, + content, + SoundType.SILENT, + channelId); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + int importance = NotificationManager.IMPORTANCE_LOW; + String channelName = CallRingingUtil.getInstance().getNotificationChannelName(this); + NotificationChannel notificationChannel = + new NotificationChannel(channelId, channelName, importance); + notificationChannel.enableLights(false); + notificationChannel.setLightColor(Color.GREEN); + notificationChannel.enableVibration(false); + notificationChannel.setSound(null, null); + notificationManager.createNotificationChannel(notificationChannel); + } + notification.defaults = Notification.DEFAULT_ALL; + if (VERSION.SDK_INT >= VERSION_CODES.Q) { + startForeground( + notificationId, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE + | ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA); + } else { + startForeground(notificationId, notification); + } + Log.d(TAG, "showNotification: startForeground"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + stopForeground(true); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + String action = intent.getStringExtra("action"); + String title = intent.getStringExtra("title"); + String content = intent.getStringExtra("content"); + ResolveInfo info = + getPackageManager() + .resolveActivity(new Intent(action), PackageManager.MATCH_DEFAULT_ONLY); + ActivityInfo activityInfo; + if (info == null || (activityInfo = info.activityInfo) == null) { + Log.e(TAG, "onStartCommand: ResolveInfo is null! action=" + action); + return super.onStartCommand(intent, flags, startId); + } + Log.d(TAG, "onStartCommand: " + activityInfo.name); + Intent launched = new Intent(action); + launched.setClassName(activityInfo.packageName, activityInfo.name); + launched.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + launched.putExtra("floatbox", intent.getBundleExtra("floatbox")); + launched.putExtra("callAction", RongCallAction.ACTION_RESUME_CALL.getName()); + PendingIntent pendingIntent; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + pendingIntent = + PendingIntent.getActivity( + this, + 1000, + launched, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + pendingIntent = + PendingIntent.getActivity( + this, 1000, launched, PendingIntent.FLAG_UPDATE_CURRENT); + } + try { + showNotification(title, content, pendingIntent, BaseCallActivity.CALL_NOTIFICATION_ID); + } catch (Exception e) { + Log.e(TAG, "showNotification: ", e); + e.printStackTrace(); + } + return super.onStartCommand(intent, flags, startId); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/CallOptionMenu.java b/callkit/src/main/java/io/rong/callkit/CallOptionMenu.java new file mode 100644 index 000000000..fc5daa58e --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/CallOptionMenu.java @@ -0,0 +1,72 @@ +package io.rong.callkit; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.TextView; + +/** Created by mamingyang on 2018/3/19. */ +public class CallOptionMenu extends PopupWindow { + private View.OnClickListener onItemClickListener; + private TextView tv_right_text; + private LinearLayout layoutAdd; + private LinearLayout layoutWhiteBoard; + private LinearLayout layoutHandUp; + + public CallOptionMenu(Context context) { + super(context); + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View content = inflater.inflate(R.layout.rc_voip_pop_menu, null); + setContentView(content); + setWidth(LayoutParams.WRAP_CONTENT); + setHeight(LayoutParams.WRAP_CONTENT); + layoutAdd = (LinearLayout) content.findViewById(R.id.voipItemAdd); + layoutAdd.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) onItemClickListener.onClick(v); + } + }); + tv_right_text = content.findViewById(R.id.tv_right_text); + tv_right_text.setText(context.getString(R.string.rc_voip_add_member)); + layoutWhiteBoard = (LinearLayout) content.findViewById(R.id.voipItemWhiteboard); + layoutWhiteBoard.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) onItemClickListener.onClick(v); + } + }); + + layoutHandUp = (LinearLayout) content.findViewById(R.id.voipItemHandup); + layoutHandUp.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) onItemClickListener.onClick(v); + } + }); + + setBackgroundDrawable(context.getResources().getDrawable(R.drawable.rc_voip_menu_bg)); + setOutsideTouchable(true); + setFocusable(true); + } + + public void setOnItemClickListener(View.OnClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + public void setHandUpvisibility(boolean isSeen) { + if (layoutHandUp != null) { + if (!isSeen) layoutHandUp.setVisibility(View.GONE); + else { + layoutHandUp.setVisibility(View.VISIBLE); + } + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/CallPromptDialog.java b/callkit/src/main/java/io/rong/callkit/CallPromptDialog.java new file mode 100644 index 000000000..d20f5a47d --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/CallPromptDialog.java @@ -0,0 +1,177 @@ +package io.rong.callkit; + +import android.app.AlertDialog; +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.TextView; + +public class CallPromptDialog extends AlertDialog { + private Context mContext; + private OnPromptButtonClickedListener mPromptButtonClickedListener; + private String mTitle; + private String mPositiveButton; + private String mNegativeButton; + private String mMessage; + private int mLayoutResId; + private boolean disableCancel; + private int positiveTxtColor = 0; + private int negativeTxtColor = 0; + + public static CallPromptDialog newInstance( + final Context context, String title, String message) { + return new CallPromptDialog(context, title, message); + } + + public static CallPromptDialog newInstance(final Context context, String message) { + return new CallPromptDialog(context, message); + } + + public static CallPromptDialog newInstance( + final Context context, String title, String message, String positiveButton) { + return new CallPromptDialog(context, title, message, positiveButton); + } + + public static CallPromptDialog newInstance( + final Context context, + String title, + String message, + String positiveButton, + String negativeButton) { + return new CallPromptDialog(context, title, message, positiveButton, negativeButton); + } + + public CallPromptDialog( + final Context context, + String title, + String message, + String positiveButton, + String negativeButton) { + this(context, title, message, positiveButton); + this.mNegativeButton = negativeButton; + } + + public CallPromptDialog( + final Context context, String title, String message, String positiveButton) { + this(context, title, message); + mPositiveButton = positiveButton; + } + + public CallPromptDialog(final Context context, String title, String message) { + super(context); + mLayoutResId = R.layout.rc_voip_dialog_popup_prompt; + mContext = context; + mTitle = title; + mMessage = message; + } + + public CallPromptDialog(final Context context, String message) { + this(context, "", message); + } + + @Override + protected void onStart() { + super.onStart(); + LayoutInflater inflater = + (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final View view = inflater.inflate(mLayoutResId, null); + TextView txtViewTitle = (TextView) view.findViewById(io.rong.imkit.R.id.popup_dialog_title); + TextView txtViewMessage = + (TextView) view.findViewById(io.rong.imkit.R.id.popup_dialog_message); + TextView txtViewOK = + (TextView) view.findViewById(io.rong.imkit.R.id.popup_dialog_button_ok); + TextView txtViewCancel = + (TextView) view.findViewById(io.rong.imkit.R.id.popup_dialog_button_cancel); + if (disableCancel) txtViewCancel.setVisibility(View.GONE); + if (positiveTxtColor != 0) { + txtViewOK.setTextColor(positiveTxtColor); + } + if (negativeTxtColor != 0) { + txtViewCancel.setTextColor(negativeTxtColor); + } + txtViewOK.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mPromptButtonClickedListener != null) { + mPromptButtonClickedListener.onPositiveButtonClicked(); + } + dismiss(); + } + }); + txtViewCancel.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mPromptButtonClickedListener != null) { + mPromptButtonClickedListener.onNegativeButtonClicked(); + } + dismiss(); + } + }); + if (!TextUtils.isEmpty(mTitle)) { + txtViewTitle.setText(mTitle); + txtViewTitle.setVisibility(View.VISIBLE); + } + if (!TextUtils.isEmpty(mPositiveButton)) { + txtViewOK.setText(mPositiveButton); + } + + if (!TextUtils.isEmpty(mNegativeButton)) { + txtViewCancel.setText(mNegativeButton); + txtViewCancel.setVisibility(View.VISIBLE); + } + + txtViewMessage.setText(mMessage); + + setContentView(view); + WindowManager.LayoutParams layoutParams = getWindow().getAttributes(); + layoutParams.width = gePopupWidth(); + layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + getWindow().setAttributes(layoutParams); + } + + public void disableCancel() { + disableCancel = true; + } + + public CallPromptDialog setPromptButtonClickedListener( + OnPromptButtonClickedListener buttonClickedListener) { + this.mPromptButtonClickedListener = buttonClickedListener; + return this; + } + + public CallPromptDialog setLayoutRes(int resId) { + this.mLayoutResId = resId; + return this; + } + + public void setPositiveTextColor(int color) { + positiveTxtColor = color; + } + + public void setNegativeTextColor(int color) { + negativeTxtColor = color; + } + + public interface OnPromptButtonClickedListener { + void onPositiveButtonClicked(); + + void onNegativeButtonClicked(); + } + + private int gePopupWidth() { + int distanceToBorder = + (int) mContext.getResources().getDimension(R.dimen.callkit_dimen_size_40); + return getScreenWidth() - 2 * (distanceToBorder); + } + + private int getScreenWidth() { + return ((WindowManager) (mContext.getSystemService(Context.WINDOW_SERVICE))) + .getDefaultDisplay() + .getWidth(); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/CallSelectMemberActivity.java b/callkit/src/main/java/io/rong/callkit/CallSelectMemberActivity.java new file mode 100644 index 000000000..c275c3a4f --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/CallSelectMemberActivity.java @@ -0,0 +1,820 @@ +package io.rong.callkit; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Message; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; +import io.rong.callkit.util.CallKitSearchBarListener; +import io.rong.callkit.util.CallKitSearchBarView; +import io.rong.callkit.util.CallKitUtils; +import io.rong.callkit.util.CallSelectMemberSerializable; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import io.rong.common.RLog; +import io.rong.imkit.feature.mention.RongMentionManager; +import io.rong.imkit.userinfo.RongUserInfoManager; +import io.rong.imkit.userinfo.model.GroupUserInfo; +import io.rong.imlib.model.Conversation; +import io.rong.imlib.model.Group; +import io.rong.imlib.model.UserInfo; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class CallSelectMemberActivity extends BaseNoActionBarActivity + implements RongUserInfoManager.UserDataObserver { + private static final String TAG = "CallSelectMemberActivity"; + public static final String DISCONNECT_ACTION = "call_disconnect"; + ArrayList selectedMember; + private boolean isFirstDialog = true; + /** 已经选择的观察者列表 */ + private ArrayList observerMember; + + TextView txtvStart, callkit_conference_selected_number; + ListAdapter mAdapter; + ListView mList; + RongCallCommon.CallMediaType mMediaType; + private Conversation.ConversationType conversationType; + private EditText searchView; + private HashMap tempNickmembers = new HashMap<>(); + + private ArrayList searchMembers = new ArrayList<>(); + private ArrayList invitedMembers; + private ArrayList tempMembers = new ArrayList<>(); + + private ArrayList allObserver = null; // 保存当前通话中从多人音/视频传递过来的观察者列表 + + private String groupId; + private String callId; + private RelativeLayout rlSearchTop; + private RelativeLayout rlActionBar; + private ImageView ivBack; + private CallKitSearchBarView searchBar; + /** + * true:只能选择n个人同时进行音视频通话,>n选择无效; false:>n个人同时音视频通话之后,其他人视为观察者加入到本次通话中; n :NORMAL_VIDEO_NUMBER 和 + * NORMAL_AUDIO_NUMBER + */ + private boolean ctrlTag = true; + + private static final int NORMAL_VIDEO_NUMBER = 7; + private static final int NORMAL_AUDIO_NUMBER = 20; + private ArrayList userInfoArrayList = new ArrayList<>(); + /** 用于存储获取不到userInfo的用户在列表中的位置 */ + private HashMap userInfoIndex = new HashMap<>(); + + private final Object listLock = new Object(); + // + private Handler uiHandler = + new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (msg.what == 2) { + Bundle bundle = msg.getData(); + if (bundle != null) { + CallSelectMemberSerializable callSelectMemberSerializable = + (CallSelectMemberSerializable) + bundle.getSerializable( + CALLSELECTMEMBERSERIALIZABLE_KEY); + if (callSelectMemberSerializable != null) { + tempNickmembers = callSelectMemberSerializable.getHashMap(); + } + } + if (userInfoArrayList.isEmpty() + && invitedMembers != null + && invitedMembers.size() > 0) { + String tmpUserID = ""; + for (int i = 0; i < invitedMembers.size(); i++) { + tmpUserID = invitedMembers.get(i); + fillInUserInfoList(tmpUserID, i); + } + } + RLog.i(TAG, "setAdapter"); + mAdapter = new ListAdapter(userInfoArrayList, invitedMembers); + mList.setAdapter(mAdapter); + callkit_conference_selected_number.setText( + getString( + R.string.callkit_selected_contacts_count, + getTotalSelectedNumber())); + } + } + }; + + private void fillInUserInfoList(String userid, int index) { + synchronized (listLock) { + if (!TextUtils.isEmpty(userid)) { + if (getUserInfo(userid) == null) { + userInfoIndex.put(userid, index); + } + // 当获取到的userInfo为空时,记录下当前index,并给userInfoArrayList增加一个空元素,等到异步结果(onHeadsetPlugUpdate)回来后,根据index,把正确的数据插入回原来位置 + userInfoArrayList.add(getUserInfo(userid)); + + } else { + RLog.e(TAG, "uiHandler->userid null."); + } + } + } + + private Handler mHandler; + private static final String GROUPMEMBERSRESULT_KEY = "GROUPMEMBERSRESULTKEY"; + private static final String CALLSELECTMEMBERSERIALIZABLE_KEY = + "CALLSELECTMEMBERSERIALIZABLEKEY"; + + private BroadcastReceiver disconnectBroadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (TextUtils.equals(intent.getAction(), DISCONNECT_ACTION)) { + if (!isFinishing()) { + setActivityResult(true); + } + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow() + .setFlags( + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.activity_call_select_member2); + RongUserInfoManager.getInstance().addUserDataObserver(this); + + initTopBar(); + + selectedMember = new ArrayList<>(); + observerMember = new ArrayList<>(); + + Intent intent = getIntent(); + int type = intent.getIntExtra("mediaType", RongCallCommon.CallMediaType.VIDEO.getValue()); + mMediaType = RongCallCommon.CallMediaType.valueOf(type); + int conType = intent.getIntExtra("conversationType", 0); + conversationType = Conversation.ConversationType.setValue(conType); + invitedMembers = intent.getStringArrayListExtra("invitedMembers"); + groupId = intent.getStringExtra("groupId"); + callId = intent.getStringExtra("callId"); + allObserver = intent.getStringArrayListExtra("allObserver"); + + ArrayList list = intent.getStringArrayListExtra("allMembers"); + if (list != null) { + for (int i = 0; i < list.size(); i++) { + fillInUserInfoList(list.get(i), i); + } + } + HandlerThread handlerThread = new HandlerThread(TAG); + handlerThread.start(); + mHandler = + new Handler(handlerThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + String key = (String) msg.obj; + if (GROUPMEMBERSRESULT_KEY.equals(key)) { + Bundle bundle = msg.getData(); + HashMap hashMap = new HashMap<>(); + if (bundle != null) { + ArrayList arrayList = + bundle.getParcelableArrayList(GROUPMEMBERSRESULT_KEY); + Conversation.ConversationType conversationType = + Conversation.ConversationType.setValue( + bundle.getInt("conversationType")); + if (arrayList != null) { + RLog.i(TAG, "onGetGroupMembersResult : " + arrayList.size()); + UserInfo userInfo = null; + String userNickName = ""; + GroupUserInfo groupUserInfo = null; + /** 转换昵称** */ + for (int i = 0; i < arrayList.size(); i++) { + userInfo = arrayList.get(i); + if (userInfo != null + && !TextUtils.isEmpty(userInfo.getUserId())) { + if (conversationType != null + && conversationType.equals( + Conversation.ConversationType.GROUP)) { + groupUserInfo = + RongUserInfoManager.getInstance() + .getGroupUserInfo( + groupId, + userInfo.getUserId()); + if (groupUserInfo != null + && !TextUtils.isEmpty( + groupUserInfo.getNickname())) { + userNickName = groupUserInfo.getNickname(); + } + } + if (TextUtils.isEmpty(userNickName)) { + userNickName = userInfo.getName(); + } else { + userInfo.setName(userNickName); + } + hashMap.put(userInfo.getUserId(), userNickName); + userNickName = ""; + } + } + } + } + CallSelectMemberSerializable callSelectMemberSerializable = + new CallSelectMemberSerializable(hashMap); + Message message = new Message(); + message.what = 2; + Bundle bundle1 = new Bundle(); + bundle1.putSerializable( + CALLSELECTMEMBERSERIALIZABLE_KEY, callSelectMemberSerializable); + message.setData(bundle1); + uiHandler.sendMessage(message); + } + } + }; + + RongCallKit.GroupMembersProvider provider = RongCallKit.getGroupMemberProvider(); + if (TextUtils.isEmpty(groupId)) { + return; + } + if (provider != null) { + provider.getMemberList( + groupId, + new RongCallKit.OnGroupMembersResult() { + @Override + public void onGotMemberList(ArrayList members) { + for (int i = 0; i < members.size(); i++) { + fillInUserInfoList(members.get(i), i); + } + Message message = new Message(); + message.obj = GROUPMEMBERSRESULT_KEY; + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList( + GROUPMEMBERSRESULT_KEY, userInfoArrayList); + bundle.putInt("conversationType", conversationType.getValue()); + message.setData(bundle); + mHandler.sendMessage(message); + } + }); + } else { + if (RongMentionManager.getInstance().getGroupMembersProvider() != null) { + RongMentionManager.getInstance() + .getGroupMembersProvider() + .getGroupMembers( + groupId, + new RongMentionManager.IGroupMemberCallback() { + @Override + public void onGetGroupMembersResult(List userInfos) { + if (userInfos == null || userInfos.size() == 0) { + RLog.e( + TAG, + "onGetGroupMembersResult userInfos is null!"); + return; + } + userInfoArrayList.addAll(userInfos); + + Message message = new Message(); + message.obj = GROUPMEMBERSRESULT_KEY; + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList( + GROUPMEMBERSRESULT_KEY, userInfoArrayList); + bundle.putInt( + "conversationType", conversationType.getValue()); + message.setData(bundle); + mHandler.sendMessage(message); + } + }); + } + } + + callkit_conference_selected_number = + (TextView) findViewById(R.id.callkit_conference_selected_number); + txtvStart = (TextView) findViewById(R.id.callkit_btn_ok); + txtvStart.setText(getString(R.string.callkit_voip_ok)); + txtvStart.setEnabled(false); + txtvStart.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + setActivityResult(false); + } + }); + + mList = (ListView) findViewById(R.id.calkit_list_view_select_member); + mList.setOnItemClickListener(adapterOnItemClickListener); + rlSearchTop = (RelativeLayout) findViewById(R.id.rl_search_top); + ivBack = (ImageView) findViewById(R.id.iv_back); + searchBar = (CallKitSearchBarView) findViewById(R.id.search_bar); + if (CallKitUtils.findConfigurationLanguage(CallSelectMemberActivity.this, "ar")) { + rlSearchTop.setLayoutDirection(View.LAYOUT_DIRECTION_RTL); + searchBar.setLayoutDirection(View.LAYOUT_DIRECTION_RTL); + } + + ivBack.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + rlSearchTop.setVisibility(View.GONE); + rlActionBar.setVisibility(View.VISIBLE); + mAdapter.setAllMembers(userInfoArrayList); + mAdapter.notifyDataSetChanged(); + CallKitUtils.closeKeyBoard(CallSelectMemberActivity.this, null); + } + }); + searchBar.setSearchBarListener( + new CallKitSearchBarListener() { + @Override + public void onSearchStart(String content) { + if (userInfoArrayList != null && userInfoArrayList.size() > 0) { + startSearchMember(content); + } else { + Toast.makeText( + CallSelectMemberActivity.this, + getString(R.string.rc_voip_search_no_member), + Toast.LENGTH_SHORT) + .show(); + } + } + + @Override + public void onSoftSearchKeyClick() {} + + @Override + public void onClearButtonClick() { + if (invitedMembers != null) { + mAdapter = new ListAdapter(userInfoArrayList, invitedMembers); + mList.setAdapter(mAdapter); + mList.setOnItemClickListener(adapterOnItemClickListener); + } + } + }); + registerDisconnectBroadcastReceiver(); + } + + private void registerDisconnectBroadcastReceiver() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(DISCONNECT_ACTION); + registerReceiver( + disconnectBroadcastReceiver, + intentFilter, + this.getApplicationInfo().packageName + ".permission.RONG_ACCESS_RECEIVER", + null); + } + + private void startSearchMember(String searchEditContent) { + try { + searchMembers.clear(); + tempMembers.clear(); + if (!TextUtils.isEmpty(searchEditContent)) { + for (UserInfo info : userInfoArrayList) { + if (info != null && !TextUtils.isEmpty(info.getUserId())) { + if (((String) tempNickmembers.get(info.getUserId())) + .indexOf(searchEditContent) + != -1) { + tempMembers.add(info); + } + } + } + } else { + tempMembers.addAll(userInfoArrayList); + } + } catch (Exception e) { + e.printStackTrace(); + tempMembers.addAll(userInfoArrayList); + } + // closeKeyBoard(this, searchBar); + setData(); + } + + private void setData() { + if (null != tempMembers) { + ListAdapter adapter = new ListAdapter(tempMembers, invitedMembers); + mList.setAdapter(adapter); + mList.setOnItemClickListener(adapterOnItemClickListener); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + RongUserInfoManager.getInstance().removeUserDataObserver(this); + unregisterReceiver(disconnectBroadcastReceiver); + } + + @Override + public void onUserUpdate(UserInfo userInfo) { + if (mList != null && userInfo != null) { + if (userInfo.getUserId() == null + || !userInfoIndex.containsKey(userInfo.getUserId()) + || userInfoIndex.get(userInfo.getUserId()) == null) { + return; + } + synchronized (listLock) { + int index = userInfoIndex.get(userInfo.getUserId()); + if (index >= 0 && index < userInfoArrayList.size()) { + userInfoIndex.remove(userInfo.getUserId()); + userInfoArrayList.remove(index); + userInfoArrayList.add(index, userInfo); + } + Message message = new Message(); + message.obj = GROUPMEMBERSRESULT_KEY; + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(GROUPMEMBERSRESULT_KEY, userInfoArrayList); + bundle.putInt("conversationType", conversationType.getValue()); + message.setData(bundle); + mHandler.sendMessage(message); + } + } + } + + @Override + public void onGroupUpdate(Group group) {} + + @Override + public void onGroupUserInfoUpdate(GroupUserInfo groupUserInfo) {} + + class ListAdapter extends BaseAdapter { + List mallMembers; + List invitedMembers; + + public ListAdapter(List allMembers, List invitedMembers) { + this.mallMembers = allMembers; + this.invitedMembers = invitedMembers; + } + + public void setAllMembers(List allMembers) { + this.mallMembers = allMembers; + } + + @Override + public int getCount() { + return mallMembers.size(); + } + + @Override + public Object getItem(int position) { + return mallMembers.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(); + convertView = + LayoutInflater.from(CallSelectMemberActivity.this) + .inflate(R.layout.rc_voip_listitem_select_member, null); + holder.checkbox = (ImageView) convertView.findViewById(R.id.rc_checkbox); + holder.portrait = (ImageView) convertView.findViewById(R.id.rc_user_portrait); + holder.name = (TextView) convertView.findViewById(R.id.rc_user_name); + convertView.setTag(holder); + } + + UserInfo mUserInfo = mallMembers.get(position); + if (mUserInfo == null || TextUtils.isEmpty(mUserInfo.getUserId())) { + // userInfo为空前,把所有值都设置为默认 + holder = (ViewHolder) convertView.getTag(); + holder.checkbox.setImageResource(R.drawable.rc_voip_checkbox); + holder.checkbox.setClickable(false); + holder.checkbox.setEnabled(true); + holder.name.setText(""); + RongCallKit.getKitImageEngine() + .loadPortrait( + getApplicationContext(), + null, + R.drawable.rc_default_portrait, + holder.portrait); + // Glide.with(holder.portrait) + // .load(R.drawable.rc_default_portrait) + // .apply(RequestOptions.bitmapTransform(new CenterCrop())) + // .into(holder.portrait); + holder.checkbox.setTag(""); + return convertView; + } + holder = (ViewHolder) convertView.getTag(); + holder.checkbox.setTag(mUserInfo.getUserId()); + if (invitedMembers.contains(mUserInfo.getUserId())) { + holder.checkbox.setClickable(false); + holder.checkbox.setEnabled(false); + holder.checkbox.setImageResource(R.drawable.rc_voip_icon_checkbox_checked); + } else { + if (selectedMember.contains(mUserInfo.getUserId())) { + holder.checkbox.setImageResource(R.drawable.rc_voip_checkbox); + holder.checkbox.setSelected(true); + } else { + holder.checkbox.setImageResource(R.drawable.rc_voip_checkbox); + holder.checkbox.setSelected(false); + } + holder.checkbox.setClickable(false); + holder.checkbox.setEnabled(true); + } + + String displayName = ""; + if (conversationType != null + && conversationType.equals(Conversation.ConversationType.GROUP)) { + GroupUserInfo groupUserInfo = + RongUserInfoManager.getInstance() + .getGroupUserInfo(groupId, mUserInfo.getUserId()); + if (groupUserInfo != null && !TextUtils.isEmpty(groupUserInfo.getNickname())) { + displayName = groupUserInfo.getNickname(); + } + } + if (TextUtils.isEmpty(displayName)) { + holder.name.setText(mUserInfo.getName()); + } else { + holder.name.setText(displayName); + } + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + mUserInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + holder.portrait); + return convertView; + } + } + + /** + * 结束页面前设置值 + * + * @param val 是否是远端挂断,如果是则关闭该页面 + */ + private void setActivityResult(boolean val) { + RongCallSession profile = RongCallClient.getInstance().getCallSession(); + if (profile != null) { + // callid由多聊页面传入,如果先启动群组选择人员页面再启动多聊页面,不允许启动多聊页面 + if (null != profile.getCallId() && (!profile.getCallId().equals(callId))) { + String msg = + profile.getMediaType() == RongCallCommon.CallMediaType.AUDIO + ? getString(io.rong.callkit.R.string.rc_voip_call_audio_start_fail) + : getString(io.rong.callkit.R.string.rc_voip_call_video_start_fail); + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); + return; + } + } + + CallKitUtils.closeKeyBoard(CallSelectMemberActivity.this, null); + Intent intent = new Intent(); + intent.putExtra("remote_hangup", val); + intent.setPackage(getPackageName()); + intent.putStringArrayListExtra("invited", selectedMember); + intent.putStringArrayListExtra("observers", observerMember); + setResult(RESULT_OK, intent); + CallSelectMemberActivity.this.finish(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + } + + class ViewHolder { + + ImageView checkbox; + ImageView portrait; + TextView name; + } + + public void initTopBar() { + rlActionBar = (RelativeLayout) findViewById(R.id.rl_actionbar); + ImageButton backImgBtn = (ImageButton) findViewById(R.id.imgbtn_custom_nav_back); + backImgBtn.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + TextView titleTextView = (TextView) findViewById(R.id.tv_custom_nav_title); + titleTextView.setText(getString(R.string.rc_select_contact)); + titleTextView.setTextSize(18); + titleTextView.setTextColor(getResources().getColor(R.color.callkit_normal_text)); + + findViewById(R.id.imgbtn_custom_nav_option).setVisibility(View.VISIBLE); + ((ImageButton) findViewById(R.id.imgbtn_custom_nav_option)) + .setImageResource(R.drawable.callkit_ic_search_focused_x); + findViewById(R.id.imgbtn_custom_nav_option) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + rlSearchTop.setVisibility(View.VISIBLE); + rlActionBar.setVisibility(View.GONE); + } + }); + } + + private AdapterView.OnItemClickListener adapterOnItemClickListener = + new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + View v = view.findViewById(R.id.rc_checkbox); + String userId = (String) v.getTag(); + if (!TextUtils.isEmpty(userId) && !invitedMembers.contains(userId)) { + if (v.isSelected()) { + if (selectedMember.contains(userId)) { + selectedMember.remove(userId); + } + if (observerMember.contains(userId)) { + observerMember.remove(userId); + } + v.setSelected(false); + if (selectedMember.size() == 0 && observerMember.size() == 0) { + txtvStart.setEnabled(false); + txtvStart.setTextColor( + getResources() + .getColor( + R.color + .callkit_color_text_operation_disable)); + callkit_conference_selected_number.setTextColor( + getResources() + .getColor( + R.color + .callkit_color_text_operation_disable)); + } + if (searchMembers != null) { + callkit_conference_selected_number.setText( + getString( + R.string.callkit_selected_contacts_count, + getTotalSelectedNumber())); + } + return; + } + int totalNumber = getTotalSelectedNumber(); + boolean videoObserverState = + totalNumber + >= (mMediaType.equals(RongCallCommon.CallMediaType.AUDIO) + ? NORMAL_AUDIO_NUMBER + : NORMAL_VIDEO_NUMBER); + if (ctrlTag) { + if (videoObserverState) { + Toast.makeText( + CallSelectMemberActivity.this, + String.format( + getString( + mMediaType.equals( + RongCallCommon + .CallMediaType + .AUDIO) + ? R.string + .rc_voip_audio_numberofobservers + : R.string + .rc_voip_video_numberofobservers), + totalNumber), + Toast.LENGTH_SHORT) + .show(); + return; + } + if (selectedMember.contains(userId)) { + selectedMember.remove(userId); + } + v.setSelected(!v.isSelected()); // 1 false + if (v.isSelected()) { + selectedMember.add(userId); + } + if (selectedMember.size() > 0 || observerMember.size() > 0) { + txtvStart.setEnabled(true); + txtvStart.setTextColor( + getResources().getColor(R.color.rc_voip_check_enable)); + callkit_conference_selected_number.setTextColor( + getResources().getColor(R.color.rc_voip_check_enable)); + } else { + txtvStart.setEnabled(false); + txtvStart.setTextColor( + getResources() + .getColor( + R.color + .callkit_color_text_operation_disable)); + callkit_conference_selected_number.setTextColor( + getResources() + .getColor( + R.color + .callkit_color_text_operation_disable)); + } + } else { + if (videoObserverState && isFirstDialog) { + CallPromptDialog dialog = + CallPromptDialog.newInstance( + CallSelectMemberActivity.this, + getString(R.string.rc_voip_video_observer)); + dialog.setPromptButtonClickedListener( + new CallPromptDialog.OnPromptButtonClickedListener() { + @Override + public void onPositiveButtonClicked() {} + + @Override + public void onNegativeButtonClicked() {} + }); + dialog.disableCancel(); + dialog.setCancelable(false); + dialog.show(); + isFirstDialog = false; + } + v.setSelected(!v.isSelected()); // 1 false + if (videoObserverState) { + if (observerMember.contains(userId)) { + observerMember.remove(userId); + } + observerMember.add(userId); + } + if (selectedMember.contains(userId)) { + selectedMember.remove(userId); + } + if (v.isSelected()) { + selectedMember.add(userId); + } + if (selectedMember.size() > 0 || observerMember.size() > 0) { + txtvStart.setEnabled(true); + txtvStart.setTextColor( + getResources().getColor(R.color.rc_voip_check_enable)); + callkit_conference_selected_number.setTextColor( + getResources().getColor(R.color.rc_voip_check_enable)); + } else { + txtvStart.setEnabled(false); + txtvStart.setTextColor( + getResources() + .getColor( + R.color + .callkit_color_text_operation_disable)); + callkit_conference_selected_number.setTextColor( + getResources() + .getColor( + R.color + .callkit_color_text_operation_disable)); + } + } + } + if (searchMembers != null) { + callkit_conference_selected_number.setText( + getString( + R.string.callkit_selected_contacts_count, + getTotalSelectedNumber())); + } + } + }; + + /** + * 关闭软键盘 + * + * @param activity + * @param view + */ + private void closeKeyBoard(Activity activity, View view) { + IBinder token; + if (view == null || view.getWindowToken() == null) { + if (null == activity) { + return; + } + Window window = activity.getWindow(); + if (window == null) { + return; + } + View v = window.peekDecorView(); + if (v == null) { + return; + } + token = v.getWindowToken(); + } else { + token = view.getWindowToken(); + } + InputMethodManager imm = + (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(token, 0); + } + + private UserInfo getUserInfo(String userid) { + if (TextUtils.isEmpty(userid)) { + return null; + } + return RongUserInfoManager.getInstance().getUserInfo(userid); + } + + private int getTotalSelectedNumber() { + return (selectedMember == null ? 0 : selectedMember.size()) + + (invitedMembers == null ? 0 : invitedMembers.size()); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/CallUserGridView.java b/callkit/src/main/java/io/rong/callkit/CallUserGridView.java new file mode 100644 index 000000000..f071c4535 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/CallUserGridView.java @@ -0,0 +1,264 @@ +package io.rong.callkit; + +import android.content.Context; +import android.content.res.TypedArray; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CircleCrop; +import com.bumptech.glide.request.RequestOptions; +import io.rong.callkit.util.ICallScrollView; +import io.rong.imlib.model.UserInfo; +import java.util.ArrayList; +import java.util.List; + +/** Created by weiqinxiao on 16/3/25. coming 横向显示 多人语音_被叫 */ +public class CallUserGridView extends HorizontalScrollView implements ICallScrollView { + + private Context context; + private boolean enableTitle; + private LinearLayout linearLayout; + + private static int CHILDREN_PER_LINE = 5; + private static final int CHILDREN_SPACE = 13; + + private int portraitSize; + private boolean isHorizontal = true; + + @Override + public int getChildrenSpace() { + return CHILDREN_SPACE; + } + + public CallUserGridView(Context context) { + super(context); + init(context); + } + + public CallUserGridView(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CallUserGridView); + isHorizontal = a.getBoolean(R.styleable.CallUserGridView_CallGridViewOrientation, true); + CHILDREN_PER_LINE = + a.getInteger(R.styleable.CallUserGridView_CallGridViewChildrenPerLine, 4); + init(context); + a.recycle(); + } + + private void init(Context context) { + this.context = context; + linearLayout = new LinearLayout(context); + linearLayout.setLayoutParams( + new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + // linearLayout.setOrientation(LinearLayout.HORIZONTAL); + addView(linearLayout); + } + + public int dip2pix(int dipValue) { + float scale = getResources().getDisplayMetrics().density; + return (int) (dipValue * scale + 0.5f); + } + + public int getScreenWidth() { + return getResources().getDisplayMetrics().widthPixels; + } + + public void setChildPortraitSize(int size) { + portraitSize = size; + } + + public void enableShowState(boolean enable) { + enableTitle = enable; + } + + public void addChild(String childId, UserInfo userInfo) { + addChild(childId, userInfo, null); + } + + public void addChild(String childId, UserInfo userInfo, String state) { + int containerCount = linearLayout.getChildCount(); + LinearLayout lastContainer = null; + int i; + for (i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + if (container.getChildCount() < CHILDREN_PER_LINE) { + lastContainer = container; + break; + } + } + if (lastContainer == null) { + lastContainer = new LinearLayout(context); + lastContainer.setLayoutParams( + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + lastContainer.setGravity(Gravity.CENTER_HORIZONTAL); + lastContainer.setPadding(0, dip2pix(CHILDREN_SPACE), 0, 0); + linearLayout.addView(lastContainer); + } + + LinearLayout child = + (LinearLayout) + LayoutInflater.from(context).inflate(R.layout.rc_voip_user_info, null); + child.setLayoutParams( + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + if (containerCount == 0) { + child.setPadding(dip2pix(15), 0, dip2pix(CHILDREN_SPACE), 0); + } else { + child.setPadding(0, 0, dip2pix(CHILDREN_SPACE), 0); + } + child.setTag(childId); + if (portraitSize > 0) { + child.findViewById(R.id.rc_user_portrait_layout) + .setLayoutParams(new LinearLayout.LayoutParams(portraitSize, portraitSize)); + } + ImageView imageView = (ImageView) child.findViewById(R.id.rc_user_portrait); + TextView name = (TextView) child.findViewById(R.id.rc_user_name); + name.setVisibility(enableTitle ? VISIBLE : GONE); + TextView stateV = (TextView) child.findViewById(R.id.rc_voip_member_state); + stateV.setVisibility(enableTitle ? VISIBLE : GONE); + if (state != null) { + stateV.setText(state); + } else { + stateV.setVisibility(GONE); + } + + if (userInfo != null) { + RongCallKit.getKitImageEngine() + .loadPortrait( + this.getContext(), + userInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + imageView); + name.setText(userInfo.getName() == null ? userInfo.getUserId() : userInfo.getName()); + } else { + name.setText(childId); + } + lastContainer.addView(child); + } + + @Override + public void setScrollViewOverScrollMode(int mode) { + this.setOverScrollMode(mode); + } + + @Override + public void removeAllChild() { + linearLayout.removeAllViews(); + } + + public void removeChild(String childId) { + int containerCount = linearLayout.getChildCount(); + + LinearLayout lastContainer = null; + List containerList = new ArrayList<>(); + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + containerList.add(container); + } + for (LinearLayout resultContainer : containerList) { + if (lastContainer == null) { + LinearLayout child = (LinearLayout) resultContainer.findViewWithTag(childId); + if (child != null) { + resultContainer.removeView(child); + if (resultContainer.getChildCount() == 0) { + linearLayout.removeView(resultContainer); + break; + } else { + lastContainer = resultContainer; + } + } + } else { + View view = resultContainer.getChildAt(0); + resultContainer.removeView(view); + lastContainer.addView(view); + if (resultContainer.getChildCount() == 0) { + linearLayout.removeView(resultContainer); + break; + } else { + lastContainer = resultContainer; + } + } + } + } + + public View findChildById(String childId) { + int containerCount = linearLayout.getChildCount(); + + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + LinearLayout child = (LinearLayout) container.findViewWithTag(childId); + if (child != null) { + return child; + } + } + return null; + } + + public void updateChildInfo(String childId, UserInfo userInfo) { + int containerCount = linearLayout.getChildCount(); + + LinearLayout lastContainer = null; + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + LinearLayout child = (LinearLayout) container.findViewWithTag(childId); + if (child != null) { + ImageView imageView = (ImageView) child.findViewById(R.id.rc_user_portrait); + Glide.with(this) + .load(userInfo.getPortraitUri()) + .placeholder(R.drawable.rc_default_portrait) + .apply(RequestOptions.bitmapTransform(new CircleCrop())) + .into(imageView); + if (enableTitle) { + TextView textView = (TextView) child.findViewById(R.id.rc_user_name); + textView.setLines(1); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setText(userInfo.getName()); + } + } + } + } + + public void updateChildState(String childId, String state) { + int containerCount = linearLayout.getChildCount(); + + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + LinearLayout child = (LinearLayout) container.findViewWithTag(childId); + if (child != null) { + TextView textView = (TextView) child.findViewById(R.id.rc_voip_member_state); + textView.setText(state); + } + } + } + + public void updateChildState(String childId, boolean visible) { + int containerCount = linearLayout.getChildCount(); + + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + LinearLayout child = (LinearLayout) container.findViewWithTag(childId); + if (child != null) { + TextView textView = (TextView) child.findViewById(R.id.rc_voip_member_state); + textView.setVisibility(visible ? VISIBLE : GONE); + } + } + } + + @Override + public View getChildAtIndex(int index) { + return linearLayout.getChildAt(index); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/ContainerLayout.java b/callkit/src/main/java/io/rong/callkit/ContainerLayout.java new file mode 100644 index 000000000..ba0f6c34d --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/ContainerLayout.java @@ -0,0 +1,111 @@ +package io.rong.callkit; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.SurfaceView; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.RelativeLayout; +import androidx.annotation.NonNull; +import cn.rongcloud.rtc.api.stream.RCRTCVideoView; +import cn.rongcloud.rtc.utils.FinLog; + +/** Created by Administrator on 2017/3/30. */ +public class ContainerLayout extends RelativeLayout { + private final String TAG = ContainerLayout.class.getSimpleName(); + private Context context; + private static boolean isNeedFillScrren = true; + SurfaceView currentView; + + public ContainerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + this.context = context; + } + + public void addView(final SurfaceView videoView) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + this.screenWidth = wm.getDefaultDisplay().getWidth(); + this.screenHeight = wm.getDefaultDisplay().getHeight(); + FinLog.d( + TAG, + "---xx-- add view " + + videoView.toString() + + " Height: " + + ((RCRTCVideoView) videoView).rotatedFrameHeight + + " Width: " + + ((RCRTCVideoView) videoView).rotatedFrameWidth); + super.addView(videoView, getBigContainerParams((RCRTCVideoView) videoView)); + currentView = videoView; + ((RCRTCVideoView) videoView) + .setOnSizeChangedListener( + new RCRTCVideoView.OnSizeChangedListener() { + @Override + public void onChanged(RCRTCVideoView.Size size) { + try { + ContainerLayout.this.removeAllViews(); + FinLog.d( + TAG, + "---xx-- change view " + + videoView.toString() + + " Height: " + + ((RCRTCVideoView) videoView) + .rotatedFrameHeight + + " Width: " + + ((RCRTCVideoView) videoView) + .rotatedFrameWidth); + ContainerLayout.this.addView( + videoView, + getBigContainerParams((RCRTCVideoView) videoView)); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @NonNull + private LayoutParams getBigContainerParams(RCRTCVideoView videoView) { + LayoutParams layoutParams = null; + if (!isNeedFillScrren) { + if (screenHeight > screenWidth) { // V + int layoutParamsHeight = + (videoView.rotatedFrameHeight == 0 || videoView.rotatedFrameWidth == 0) + ? ViewGroup.LayoutParams.WRAP_CONTENT + : screenWidth + * videoView.rotatedFrameHeight + / videoView.rotatedFrameWidth; + layoutParams = new LayoutParams(screenWidth, layoutParamsHeight); + } else { + int layoutParamsWidth = + (videoView.rotatedFrameWidth == 0 || videoView.rotatedFrameHeight == 0) + ? ViewGroup.LayoutParams.WRAP_CONTENT + : (screenWidth + * videoView.rotatedFrameWidth + / videoView.rotatedFrameHeight + > screenWidth + ? screenWidth + : screenHeight + * videoView.rotatedFrameWidth + / videoView.rotatedFrameHeight); + layoutParams = new LayoutParams(layoutParamsWidth, screenHeight); + } + } else { + layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); + return layoutParams; + } + + public void setIsNeedFillScrren(boolean isNeed) { + isNeedFillScrren = isNeed; + } + + @Override + public void removeAllViews() { + if (currentView != null) ((RCRTCVideoView) currentView).setOnSizeChangedListener(null); + super.removeAllViews(); + } + + private int screenWidth; + private int screenHeight; +} diff --git a/callkit/src/main/java/io/rong/callkit/GlideCallKitImageEngine.java b/callkit/src/main/java/io/rong/callkit/GlideCallKitImageEngine.java new file mode 100644 index 000000000..dfc991f5c --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/GlideCallKitImageEngine.java @@ -0,0 +1,28 @@ +package io.rong.callkit; + +import android.content.Context; +import android.net.Uri; +import android.widget.ImageView; +import androidx.annotation.DrawableRes; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; + +public class GlideCallKitImageEngine { + /** + * 设置头像加载样式 + * + * @param context + * @param url + * @param replaceRes + * @param imageView + */ + public void loadPortrait( + Context context, Uri url, @DrawableRes int replaceRes, ImageView imageView) { + Glide.with(context) + .load(url) + .error(replaceRes) + .placeholder(replaceRes) + .apply(RequestOptions.circleCropTransform()) + .into(imageView); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/MultiAudioCallActivity.java b/callkit/src/main/java/io/rong/callkit/MultiAudioCallActivity.java new file mode 100644 index 000000000..e10b4d557 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/MultiAudioCallActivity.java @@ -0,0 +1,989 @@ +package io.rong.callkit; + +import static io.rong.callkit.CallSelectMemberActivity.DISCONNECT_ACTION; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; +import cn.rongcloud.rtc.api.RCRTCEngine; +import cn.rongcloud.rtc.audioroute.RCAudioRouteType; +import cn.rongcloud.rtc.utils.FinLog; +import io.rong.callkit.util.BluetoothUtil; +import io.rong.callkit.util.CallKitUtils; +import io.rong.callkit.util.CallVerticalScrollView; +import io.rong.callkit.util.DefaultPushConfig; +import io.rong.callkit.util.HeadsetInfo; +import io.rong.callkit.util.ICallScrollView; +import io.rong.callkit.util.RingingMode; +import io.rong.callkit.util.RongCallPermissionUtil; +import io.rong.callkit.util.SPUtils; +import io.rong.calllib.CallUserProfile; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import io.rong.calllib.message.MultiCallEndMessage; +import io.rong.common.RLog; +import io.rong.imkit.IMCenter; +import io.rong.imkit.userinfo.RongUserInfoManager; +import io.rong.imlib.IRongCoreCallback; +import io.rong.imlib.IRongCoreEnum; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.discussion.base.RongDiscussionClient; +import io.rong.imlib.discussion.model.Discussion; +import io.rong.imlib.model.Conversation; +import io.rong.imlib.model.Group; +import io.rong.imlib.model.UserInfo; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +/** 如何实现不基于于群组的voip */ +public class MultiAudioCallActivity extends BaseCallActivity { + private static final String TAG = "VoIPMultiAudioCallActivity"; + LinearLayout audioContainer; + ICallScrollView memberContainer; + + RelativeLayout incomingLayout; + RelativeLayout outgoingLayout; + RelativeLayout outgoingController; + RelativeLayout incomingController; + RongCallAction callAction; + RongCallSession callSession; + + boolean shouldShowFloat = true; + boolean startForCheckPermissions = false; + private boolean handFree = false; + private boolean muted = false; + private final String KEY_MUTED = "muted"; + private final String KEY_HAND_FREE = "handFree"; + + @Override + @TargetApi(23) + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null && RongCallClient.getInstance() == null) { + // 音视频请求权限时,用户在设置页面取消权限,导致应用重启,退出当前activity. + finish(); + return; + } + setContentView(R.layout.rc_voip_ac_muti_audio); + audioContainer = (LinearLayout) findViewById(R.id.rc_voip_container); + incomingLayout = + (RelativeLayout) + LayoutInflater.from(this) + .inflate(R.layout.rc_voip_item_incoming_maudio, null); + TextView tv_invite_incoming_audio = + incomingLayout.findViewById(R.id.tv_invite_incoming_audio); + CallKitUtils.textViewShadowLayer(tv_invite_incoming_audio, MultiAudioCallActivity.this); + + outgoingLayout = + (RelativeLayout) + LayoutInflater.from(this) + .inflate(R.layout.rc_voip_item_outgoing_maudio, null); + TextView rc_voip_remind = incomingLayout.findViewById(R.id.rc_voip_remind); + CallKitUtils.textViewShadowLayer(rc_voip_remind, MultiAudioCallActivity.this); + + outgoingController = + (RelativeLayout) + LayoutInflater.from(this) + .inflate( + R.layout.rc_voip_call_bottom_connected_button_layout, null); + ImageView button = outgoingController.findViewById(R.id.rc_voip_call_mute_btn); + button.setEnabled(true); + incomingController = + (RelativeLayout) + LayoutInflater.from(this) + .inflate(R.layout.rc_voip_call_bottom_incoming_button_layout, null); + + startForCheckPermissions = getIntent().getBooleanExtra("checkPermissions", false); + if (requestCallPermissions( + RongCallCommon.CallMediaType.AUDIO, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)) { + initView(); + } + } + + @Override + protected void onNewIntent(Intent intent) { + startForCheckPermissions = getIntent().getBooleanExtra("checkPermissions", false); + super.onNewIntent(intent); + if (requestCallPermissions( + RongCallCommon.CallMediaType.AUDIO, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)) { + initView(); + } + } + + @TargetApi(23) + @Override + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + switch (requestCode) { + case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: + if (RongCallPermissionUtil.checkAudioCallNeedPermission(this)) { + if (startForCheckPermissions) { + startForCheckPermissions = false; + RongCallClient.getInstance().onPermissionGranted(); + } else { + initView(); + } + } else { + if (startForCheckPermissions) { + startForCheckPermissions = false; + Toast.makeText( + this, + getString(R.string.rc_voip_relevant_permissions), + Toast.LENGTH_SHORT) + .show(); + RongCallClient.getInstance().onPermissionDenied(); + } else { + finish(); + } + } + break; + + default: + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + @Override + public void onRestoreFloatBox(Bundle bundle) { + super.onRestoreFloatBox(bundle); + if (bundle != null) { + handFree = bundle.getBoolean(KEY_HAND_FREE); + muted = bundle.getBoolean(KEY_MUTED); + audioContainer.removeAllViews(); + audioContainer.addView(outgoingLayout); + String str = (String) SPUtils.get(MultiAudioCallActivity.this, "ICallScrollView", ""); + + FrameLayout controller = + (FrameLayout) audioContainer.findViewById(R.id.rc_voip_control_layout); + controller.removeAllViews(); + controller.addView(outgoingController); + callSession = RongCallClient.getInstance().getCallSession(); + if (callSession == null) { + setShouldShowFloat(false); + finish(); + return; + } + List participantProfiles = callSession.getParticipantProfileList(); + + /** 初始化列表* */ + if (str.equals("CallVerticalScrollView")) { + memberContainer = + (CallVerticalScrollView) + audioContainer.findViewById(R.id.rc_voip_members_container); + } else { + memberContainer = + (CallUserGridView) + audioContainer.findViewById( + R.id.rc_voip_members_container_gridView); + } + memberContainer.enableShowState(true); + LinearLayout linear_scrollviewTag = + (LinearLayout) outgoingLayout.findViewById(R.id.linear_scrollviewTag); + if (participantProfiles.size() > 4) { + ViewGroup.LayoutParams params = linear_scrollviewTag.getLayoutParams(); + params.height = CallKitUtils.dp2px(200, MultiAudioCallActivity.this); + linear_scrollviewTag.setLayoutParams(params); + } + // 添加数据 + for (CallUserProfile item : participantProfiles) { + if (!item.getUserId().equals(callSession.getSelfUserId()) + && memberContainer.findChildById(item.getUserId()) == null) { + if (item.getCallStatus().equals(RongCallCommon.CallStatus.CONNECTED)) { + memberContainer.addChild( + item.getUserId(), + RongUserInfoManager.getInstance().getUserInfo(item.getUserId())); + memberContainer.updateChildState(item.getUserId(), false); + } else { + String state = getString(R.string.rc_voip_call_connecting); + memberContainer.addChild( + item.getUserId(), + RongUserInfoManager.getInstance().getUserInfo(item.getUserId()), + state); + } + } + } + if (!(boolean) bundle.get("isDial")) { + onCallConnected(callSession, null); // 接听 + } else { + onCallOutgoing(callSession, null); + } + } + } + + void initView() { + Intent intent = getIntent(); + callAction = RongCallAction.valueOf(intent.getStringExtra("callAction")); + if (callAction == null || callAction.equals(RongCallAction.ACTION_RESUME_CALL)) { + RelativeLayout relativeLayout = + (RelativeLayout) + outgoingLayout.findViewById(R.id.reltive_voip_outgoing_audio_title); + relativeLayout.setVisibility(View.VISIBLE); + return; + } + ArrayList invitedList = new ArrayList<>(); + if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + // 正常在收到呼叫后,RongCallClient 和 CallSession均不会为空 + if (RongCallClient.getInstance() == null + || RongCallClient.getInstance().getCallSession() == null) { + // 如果为空 表示通话已经结束 但依然启动了本页面,这样会导致页面无法销毁问题 + // 所以 需要在这里 finish 结束当前页面 推荐开发者在结束当前页面前跳转至APP主页或者其他页面 + RLog.e( + TAG, + "MultiAudioCallActivity#initView()->RongCallClient or CallSession is empty---->finish()"); + finish(); + return; + } + audioContainer.removeAllViews(); + callSession = RongCallClient.getInstance().getCallSession(); + UserInfo userInfo = + RongUserInfoManager.getInstance().getUserInfo(callSession.getCallerUserId()); + setTopContainerUserView(callSession.getCallerUserId()); + + audioContainer.addView(incomingLayout); + memberContainer = + (CallUserGridView) + audioContainer.findViewById(R.id.rc_voip_members_container_gridView); + SPUtils.put(MultiAudioCallActivity.this, "ICallScrollView", "CallUserGridView"); + memberContainer.removeAllChild(); + memberContainer.setChildPortraitSize(memberContainer.dip2pix(55)); + List list = callSession.getParticipantProfileList(); + for (CallUserProfile profile : list) { + if (!profile.getUserId().equals(callSession.getCallerUserId())) { + invitedList.add(profile.getUserId()); + userInfo = RongUserInfoManager.getInstance().getUserInfo(profile.getUserId()); + memberContainer.addChild(profile.getUserId(), userInfo); + } + } + FrameLayout controller = + (FrameLayout) audioContainer.findViewById(R.id.rc_voip_control_layout); + controller.removeAllViews(); + controller.addView(incomingController); + + ImageView iv_answerBtn = + (ImageView) incomingController.findViewById(R.id.rc_voip_call_answer_btn); + iv_answerBtn.setBackground( + CallKitUtils.BackgroundDrawable( + R.drawable.rc_voip_audio_answer_selector_new, + MultiAudioCallActivity.this)); + + onIncomingCallRinging(callSession); + } else if (callAction.equals(RongCallAction.ACTION_OUTGOING_CALL)) { + audioContainer.removeAllViews(); + Conversation.ConversationType conversationType = + Conversation.ConversationType.valueOf( + intent.getStringExtra("conversationType").toUpperCase(Locale.US)); + String targetId = intent.getStringExtra("targetId"); + ArrayList userIds = intent.getStringArrayListExtra("invitedUsers"); + ArrayList observers = intent.getStringArrayListExtra("observers"); + audioContainer.addView(outgoingLayout); + + LinearLayout linear_scrollviewTag = + (LinearLayout) outgoingLayout.findViewById(R.id.linear_scrollviewTag); + + // 多人语音主叫方顶部布局 + RelativeLayout relativeLayout = + (RelativeLayout) + outgoingLayout.findViewById(R.id.reltive_voip_outgoing_audio_title); + relativeLayout.setVisibility(View.VISIBLE); + + memberContainer = + (CallVerticalScrollView) + audioContainer.findViewById(R.id.rc_voip_members_container); + SPUtils.put(MultiAudioCallActivity.this, "ICallScrollView", "CallVerticalScrollView"); + memberContainer.enableShowState(true); + FrameLayout controller = + (FrameLayout) audioContainer.findViewById(R.id.rc_voip_control_layout); + controller.removeAllViews(); + controller.addView(outgoingController); + + ImageView iv_answerBtn = + (ImageView) incomingController.findViewById(R.id.rc_voip_call_answer_btn); + iv_answerBtn.setBackground( + CallKitUtils.BackgroundDrawable( + R.drawable.rc_voip_audio_answer_selector_new, + MultiAudioCallActivity.this)); + + ImageView button = outgoingController.findViewById(R.id.rc_voip_call_mute_btn); + button.setEnabled(true); + for (int i = 0; i < userIds.size(); i++) { + if (!userIds.get(i).equals(RongIMClient.getInstance().getCurrentUserId())) { + invitedList.add(userIds.get(i)); + UserInfo userInfo = + RongUserInfoManager.getInstance().getUserInfo(userIds.get(i)); + memberContainer.addChild( + userIds.get(i), userInfo, getString(R.string.rc_voip_call_connecting)); + } + } + // + if (userIds.size() > 4) { + ViewGroup.LayoutParams params = linear_scrollviewTag.getLayoutParams(); + params.height = CallKitUtils.dp2px(200, MultiAudioCallActivity.this); + linear_scrollviewTag.setLayoutParams(params); + } + + String groupName = ""; + Group group = RongUserInfoManager.getInstance().getGroupInfo(targetId); + if (group != null && !TextUtils.isEmpty(group.getName())) { + groupName = group.getName(); + } + RongCallClient.getInstance() + .setPushConfig( + DefaultPushConfig.getInviteConfig(this, true, false, groupName), + DefaultPushConfig.getHangupConfig(this, false, groupName)); + + RongCallClient.getInstance() + .startCall( + conversationType, + targetId, + invitedList, + observers, + RongCallCommon.CallMediaType.AUDIO, + "multi"); + } + memberContainer.setScrollViewOverScrollMode(View.OVER_SCROLL_NEVER); + createPickupDetector(); + } + + @Override + protected void onPause() { + if (pickupDetector != null) { + pickupDetector.unRegister(); + } + super.onPause(); + } + + @Override + protected void onResume() { + if (pickupDetector == null) createPickupDetector(); + if (pickupDetector != null) { + pickupDetector.register(this); + } + super.onResume(); + } + + public void onHangupBtnClick(View view) { + // unRegisterHeadsetplugReceiver(); + if (callSession == null || isFinishing) { + FinLog.e( + TAG, + "_挂断多人语音出错 callSession=" + + (callSession == null) + + ",isFinishing=" + + isFinishing); + return; + } + RongCallClient.getInstance().hangUpCall(callSession.getCallId()); + } + + public void onReceiveBtnClick(View view) { + if (callSession == null || isFinishing) { + FinLog.e( + TAG, + "_接听多人语音出错 callSession=" + + (callSession == null) + + ",isFinishing=" + + isFinishing); + return; + } + RongCallClient.getInstance().acceptCall(callSession.getCallId()); + } + + @Override + protected void onAddMember(List newMemberIds) { + if (newMemberIds == null || newMemberIds.isEmpty()) { + return; + } + ArrayList added = new ArrayList<>(); + List participants = new ArrayList<>(); + List list = + RongCallClient.getInstance().getCallSession().getParticipantProfileList(); + for (CallUserProfile profile : list) { + participants.add(profile.getUserId()); + } + for (String id : newMemberIds) { + if (participants.contains(id)) { + continue; + } else { + added.add(id); + } + } + if (added.isEmpty()) { + return; + } + + RongCallClient.getInstance().addParticipants(callSession.getCallId(), added, null); + } + + @Override + public void onRemoteUserRinging(String userId) {} + + @Override + public void onCallOutgoing(RongCallSession callSession, SurfaceView localVideo) { + super.onCallOutgoing(callSession, localVideo); + this.callSession = callSession; + callRinging(RingingMode.Outgoing); + } + + @Override + public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) { + super.onRemoteUserInvited(userId, mediaType); + memberContainer.addChild( + userId, + RongUserInfoManager.getInstance().getUserInfo(userId), + getString(R.string.rc_voip_call_connecting)); + } + + @Override + public void onRemoteUserJoined( + String userId, + RongCallCommon.CallMediaType mediaType, + int userType, + SurfaceView remoteVideo) { + View view = memberContainer.findChildById(userId); + if (view != null) { + memberContainer.updateChildState(userId, false); + } else { + memberContainer.addChild(userId, RongUserInfoManager.getInstance().getUserInfo(userId)); + } + } + + @Override + public void onRemoteUserLeft( + final String userId, RongCallCommon.CallDisconnectedReason reason) { + if (isTopContainerUserExit(userId)) { + return; + } + String text = null; + switch (reason) { + case REMOTE_BUSY_LINE: + text = getString(R.string.rc_voip_mt_busy_toast); + break; + case REMOTE_CANCEL: + text = getString(R.string.rc_voip_mt_cancel); + break; + case REMOTE_REJECT: + text = getString(R.string.rc_voip_mt_reject); + break; + case NO_RESPONSE: + text = getString(R.string.rc_voip_mt_no_response); + break; + case NETWORK_ERROR: + case HANGUP: + case REMOTE_HANGUP: + break; + } + if (text != null && memberContainer != null) { + memberContainer.updateChildState(userId, text); + } + if (memberContainer != null) { + memberContainer.removeChild(userId); + } + } + + private boolean isTopContainerUserExit(String userId) { + if (CallKitUtils.callConnected) { + return false; + } + if (callSession != null + && TextUtils.equals(callSession.getInviterUserId(), userId) + && memberContainer != null) { + if (((LinearLayout) memberContainer.getChildAtIndex(0)) != null + && ((LinearLayout) memberContainer.getChildAtIndex(0)).getChildAt(0) != null + && ((LinearLayout) memberContainer.getChildAtIndex(0)).getChildAt(0).getTag() + != null) { + LinearLayout firstView = + (LinearLayout) + ((LinearLayout) memberContainer.getChildAtIndex(0)).getChildAt(0); + String firstUserId = (String) firstView.getTag(); + setTopContainerUserView(firstUserId); + + memberContainer.removeChild(firstUserId); + + LinearLayout linearLayout = + (LinearLayout) + ((LinearLayout) memberContainer.getChildAtIndex(0)).getChildAt(0); + linearLayout.setPadding( + memberContainer.dip2pix(15), + 0, + memberContainer.dip2pix(memberContainer.getChildrenSpace()), + 0); + linearLayout.requestLayout(); + return true; + } + } + return false; + } + + private void setTopContainerUserView(String userId) { + TextView name = (TextView) incomingLayout.findViewById(R.id.rc_user_name); + ImageView userPortrait = + (ImageView) incomingLayout.findViewById(R.id.rc_voip_user_portrait); + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(userId); + if (userInfo != null && userInfo.getName() != null) { + name.setText(userInfo.getName()); + } else { + name.setText(userId); + } + if (userInfo != null && userInfo.getPortraitUri() != null) { + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + userInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + userPortrait); + userPortrait.setVisibility(View.VISIBLE); + } + name.setTag(userId + "callerName"); + } + + /** + * 已建立通话。 通话接通时,通过回调 onCallConnected 通知当前 call 的详细信息。 + * + * @param callSession 通话实体。 + * @param localVideo 本地 camera 信息。 + */ + @Override + public void onCallConnected(final RongCallSession callSession, SurfaceView localVideo) { + super.onCallConnected(callSession, localVideo); + RongCallClient.getInstance().setEnableLocalVideo(false); + this.callSession = callSession; + if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + audioContainer.removeAllViews(); + FrameLayout controller = + (FrameLayout) outgoingLayout.findViewById(R.id.rc_voip_control_layout); + controller.addView(outgoingController); + audioContainer.addView(outgoingLayout); + SPUtils.put(MultiAudioCallActivity.this, "ICallScrollView", "CallVerticalScrollView"); + // 多人语音通话中竖向滑动 + memberContainer = + (CallVerticalScrollView) + outgoingLayout.findViewById(R.id.rc_voip_members_container); + memberContainer.enableShowState(true); + LinearLayout linear_scrollviewTag = + (LinearLayout) outgoingLayout.findViewById(R.id.linear_scrollviewTag); + if (callSession.getParticipantProfileList().size() > 4) { + ViewGroup.LayoutParams params = linear_scrollviewTag.getLayoutParams(); + params.height = CallKitUtils.dp2px(200, MultiAudioCallActivity.this); + linear_scrollviewTag.setLayoutParams(params); + } + for (CallUserProfile profile : callSession.getParticipantProfileList()) { + if (!profile.getUserId().equals(callSession.getSelfUserId())) { + UserInfo userInfo = + RongUserInfoManager.getInstance().getUserInfo(profile.getUserId()); + String state = + profile.getCallStatus().equals(RongCallCommon.CallStatus.CONNECTED) + ? null + : getString(R.string.rc_voip_call_connecting); + memberContainer.addChild(profile.getUserId(), userInfo, state); + } + } + } + + outgoingLayout.findViewById(R.id.rc_voip_remind).setVisibility(View.GONE); + outgoingLayout.findViewById(R.id.rc_voip_handfree).setVisibility(View.VISIBLE); + ImageView button = outgoingController.findViewById(R.id.rc_voip_call_mute_btn); + button.setEnabled(true); + outgoingLayout.findViewById(R.id.rc_voip_call_mute).setVisibility(View.VISIBLE); + // 多人语音主叫方顶部布局 + RelativeLayout relativeLayout = + (RelativeLayout) + outgoingLayout.findViewById(R.id.reltive_voip_outgoing_audio_title); + relativeLayout.setVisibility(View.GONE); + + View muteV = outgoingLayout.findViewById(R.id.rc_voip_call_mute_btn); + muteV.setVisibility(View.VISIBLE); + muteV.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + onMuteButtonClick(v); + } + }); + + View handfreeV = outgoingLayout.findViewById(R.id.rc_voip_handfree_btn); + handfreeV.setVisibility(View.VISIBLE); + handfreeV.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + onHandFreeButtonClick(v); + } + }); + + outgoingLayout.findViewById(R.id.rc_voip_title).setVisibility(View.VISIBLE); + TextView timeV = (TextView) outgoingLayout.findViewById(R.id.rc_voip_time); + setupTime(timeV); + + View imgvAdd = outgoingLayout.findViewById(R.id.rc_voip_add_btn); + imgvAdd.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + setShouldShowFloat(false); + if (callSession + .getConversationType() + .equals(Conversation.ConversationType.DISCUSSION)) { + RongDiscussionClient.getInstance() + .getDiscussion( + callSession.getTargetId(), + new IRongCoreCallback.ResultCallback() { + @Override + public void onSuccess(Discussion discussion) { + Intent intent = + new Intent( + MultiAudioCallActivity.this, + CallSelectMemberActivity.class); + ArrayList added = + new ArrayList(); + List list = + RongCallClient.getInstance() + .getCallSession() + .getParticipantProfileList(); + for (CallUserProfile profile : list) { + added.add(profile.getUserId()); + } + List allObserver = + RongCallClient.getInstance() + .getCallSession() + .getObserverUserList(); + intent.putStringArrayListExtra( + "allObserver", + new ArrayList<>(allObserver)); + intent.putStringArrayListExtra( + "allMembers", + (ArrayList) + discussion.getMemberIdList()); + intent.putStringArrayListExtra( + "invitedMembers", added); + intent.putExtra( + "conversationType", + callSession + .getConversationType() + .getValue()); + intent.putExtra( + "mediaType", + RongCallCommon.CallMediaType.AUDIO + .getValue()); + startActivityForResult( + intent, REQUEST_CODE_ADD_MEMBER); + } + + @Override + public void onError( + IRongCoreEnum.CoreErrorCode e) {} + }); + } else if (callSession + .getConversationType() + .equals(Conversation.ConversationType.GROUP)) { + Intent intent = + new Intent( + MultiAudioCallActivity.this, + CallSelectMemberActivity.class); + ArrayList added = new ArrayList<>(); + List list = + RongCallClient.getInstance() + .getCallSession() + .getParticipantProfileList(); + for (CallUserProfile profile : list) { + added.add(profile.getUserId()); + } + List allObserver = + RongCallClient.getInstance() + .getCallSession() + .getObserverUserList(); + intent.putExtra("callId", callSession.getCallId()); + intent.putStringArrayListExtra( + "allObserver", new ArrayList<>(allObserver)); + intent.putStringArrayListExtra("invitedMembers", added); + intent.putExtra( + "conversationType", + callSession.getConversationType().getValue()); + intent.putExtra("groupId", callSession.getTargetId()); + intent.putExtra( + "mediaType", RongCallCommon.CallMediaType.AUDIO.getValue()); + startActivityForResult(intent, REQUEST_CODE_ADD_MEMBER); + } else { + ArrayList added = new ArrayList<>(); + List list = + RongCallClient.getInstance() + .getCallSession() + .getParticipantProfileList(); + for (CallUserProfile profile : list) { + added.add(profile.getUserId()); + } + addMember(added); + } + } + }); + outgoingLayout.findViewById(R.id.rc_voip_minimize_outgoing).setVisibility(View.VISIBLE); + View minimizeV = outgoingLayout.findViewById(R.id.rc_voip_minimize); + minimizeV.setVisibility(View.VISIBLE); + minimizeV.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.i( + "audioTag", + "************ outgoingLayout.findViewById(R.id.rc_voip_minimize)*****************"); + MultiAudioCallActivity.super.onMinimizeClick(v); + } + }); + + RongCallClient.getInstance().setEnableLocalAudio(!muted); + if (muteV != null) { + muteV.setSelected(muted); + } + RCRTCEngine.getInstance().enableSpeaker(false); + + stopRing(); + } + + protected void resetHandFreeStatus(RCAudioRouteType type) { + ImageView handFreeV = null; + if (null != outgoingLayout) { + handFreeV = outgoingLayout.findViewById(R.id.rc_voip_handfree_btn); + } + if (handFreeV != null) { + // 耳机状态 + if (type == RCAudioRouteType.HEADSET || type == RCAudioRouteType.HEADSET_BLUETOOTH) { + // handFreeV.setSelected(false); + } else { + // 非耳机状态 + handFreeV.setSelected(type == RCAudioRouteType.SPEAKER_PHONE); + } + } + } + + @Override + public void onCallDisconnected( + RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) { + super.onCallDisconnected(callSession, reason); + + isFinishing = true; + if (reason == null || callSession == null) { + RLog.e(TAG, "onCallDisconnected. callSession is null!"); + postRunnableDelay( + new Runnable() { + @Override + public void run() { + finish(); + } + }); + return; + } + + MultiCallEndMessage multiCallEndMessage = new MultiCallEndMessage(); + multiCallEndMessage.setReason(reason); + multiCallEndMessage.setMediaType(IRongCoreEnum.MediaType.AUDIO); + long serverTime = System.currentTimeMillis() - RongIMClient.getInstance().getDeltaTime(); + IMCenter.getInstance() + .insertIncomingMessage( + callSession.getConversationType(), + callSession.getTargetId(), + callSession.getCallerUserId(), + CallKitUtils.getReceivedStatus(reason), + multiCallEndMessage, + serverTime, + null); + cancelTime(); + stopRing(); + postRunnableDelay( + new Runnable() { + @Override + public void run() { + finish(); + } + }); + sendBroadcast(new Intent(DISCONNECT_ACTION).setPackage(getPackageName())); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + callSession = RongCallClient.getInstance().getCallSession(); + if (requestCode == REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS) { + if (RongCallPermissionUtil.checkAudioCallNeedPermission(this)) { + if (startForCheckPermissions) { + startForCheckPermissions = false; + RongCallClient.getInstance().onPermissionGranted(); + } else { + initView(); + } + } else { + if (startForCheckPermissions) { + startForCheckPermissions = false; + RongCallClient.getInstance().onPermissionDenied(); + } else { + finish(); + } + } + + } else if (requestCode == REQUEST_CODE_ADD_MEMBER) { + if (resultCode == RESULT_OK) { + if (data.getBooleanExtra("remote_hangup", false)) { + RLog.d(TAG, "Remote exit, end the call."); + return; + } + } + if (callSession.getEndTime() != 0) { + finish(); + return; + } + shouldShowFloat = true; + if (resultCode == RESULT_OK) { + ArrayList invited = data.getStringArrayListExtra("invited"); + ArrayList observers = data.getStringArrayListExtra("observers"); + List callUserProfiles = callSession.getParticipantProfileList(); + Iterator iterator = invited.iterator(); + while (iterator.hasNext()) { + String id = iterator.next(); + for (CallUserProfile profile : callUserProfiles) { + if (profile.getUserId().equals(id)) { + iterator.remove(); + } + } + } + RongCallClient.getInstance() + .addParticipants(callSession.getCallId(), invited, observers); + } + } else if (requestCode == REQUEST_CODE_ADD_MEMBER_NONE) { + try { + if (callSession.getEndTime() != 0) { + finish(); + return; + } + setShouldShowFloat(true); + if (resultCode == RESULT_OK) { + ArrayList invited = data.getStringArrayListExtra("pickedIds"); + RongCallClient.getInstance() + .addParticipants(callSession.getCallId(), invited, null); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void onHandFreeButtonClick(View view) { + RongCallClient.getInstance().setEnableSpeakerphone(!view.isSelected()); + view.setSelected(!view.isSelected()); + handFree = view.isSelected(); + } + + public void onMuteButtonClick(View view) { + RongCallClient.getInstance().setEnableLocalAudio(view.isSelected()); + view.setSelected(!view.isSelected()); + muted = view.isSelected(); + } + + @Override + public String onSaveFloatBoxState(Bundle bundle) { + super.onSaveFloatBoxState(bundle); + String intentAction = null; + Log.i("audioTag", "onSaveFloatBoxState shouldShowFloat=" + shouldShowFloat); + if (shouldShowFloat) { + intentAction = getIntent().getAction(); + bundle.putInt("mediaType", RongCallCommon.CallMediaType.AUDIO.getValue()); + bundle.putBoolean(KEY_HAND_FREE, handFree); + bundle.putBoolean(KEY_MUTED, muted); + } + return intentAction; + } + + @Override + public void onBackPressed() { + return; + } + + public void onMinimizeClick(View view) { + super.onMinimizeClick(view); + } + + @Override + public void onUserUpdate(UserInfo userInfo) { + if (isFinishing()) { + return; + } + TextView callerName = + (TextView) audioContainer.findViewWithTag(userInfo.getUserId() + "callerName"); + + if (callerName != null && userInfo.getName() != null) { + callerName.setLines(1); + callerName.setEllipsize(TextUtils.TruncateAt.END); + callerName.setText(userInfo.getName()); + } + if (memberContainer != null + && memberContainer.findChildById(userInfo.getUserId()) != null) { + memberContainer.updateChildInfo(userInfo.getUserId(), userInfo); + } + } + + public void onHeadsetPlugUpdate(HeadsetInfo headsetInfo) { + if (headsetInfo == null || !BluetoothUtil.isForground(MultiAudioCallActivity.this)) { + FinLog.v("bugtags", "MultiAudioCallActivity 不在前台!"); + return; + } + Log.i( + "bugtags", + "Insert=" + + headsetInfo.isInsert() + + ",headsetInfo.getType=" + + headsetInfo.getType().getValue()); + try { + if (headsetInfo.isInsert()) { + RongCallClient.getInstance().setEnableSpeakerphone(false); + ImageView handFreeV = null; + if (null != outgoingLayout) { + handFreeV = outgoingLayout.findViewById(R.id.rc_voip_handfree_btn); + } + if (handFreeV != null) { + handFreeV.setSelected(false); + handFreeV.setEnabled(false); + handFreeV.setClickable(false); + } + if (headsetInfo.getType() == HeadsetInfo.HeadsetType.BluetoothA2dp) { + AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + am.setMode(AudioManager.MODE_IN_COMMUNICATION); + am.startBluetoothSco(); + am.setBluetoothScoOn(true); + am.setSpeakerphoneOn(false); + } + } else { + if (headsetInfo.getType() == HeadsetInfo.HeadsetType.WiredHeadset + && BluetoothUtil.hasBluetoothA2dpConnected()) { + return; + } + RongCallClient.getInstance().setEnableSpeakerphone(true); + ImageView handFreeV = null; + if (null != outgoingLayout) { + handFreeV = outgoingLayout.findViewById(R.id.rc_voip_handfree_btn); + } + if (handFreeV != null) { + handFreeV.setSelected(true); + handFreeV.setEnabled(true); + handFreeV.setClickable(true); + } + } + } catch (Exception e) { + e.printStackTrace(); + Log.i("bugtags", "MultiAudioCallActivity->onHeadsetPlugUpdate Error=" + e.getMessage()); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/MultiCallEndMessageProvider.java b/callkit/src/main/java/io/rong/callkit/MultiCallEndMessageProvider.java new file mode 100644 index 000000000..1f46bed43 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/MultiCallEndMessageProvider.java @@ -0,0 +1,103 @@ +package io.rong.callkit; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableString; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.message.MultiCallEndMessage; +import io.rong.imkit.conversation.messgelist.provider.BaseNotificationMessageItemProvider; +import io.rong.imkit.model.UiMessage; +import io.rong.imkit.widget.adapter.IViewProviderListener; +import io.rong.imkit.widget.adapter.ViewHolder; +import io.rong.imlib.IRongCoreEnum; +import io.rong.imlib.model.MessageContent; +import java.util.List; + +public class MultiCallEndMessageProvider + extends BaseNotificationMessageItemProvider { + + @Override + protected ViewHolder onCreateMessageContentViewHolder(ViewGroup parent, int viewType) { + View v = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.rc_voip_msg_multi_call_end, parent, false); + return new ViewHolder(parent.getContext(), v); + } + + @Override + protected void bindMessageContentViewHolder( + ViewHolder holder, + ViewHolder parentHolder, + MultiCallEndMessage multiCallEndMessage, + UiMessage uiMessage, + int position, + List list, + IViewProviderListener listener) { + Context context = holder.getContext(); + String msg = ""; + RongCallCommon.CallDisconnectedReason reason = multiCallEndMessage.getReason(); + IRongCoreEnum.MediaType mediaType = multiCallEndMessage.getMediaType(); + if (reason == RongCallCommon.CallDisconnectedReason.OTHER_DEVICE_HAD_ACCEPTED) { + msg = context.getResources().getString(R.string.rc_voip_call_other); + } else if (reason == RongCallCommon.CallDisconnectedReason.REMOTE_HANGUP + || reason == RongCallCommon.CallDisconnectedReason.HANGUP) { + if (mediaType == IRongCoreEnum.MediaType.AUDIO) { + msg = context.getResources().getString(R.string.rc_voip_audio_ended); + } else if (mediaType == IRongCoreEnum.MediaType.VIDEO) { + msg = context.getResources().getString(R.string.rc_voip_video_ended); + } + } else if (reason == RongCallCommon.CallDisconnectedReason.REMOTE_REJECT + || reason == RongCallCommon.CallDisconnectedReason.REJECT) { + if (mediaType == IRongCoreEnum.MediaType.AUDIO) { + msg = context.getResources().getString(R.string.rc_voip_audio_refuse); + } else if (mediaType == IRongCoreEnum.MediaType.VIDEO) { + msg = context.getResources().getString(R.string.rc_voip_video_refuse); + } + } else if (reason == RongCallCommon.CallDisconnectedReason.SERVICE_NOT_OPENED + || reason == RongCallCommon.CallDisconnectedReason.REMOTE_ENGINE_UNSUPPORTED) { + msg = context.getResources().getString(R.string.rc_voip_engine_notfound); + } else if (reason == RongCallCommon.CallDisconnectedReason.CANCEL) { + if (mediaType == IRongCoreEnum.MediaType.AUDIO) { + msg = context.getResources().getString(R.string.rc_voip_audio_cancel); + } else if (mediaType == IRongCoreEnum.MediaType.VIDEO) { + msg = context.getResources().getString(R.string.rc_voip_video_cancel); + } + } else { + if (mediaType == IRongCoreEnum.MediaType.AUDIO) { + msg = context.getResources().getString(R.string.rc_voip_audio_no_response); + } else if (mediaType == IRongCoreEnum.MediaType.VIDEO) { + msg = context.getResources().getString(R.string.rc_voip_video_no_response); + } + } + TextView tv = holder.getView(R.id.rc_msg); + tv.setText(msg); + } + + @Override + protected boolean isMessageViewType(MessageContent messageContent) { + return messageContent instanceof MultiCallEndMessage; + } + + @Override + public Spannable getSummarySpannable(Context context, MultiCallEndMessage multiCallEndMessage) { + String msg = ""; + if (multiCallEndMessage.getReason() == RongCallCommon.CallDisconnectedReason.NO_RESPONSE) { + if (multiCallEndMessage.getMediaType() == IRongCoreEnum.MediaType.AUDIO) { + msg = context.getResources().getString(R.string.rc_voip_audio_no_response); + } else if (multiCallEndMessage.getMediaType() == IRongCoreEnum.MediaType.VIDEO) { + msg = context.getResources().getString(R.string.rc_voip_video_no_response); + } + } else { + if (multiCallEndMessage.getMediaType() == IRongCoreEnum.MediaType.AUDIO) { + msg = context.getResources().getString(R.string.rc_voip_message_audio); + } else if (multiCallEndMessage.getMediaType() == IRongCoreEnum.MediaType.VIDEO) { + msg = context.getResources().getString(R.string.rc_voip_message_video); + } + } + return new SpannableString(msg); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/MultiVideoCallActivity.java b/callkit/src/main/java/io/rong/callkit/MultiVideoCallActivity.java new file mode 100644 index 000000000..2870dec15 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/MultiVideoCallActivity.java @@ -0,0 +1,1943 @@ +package io.rong.callkit; + +import static io.rong.callkit.CallSelectMemberActivity.DISCONNECT_ACTION; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; +import cn.rongcloud.rtc.api.RCRTCEngine; +import cn.rongcloud.rtc.api.stream.RCRTCVideoView; +import cn.rongcloud.rtc.audioroute.RCAudioRouteType; +import cn.rongcloud.rtc.base.RCRTCStream; +import cn.rongcloud.rtc.core.RendererCommon; +import cn.rongcloud.rtc.utils.FinLog; +import io.rong.callkit.util.BluetoothUtil; +import io.rong.callkit.util.CallKitUtils; +import io.rong.callkit.util.DefaultPushConfig; +import io.rong.callkit.util.GlideUtils; +import io.rong.callkit.util.HeadsetInfo; +import io.rong.callkit.util.RingingMode; +import io.rong.callkit.util.RongCallPermissionUtil; +import io.rong.callkit.util.UserProfileOrderManager; +import io.rong.calllib.CallUserProfile; +import io.rong.calllib.ReportUtil; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import io.rong.calllib.StartIncomingPreviewCallback; +import io.rong.calllib.StreamProfile; +import io.rong.calllib.Utils; +import io.rong.calllib.message.MultiCallEndMessage; +import io.rong.common.RLog; +import io.rong.imkit.IMCenter; +import io.rong.imkit.userinfo.RongUserInfoManager; +import io.rong.imlib.IRongCoreCallback; +import io.rong.imlib.IRongCoreEnum; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.discussion.base.RongDiscussionClient; +import io.rong.imlib.discussion.model.Discussion; +import io.rong.imlib.model.Conversation; +import io.rong.imlib.model.Group; +import io.rong.imlib.model.UserInfo; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +/** 如何实现不基于于群组的voip */ +public class MultiVideoCallActivity extends BaseCallActivity { + private static final String TAG = "MultiVideoCallActivity"; + private static final String REMOTE_FURFACEVIEW_TAG = "surfaceview"; // + private static final String REMOTE_VIEW_TAG = "remoteview"; // rc_voip_viewlet_remote_user tag + private static final String VOIP_USERNAME_TAG = + "username"; // topContainer.findViewById(R.id.rc_voip_user_name); + private static final String VOIP_PARTICIPANT_PORTAIT_CONTAINER_TAG = + "participantPortraitView"; // 被叫方显示头像容器tag + RongCallSession callSession; + SurfaceView localView; + ContainerLayout localViewContainer; + LinearLayout remoteViewContainer; + LinearLayout remoteViewContainer2; + LinearLayout topContainer; + LinearLayout waitingContainer; + LinearLayout bottomButtonContainer; + LinearLayout participantPortraitContainer; + LinearLayout portraitContainer1; // 维护未接听时,所有成员列表 + LayoutInflater inflater; + // 通话中的最小化按钮、呼叫中的最小化按钮 + ImageView minimizeButton, rc_voip_multiVideoCall_minimize; + ImageView moreButton; + ImageView switchCameraButton; + ImageView userPortrait; + LinearLayout infoLayout; + ImageView signalView; + TextView userNameView; + private int remoteUserViewWidth; + // private int remoteUserViewHeight; + // 主叫、通话中 远端View + private float remoteUserViewMarginsRight = 10; + private float remoteUserViewMarginsLeft = 20; + + boolean isFullScreen = false; + boolean isMuteMIC = false; + boolean startForCheckPermissions = false; + + String localViewUserId; + private CallOptionMenu optionMenu; + ImageView muteButtion; + ImageView disableCameraButtion; + CallPromptDialog dialog = null; + RelativeLayout observerLayout; + private ImageView iv_large_preview_mutilvideo, iv_large_preview_Mask; + private String topUserName = "", topUserNameTag = ""; + private UserProfileOrderManager mUserProfileOrderManager; + + @Override + @TargetApi(23) + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null && RongCallClient.getInstance() == null) { + // 音视频请求权限时,用户在设置页面取消权限,导致应用重启,退出当前activity. + finish(); + return; + } + setContentView(R.layout.rc_voip_multi_video_call); + Intent intent = getIntent(); + startForCheckPermissions = intent.getBooleanExtra("checkPermissions", false); + boolean val = + requestCallPermissions( + RongCallCommon.CallMediaType.VIDEO, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); + RLog.i(TAG, "onCreate initViews requestCallPermissions=" + val); + if (val) { + RLog.i(TAG, "--- onCreate initViews------"); + initViews(); + setupIntent(); + } + mUserProfileOrderManager = new UserProfileOrderManager(); + } + + @Override + protected void onNewIntent(Intent intent) { + RLog.d(TAG, "onNewIntent: [intent]"); + startForCheckPermissions = intent.getBooleanExtra("checkPermissions", false); + super.onNewIntent(intent); + boolean bool = + requestCallPermissions( + RongCallCommon.CallMediaType.VIDEO, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); + RLog.i(TAG, "mult onNewIntent==" + bool); + if (bool) { + RLog.i(TAG, "mult onNewIntent initViews"); + initViews(); + setupIntent(); + } + } + + @TargetApi(23) + @Override + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + RLog.d(TAG, "onRequestPermissionsResult: " + requestCode); + switch (requestCode) { + case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: + if (RongCallPermissionUtil.checkVideoCallNeedPermission(this)) { + if (startForCheckPermissions) { + startForCheckPermissions = false; + RongCallClient.getInstance().onPermissionGranted(); + } else { + initViews(); + setupIntent(); + } + } else { + if (permissions.length > 0) { + RongCallClient.getInstance().onPermissionDenied(); + Toast.makeText( + this, + getString(R.string.rc_voip_relevant_permissions), + Toast.LENGTH_SHORT) + .show(); + finish(); + } + } + break; + default: + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + RLog.i(TAG, "mult onActivityResult requestCode=" + requestCode); + super.onActivityResult(requestCode, resultCode, data); + callSession = RongCallClient.getInstance().getCallSession(); + if (requestCode == REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS) { + if (RongCallPermissionUtil.checkVideoCallNeedPermission(this)) { + if (startForCheckPermissions) { + startForCheckPermissions = false; + RongCallClient.getInstance().onPermissionGranted(); + } else { + RLog.i(TAG, "mult onActivityResult initView"); + initViews(); + setupIntent(); + } + } else { + if (startForCheckPermissions) { + startForCheckPermissions = false; + RongCallClient.getInstance().onPermissionDenied(); + } else { + finish(); + } + } + + } else if (requestCode == REQUEST_CODE_ADD_MEMBER) { + if (resultCode == RESULT_OK) { + if (data.getBooleanExtra("remote_hangup", false)) { + RLog.d(TAG, "Remote exit, end the call."); + return; + } + } + if (callSession.getEndTime() != 0) { + finish(); + return; + } + setShouldShowFloat(true); + if (resultCode == RESULT_OK) { + ArrayList invited = data.getStringArrayListExtra("invited"); + ArrayList observers = data.getStringArrayListExtra("observers"); + List callUserProfiles = callSession.getParticipantProfileList(); + Iterator iterator = invited.iterator(); + while (iterator.hasNext()) { + String id = iterator.next(); + for (CallUserProfile profile : callUserProfiles) { + if (profile.getUserId().equals(id)) { + iterator.remove(); + } + } + } + RongCallClient.getInstance() + .addParticipants(callSession.getCallId(), invited, observers); + } + } else if (requestCode == REQUEST_CODE_ADD_MEMBER_NONE) { + try { + if (callSession.getEndTime() != 0) { + finish(); + return; + } + setShouldShowFloat(true); + if (resultCode == RESULT_OK) { + ArrayList invited = data.getStringArrayListExtra("pickedIds"); + RongCallClient.getInstance() + .addParticipants(callSession.getCallId(), invited, null); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + CallKitUtils.callConnected = false; + if (localViewContainer != null) { + localViewContainer.setIsNeedFillScrren(true); + } + } + + @Override + public String onSaveFloatBoxState(Bundle bundle) { + super.onSaveFloatBoxState(bundle); + String intentAction = getIntent().getAction(); + bundle.putBoolean(EXTRA_BUNDLE_KEY_MUTECAMERA, isMuteCamera); + bundle.putBoolean(EXTRA_BUNDLE_KEY_MUTEMIC, isMuteMIC); + bundle.putString(EXTRA_BUNDLE_KEY_LOCALVIEWUSERID, localViewUserId); + bundle.putString(EXTRA_BUNDLE_KEY_CALLACTION, RongCallAction.ACTION_RESUME_CALL.getName()); + bundle.putInt(EXTRA_BUNDLE_KEY_MEDIATYPE, RongCallCommon.CallMediaType.VIDEO.getValue()); + bundle.putString(EXTRA_BUNDLE_KEY_USER_TOP_NAME, topUserName); + bundle.putString(EXTRA_BUNDLE_KEY_USER_TOP_NAME_TAG, topUserNameTag); + bundle.putStringArrayList( + EXTRA_BUNDLE_KEY_USER_PROFILE_TAG_ORDER_TAG, mUserProfileOrderManager.getUserIds()); + RLog.d(TAG, "onSaveFloatBoxState-->localViewUserId : " + localViewUserId); + return intentAction; + } + + @Override + public void onRestoreFloatBox(Bundle bundle) { + super.onRestoreFloatBox(bundle); + try { + RLog.i(TAG, "--- onRestoreFloatBox ------"); + callSession = RongCallClient.getInstance().getCallSession(); + if (bundle != null) { + RongCallAction callAction = RongCallAction.valueOf(bundle.getString("callAction")); + if (!callAction.equals(RongCallAction.ACTION_RESUME_CALL)) { + return; + } + + if (mUserProfileOrderManager != null) { + mUserProfileOrderManager = null; + } + mUserProfileOrderManager = + new UserProfileOrderManager( + bundle.getStringArrayList( + EXTRA_BUNDLE_KEY_USER_PROFILE_TAG_ORDER_TAG)); + localViewUserId = bundle.getString(EXTRA_BUNDLE_KEY_LOCALVIEWUSERID); + isMuteCamera = bundle.getBoolean(EXTRA_BUNDLE_KEY_MUTECAMERA); + isMuteMIC = bundle.getBoolean(EXTRA_BUNDLE_KEY_MUTEMIC); + topUserName = bundle.getString(EXTRA_BUNDLE_KEY_USER_TOP_NAME); + topUserNameTag = bundle.getString(EXTRA_BUNDLE_KEY_USER_TOP_NAME_TAG); + if (callSession == null) { + setShouldShowFloat(false); + finish(); + return; + } + + boolean isLocalViewExist = false; + for (CallUserProfile profile : callSession.getParticipantProfileList()) { + if (profile.getUserId().equals(localViewUserId)) { + isLocalViewExist = true; + break; + } + for (StreamProfile streamProfile : profile.streamProfiles) { + if (TextUtils.equals(streamProfile.streamId, localViewUserId)) { + isLocalViewExist = true; + break; + } + } + if (isLocalViewExist) { + break; + } + } + if (remoteViewContainer2 != null) { + remoteViewContainer2.removeAllViews(); + } + for (CallUserProfile profile : callSession.getParticipantProfileList()) { + String currentUserId = RongIMClient.getInstance().getCurrentUserId(); + if (profile.getUserId().equals(localViewUserId) + || (!isLocalViewExist && profile.getUserId().equals(currentUserId))) { + localView = profile.getVideoView(); + } + if (isLocalViewExist) { + for (StreamProfile streamProfile : profile.streamProfiles) { + if (TextUtils.equals(streamProfile.streamId, localViewUserId)) { + localView = streamProfile.videoView; + } + } + } + if (localView != null) { + if (localView.getParent() != null) { + ((ViewGroup) localView.getParent()).removeAllViews(); + } + String tag = (String) localView.getTag(); + localViewUserId = tag.substring(0, tag.indexOf(REMOTE_FURFACEVIEW_TAG)); + localView.setZOrderOnTop(false); + localView.setZOrderMediaOverlay(false); + localViewContainer.addView(localView); + localViewContainer.addView(getObserverLayout()); + localView.setTag( + CallKitUtils.getStitchedContent( + localViewUserId, REMOTE_FURFACEVIEW_TAG)); + TextView userNameView = + (TextView) topContainer.findViewById(R.id.rc_voip_user_name); + userNameView.setLines(1); + userNameView.setEllipsize(TextUtils.TruncateAt.END); + if (!TextUtils.isEmpty(topUserName)) { + userNameView.setTag(topUserNameTag); + userNameView.setText(CallKitUtils.nickNameRestrict(topUserName)); + } else { + userNameView.setTag( + CallKitUtils.getStitchedContent( + localViewUserId, VOIP_USERNAME_TAG)); + UserInfo userInfo = + RongUserInfoManager.getInstance().getUserInfo(localViewUserId); + if (userInfo != null) { + userNameView.setText( + CallKitUtils.nickNameRestrict(userInfo.getName())); + } else { + userNameView.setText(localViewUserId); + } + } + break; + } + } + if (!(boolean) bundle.get("isDial")) { + // 已经有用户接听 + onCallConnected(callSession, null); + } else { + // 无用户接听 + updateRemoteVideoViews(callSession); + FrameLayout bottomButtonLayout; + if (CallKitUtils.findConfigurationLanguage(MultiVideoCallActivity.this, "ar")) { + bottomButtonLayout = + (FrameLayout) + inflater.inflate( + R.layout + .rc_voip_multi_video_calling_bottom_view_rtl, + null); + } else { + bottomButtonLayout = + (FrameLayout) + inflater.inflate( + R.layout.rc_voip_multi_video_calling_bottom_view, + null); + } + bottomButtonLayout + .findViewById(R.id.rc_voip_call_mute) + .setVisibility(View.GONE); + bottomButtonLayout + .findViewById(R.id.rc_voip_disable_camera) + .setVisibility(View.GONE); + bottomButtonLayout.findViewById(R.id.rc_voip_handfree).setVisibility(View.GONE); + bottomButtonContainer.removeAllViews(); + bottomButtonContainer.addView(bottomButtonLayout); + topContainer.setVisibility(View.GONE); + waitingContainer.setVisibility(View.VISIBLE); + remoteViewContainer.setVisibility(View.VISIBLE); + participantPortraitContainer.setVisibility(View.GONE); + bottomButtonContainer.setVisibility(View.VISIBLE); + rc_voip_multiVideoCall_minimize.setVisibility(View.GONE); + + callRinging(RingingMode.Outgoing); + } + } + } catch (Exception e) { + e.printStackTrace(); + RLog.i(TAG, "MultiVideoCallActivity onRestoreFloatBox Error=" + e.getMessage()); + } + } + + private void incomingPreview() { + RongCallClient.getInstance().setEnableLocalAudio(true); + RongCallClient.getInstance().setEnableLocalVideo(true); + RongCallClient.getInstance() + .startIncomingPreview( + new StartIncomingPreviewCallback() { + @Override + public void onDone(boolean isFront, SurfaceView localVideo) { + localView = localVideo; + ((RCRTCVideoView) localView) + .setScalingType( + RendererCommon.ScalingType.SCALE_ASPECT_BALANCED); + // localView.setZOrderOnTop(true); + // localView.setZOrderMediaOverlay(true); + ViewParent parent = localView.getParent(); + if (parent != null) { + ((ViewGroup) parent).removeView(localView); + } + localViewContainer.addView(localView); + + // 加载观察者布局 默认不显示 + localViewContainer.addView(getObserverLayout()); + localViewUserId = RongIMClient.getInstance().getCurrentUserId(); + localView.setTag( + CallKitUtils.getStitchedContent( + localViewUserId, REMOTE_FURFACEVIEW_TAG)); + } + + @Override + public void onError(int errorCode) {} + }); + } + + /** + * 电话已拨出。 主叫端拨出电话后 + * + * @param callSession 通话实体。 + * @param localVideo 本地 camera 信息。 + */ + @Override + public void onCallOutgoing(final RongCallSession callSession, SurfaceView localVideo) { + super.onCallOutgoing(callSession, localVideo); + this.callSession = callSession; + RongCallClient.getInstance().setEnableLocalAudio(true); + RongCallClient.getInstance().setEnableLocalVideo(true); + localView = localVideo; + callRinging(RingingMode.Outgoing); + ((RCRTCVideoView) localView) + .setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_BALANCED); + // localView.setZOrderOnTop(true); + // localView.setZOrderMediaOverlay(true); + localViewContainer.addView(localView); + + // 加载观察者布局 默认不显示 + localViewContainer.addView(getObserverLayout()); + + localViewUserId = RongIMClient.getInstance().getCurrentUserId(); + localView.setTag(CallKitUtils.getStitchedContent(localViewUserId, REMOTE_FURFACEVIEW_TAG)); + } + + @Override + public void onFirstRemoteVideoFrame(String userId, int height, int width) { + RLog.d("bugtags", "onFirstRemoteVideoFrame,uid :" + userId); + if (remoteViewContainer2 == null) { + RLog.e( + "bugtags", + "onFirstRemoteVideoFrame()->remoteViewContainer2 is empty.userId : " + userId); + return; + } + + View singleRemoteView = + remoteViewContainer2.findViewWithTag( + CallKitUtils.getStitchedContent(userId, REMOTE_VIEW_TAG)); + if (singleRemoteView == null) { + RLog.e("bugtags", "onFirstRemoteVideoFrame(). singleRemoteView is empty"); + + if (localViewContainer == null || localViewContainer.getChildCount() == 0) { + RLog.e("bugtags", "onFirstRemoteVideoFrame(). localViewContainer is empty"); + } else { + for (int i = 0; i < localViewContainer.getChildCount(); i++) { + if (localViewContainer.getChildAt(i) instanceof RCRTCVideoView) { + ((RCRTCVideoView) localViewContainer.getChildAt(i)).setZOrderOnTop(false); + ((RCRTCVideoView) localViewContainer.getChildAt(i)) + .setZOrderMediaOverlay(false); + ((RCRTCVideoView) localViewContainer.getChildAt(i)) + .setBackgroundColor(Color.TRANSPARENT); + break; + } + } + } + return; + } + View stateView = singleRemoteView.findViewById(R.id.user_status); + if (stateView != null) { + stateView.setVisibility(View.GONE); + } + + FrameLayout remoteVideoView = + (FrameLayout) singleRemoteView.findViewById(R.id.viewlet_remote_video_user); + if (remoteVideoView == null) { + return; + } + int childCount = remoteVideoView.getChildCount(); + for (int i = 0; i < childCount; i++) { + if (remoteVideoView.getChildAt(i) != null + && remoteVideoView.getChildAt(i) instanceof RCRTCVideoView) { + // if (!TextUtils.equals(Build.MODEL, "PEPM00")) { + // ((RCRTCVideoView) + // remoteVideoView.getChildAt(i)).setZOrderOnTop(true); + // ((RCRTCVideoView) + // remoteVideoView.getChildAt(i)).setZOrderMediaOverlay(true); + // } + remoteVideoView.getChildAt(i).setBackgroundColor(Color.TRANSPARENT); + break; + } + } + + TextView textView = singleRemoteView.findViewById(R.id.user_name); + textView.setVisibility(View.VISIBLE); + } + + /** + * 被叫端加入通话。 主叫端拨出电话,被叫端收到请求后,加入通话,回调 onRemoteUserJoined。 + * + * @param userId 加入用户的 id。 + * @param mediaType 加入用户的媒体类型,audio or video。 + * @param userType 加入用户的类型,1:正常用户,2:观察者。 + * @param remoteVideo 加入用户者的 camera 信息。 + */ + @Override + public void onRemoteUserJoined( + String userId, + RongCallCommon.CallMediaType mediaType, + int userType, + SurfaceView remoteVideo) { + remoteVideo.setBackgroundColor(Color.BLACK); + stopRing(); + if (localViewContainer != null && localViewContainer.getVisibility() != View.VISIBLE) { + localViewContainer.setVisibility(View.VISIBLE); + if (null != iv_large_preview_mutilvideo) { + iv_large_preview_mutilvideo.setVisibility(View.GONE); + } + if (null != iv_large_preview_Mask) { + iv_large_preview_Mask.setVisibility(View.GONE); + } + } + if (localViewUserId != null && localViewUserId.equals(userId)) { + return; + } + View singleRemoteView = + remoteViewContainer.findViewWithTag( + CallKitUtils.getStitchedContent(userId, REMOTE_VIEW_TAG)); + + if (singleRemoteView == null) { + singleRemoteView = addSingleRemoteView(userId, userType); + } + addRemoteVideo(singleRemoteView, remoteVideo, userId, false); + singleRemoteView.findViewById(R.id.user_status).setVisibility(View.GONE); + singleRemoteView.findViewById(R.id.user_name).setVisibility(View.GONE); + } + + @Override + public void onRemoteUserLeft(String userId, RongCallCommon.CallDisconnectedReason reason) { + // 通话过程中 toast "通话结束"有些突兀,所以只有远端忙线和拒绝时我们提醒用户 + if (reason.equals(RongCallCommon.CallDisconnectedReason.REMOTE_BUSY_LINE) + || reason.equals(RongCallCommon.CallDisconnectedReason.REMOTE_REJECT)) { + super.onRemoteUserLeft(userId, reason); + } + if (isTopContainerUserExit(userId)) { + return; + } + String delUserid = userId; + // incomming state + if (participantPortraitContainer != null + && participantPortraitContainer.getVisibility() == View.VISIBLE) { + View participantView = + participantPortraitContainer.findViewWithTag( + CallKitUtils.getStitchedContent( + userId, VOIP_PARTICIPANT_PORTAIT_CONTAINER_TAG)); + if (participantView == null) { + return; + } + LinearLayout portraitContainer = (LinearLayout) participantView.getParent(); + portraitContainer.removeView(participantView); + } + // incoming状态,localViewUserId为空 + if (localViewUserId == null) { + return; + } + if (localViewUserId.equals(userId)) { + localViewContainer.removeAllViews(); + delUserid = RongIMClient.getInstance().getCurrentUserId(); + // 拿到本地视频流装载对象 + FrameLayout remoteVideoView = + (FrameLayout) remoteViewContainer.findViewWithTag(delUserid); + localView = (SurfaceView) remoteVideoView.getChildAt(0); + remoteVideoView.removeAllViews(); + localView.setZOrderOnTop(false); + localViewContainer.addView(localView); // 将本地的给大屏 + + localViewContainer.addView(getObserverLayout()); + + TextView topUserNameView = (TextView) topContainer.findViewById(R.id.rc_voip_user_name); + topUserNameView.setTag(CallKitUtils.getStitchedContent(delUserid, VOIP_USERNAME_TAG)); + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(delUserid); + if (userInfo != null) { + topUserNameView.setText(CallKitUtils.nickNameRestrict(userInfo.getName())); + } else { + topUserNameView.setText(delUserid); + } + localViewUserId = delUserid; + } + if (remoteViewContainer2 != null && !TextUtils.isEmpty(delUserid)) { // 删除退出用户的头像框 + View singleRemoteView = + remoteViewContainer2.findViewWithTag( + CallKitUtils.getStitchedContent(delUserid, REMOTE_VIEW_TAG)); + if (singleRemoteView == null) { + return; + } + remoteViewContainer2.removeView(singleRemoteView); + } + } + + private boolean isTopContainerUserExit(String userId) { + if (CallKitUtils.callConnected) { + return false; + } + if (callSession != null + && TextUtils.equals(callSession.getInviterUserId(), userId) + && portraitContainer1 != null) { + View userPortraitView = portraitContainer1.getChildAt(0); + if (userPortraitView != null && userPortraitView.getTag() != null) { + String tag = (String) userPortraitView.getTag(); + String firstUserId = tag.replace(VOIP_PARTICIPANT_PORTAIT_CONTAINER_TAG, ""); + UserInfo firstUserInfo = RongUserInfoManager.getInstance().getUserInfo(firstUserId); + // topContainer + TextView userNameView = + (TextView) topContainer.findViewById(R.id.rc_voip_user_name); + userNameView.setTag( + CallKitUtils.getStitchedContent(firstUserId, VOIP_USERNAME_TAG)); + if (firstUserInfo != null) { + userNameView.setText(CallKitUtils.nickNameRestrict(firstUserInfo.getName())); + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + firstUserInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + userPortrait); + userPortrait.setVisibility(View.VISIBLE); + } else { + userNameView.setText(firstUserId); + } + // + if (participantPortraitContainer != null + && participantPortraitContainer.getVisibility() == View.VISIBLE) { + View participantView = + participantPortraitContainer.findViewWithTag( + CallKitUtils.getStitchedContent( + firstUserId, VOIP_PARTICIPANT_PORTAIT_CONTAINER_TAG)); + if (participantView != null) { + LinearLayout portraitContainer = (LinearLayout) participantView.getParent(); + portraitContainer.removeView(participantView); + } + } + // + View firstView = portraitContainer1.getChildAt(0); + LinearLayout.LayoutParams layoutParams = (LayoutParams) firstView.getLayoutParams(); + layoutParams.setMargins( + CallKitUtils.dp2px(remoteUserViewMarginsLeft, MultiVideoCallActivity.this), + 0, + CallKitUtils.dp2px(remoteUserViewMarginsRight, MultiVideoCallActivity.this), + 0); + firstView.requestLayout(); + return true; + } + } + return false; + } + + private ArrayList getInvitedList() { + ArrayList invitedList = new ArrayList<>(); + List list = callSession.getParticipantProfileList(); + List incomingObserverUserList = callSession.getObserverUserList(); + for (CallUserProfile profile : list) { + if (!profile.getUserId().equals(callSession.getCallerUserId())) { + if (null != incomingObserverUserList + && !incomingObserverUserList.contains(profile.getUserId())) { + invitedList.add(profile.getUserId()); + } + } + } + return invitedList; + } + + @Override + public void onRemoteUserPublishVideoStream( + String userId, String streamId, String tag, SurfaceView surfaceView) { + if (TextUtils.equals(userId, localViewUserId)) { + return; + } + View singleRemoteView = null; + if (remoteViewContainer2 != null) { + // 先去找是否已经添加了对方的viewGroup,没有再创建 + singleRemoteView = + remoteViewContainer2.findViewWithTag( + CallKitUtils.getStitchedContent(userId, REMOTE_VIEW_TAG)); + } + if (singleRemoteView == null) { + singleRemoteView = addSingleRemoteView(userId, 1); + } + singleRemoteView.findViewById(R.id.user_status).setVisibility(View.GONE); + singleRemoteView.findViewById(R.id.user_portrait).setVisibility(View.GONE); + singleRemoteView.findViewById(R.id.user_name).setVisibility(View.GONE); + // 把最新的 surfaceView 展示出来,onRemoteUserJoined 返回的 surfaceView 已经失效了,流被绑定到新的 surfaceView 上了 + addRemoteVideo(singleRemoteView, surfaceView, userId, true); + } + + @Override + public void onRemoteUserUnpublishVideoStream(String userId, String streamId, String tag) { + if (remoteViewContainer2 != null) { // 删除退出用户的头像框 + View singleRemoteView = + remoteViewContainer2.findViewWithTag( + CallKitUtils.getStitchedContent(streamId, REMOTE_VIEW_TAG)); + if (singleRemoteView == null) { + onRemoteUserLeft(streamId, RongCallCommon.CallDisconnectedReason.HANGUP); + } else { + remoteViewContainer2.removeView(singleRemoteView); + } + } + } + + /** + * @param userId + * @param mediaType + */ + @Override + public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) { + super.onRemoteUserInvited(userId, mediaType); + if (callSession != null) { + for (CallUserProfile profile : callSession.getParticipantProfileList()) { + if (profile.getUserId().equals(RongIMClient.getInstance().getCurrentUserId())) { + if (profile.getCallStatus().equals(RongCallCommon.CallStatus.CONNECTED)) { + int callUserType = 1; + if (callSession.getObserverUserList() != null + && callSession.getObserverUserList().contains(userId)) { + callUserType = 2; + } + addSingleRemoteView(userId, callUserType); + } + } + } + } + } + + /** + * 已建立通话。 通话接通时,通过回调 onCallConnected 通知当前 call 的详细信息。 + * + * @param callSession 通话实体。 + * @param localVideo 本地 camera 信息。 + */ + @SuppressLint("NewApi") + @Override + public void onCallConnected(RongCallSession callSession, SurfaceView localVideo) { + super.onCallConnected(callSession, localVideo); + this.callSession = callSession; + if (null != rc_voip_multiVideoCall_minimize) { + rc_voip_multiVideoCall_minimize.setVisibility(View.GONE); + } + if (iv_large_preview_mutilvideo != null + && iv_large_preview_mutilvideo.getVisibility() == View.VISIBLE) { + iv_large_preview_mutilvideo.setVisibility(View.GONE); + } + if (null != iv_large_preview_Mask) { + iv_large_preview_Mask.setVisibility(View.GONE); + } + + if (localView == null) { + localView = localVideo; + // localView.setZOrderOnTop(true); + // localView.setZOrderMediaOverlay(true); + localViewContainer.removeAllViews(); + localViewContainer.addView(localView); + getObserverLayout(); + localViewContainer.addView(observerLayout); + observerLayout.setVisibility( + callSession.getUserType() == RongCallCommon.CallUserType.OBSERVER + ? View.VISIBLE + : View.GONE); + localViewUserId = RongIMClient.getInstance().getCurrentUserId(); + localView.setTag( + CallKitUtils.getStitchedContent(localViewUserId, REMOTE_FURFACEVIEW_TAG)); + } + + localViewContainer.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!isFullScreen) { + isFullScreen = true; + rc_voip_multiVideoCall_minimize.setVisibility(View.INVISIBLE); + topContainer.setVisibility(View.GONE); + bottomButtonContainer.setVisibility(View.GONE); + } else { + isFullScreen = false; + rc_voip_multiVideoCall_minimize.setVisibility(View.VISIBLE); + topContainer.setVisibility(View.VISIBLE); + bottomButtonContainer.setVisibility(View.VISIBLE); + } + } + }); + bottomButtonContainer.removeAllViews(); + + FrameLayout bottomButtonLayout; + if (CallKitUtils.findConfigurationLanguage(MultiVideoCallActivity.this, "ar")) { + bottomButtonLayout = + (FrameLayout) + inflater.inflate( + R.layout.rc_voip_multi_video_calling_bottom_view_rtl, null); + bottomButtonLayout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); + } else { + bottomButtonLayout = + (FrameLayout) + inflater.inflate( + R.layout.rc_voip_multi_video_calling_bottom_view, null); + } + + RelativeLayout relativeHangup = bottomButtonLayout.findViewById(R.id.relativeHangup); + if (CallKitUtils.findConfigurationLanguage(MultiVideoCallActivity.this, "ar")) { + relativeHangup.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); + } + bottomButtonContainer.addView(bottomButtonLayout); + muteButtion = bottomButtonContainer.findViewById(R.id.rc_voip_call_mute_btn); + muteButtion.setSelected(isMuteMIC); + disableCameraButtion = bottomButtonContainer.findViewById(R.id.rc_voip_disable_camera_btn); + disableCameraButtion.setSelected(isMuteCamera); + topContainer.setVisibility(View.VISIBLE); + minimizeButton.setVisibility(View.VISIBLE); + rc_voip_multiVideoCall_minimize.setVisibility(View.VISIBLE); + userPortrait.setVisibility(View.GONE); + moreButton.setVisibility(View.VISIBLE); + switchCameraButton.setVisibility(View.VISIBLE); + waitingContainer.setVisibility(View.GONE); + remoteViewContainer.setVisibility(View.VISIBLE); + participantPortraitContainer.setVisibility(View.GONE); + + userNameView = (TextView) topContainer.findViewById(R.id.rc_voip_user_name); + CallKitUtils.textViewShadowLayer(userNameView, MultiVideoCallActivity.this); + + String currentUserId = RongIMClient.getInstance().getCurrentUserId(); + if (!TextUtils.isEmpty(topUserName)) { + userNameView.setTag(topUserNameTag); + userNameView.setText(CallKitUtils.nickNameRestrict(topUserName)); + } else { + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(currentUserId); + userNameView.setTag(CallKitUtils.getStitchedContent(currentUserId, VOIP_USERNAME_TAG)); + if (userInfo != null) { + userNameView.setText(CallKitUtils.nickNameRestrict(userInfo.getName())); + } else { + userNameView.setText(currentUserId); + } + } + RelativeLayout.LayoutParams parm = + new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT); + parm.addRule(RelativeLayout.CENTER_HORIZONTAL); // ALIGN_PARENT_LEFT + parm.setMarginEnd(10); + parm.setMarginStart(20); + parm.setMargins(20, 40, 0, 0); + userNameView.setLayoutParams(parm); + + TextView remindInfo = (TextView) topContainer.findViewById(R.id.rc_voip_call_remind_info); + CallKitUtils.textViewShadowLayer(remindInfo, MultiVideoCallActivity.this); + setupTime(remindInfo); + + infoLayout = (LinearLayout) topContainer.findViewById(R.id.rc_voip_call_info_layout); + parm = + new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT); + parm.addRule(RelativeLayout.CENTER_HORIZONTAL); + parm.addRule(RelativeLayout.BELOW, userNameView.getId()); + parm.setMargins(0, 8, 0, 0); + infoLayout.setLayoutParams(parm); + + signalView = (ImageView) topContainer.findViewById(R.id.rc_voip_signal); + signalView.setVisibility(View.VISIBLE); + + updateRemoteVideoViews(callSession); + RCRTCEngine.getInstance().enableSpeaker(true); + } + + protected void resetHandFreeStatus(RCAudioRouteType type) { + ImageView handFreeV = null; + if (null != bottomButtonContainer) { + handFreeV = bottomButtonContainer.findViewById(R.id.rc_voip_handfree_btn); + } + if (handFreeV != null) { + // 耳机状态 + if (type == RCAudioRouteType.HEADSET || type == RCAudioRouteType.HEADSET_BLUETOOTH) { + // handFreeV.setSelected(false); + } else { + // 非耳机状态 + handFreeV.setSelected(type == RCAudioRouteType.SPEAKER_PHONE); + } + } + } + + void updateRemoteVideoViews(RongCallSession callSession) { + String remoteUserID = ""; + FrameLayout remoteVideoView = null; + View singleRemoteView = null; + SurfaceView video; + List callUserProfileList = + mUserProfileOrderManager.getSortedProfileList( + callSession.getParticipantProfileList()); + for (CallUserProfile profile : callUserProfileList) { + remoteUserID = profile.getUserId(); + RLog.d( + TAG, + "remoteUserID : " + remoteUserID + " , localViewUserId : " + localViewUserId); + if (remoteUserID.equals(localViewUserId) + || profile.getUserType() == RongCallCommon.CallUserType.OBSERVER) continue; + singleRemoteView = + remoteViewContainer.findViewWithTag( + CallKitUtils.getStitchedContent(remoteUserID, REMOTE_VIEW_TAG)); + if (singleRemoteView == null) { + int userType = 1; + if (callSession.getObserverUserList() != null + && callSession.getObserverUserList().contains(remoteUserID)) { + userType = 2; + } + singleRemoteView = addSingleRemoteView(remoteUserID, userType); + } + video = profile.getVideoView(); + if (video != null) { + remoteVideoView = (FrameLayout) remoteViewContainer.findViewWithTag(remoteUserID); + if (remoteVideoView == null) { + addRemoteVideo(singleRemoteView, video, remoteUserID, false); + } + } + for (StreamProfile streamProfile : profile.streamProfiles) { + if (TextUtils.equals(localViewUserId, streamProfile.streamId)) { + continue; + } + onRemoteUserPublishVideoStream( + streamProfile.userId, + streamProfile.streamId, + streamProfile.tag, + streamProfile.videoView); + } + if (profile.drawed() + || TextUtils.equals( + profile.getUserId(), RongIMClient.getInstance().getCurrentUserId())) { + View statusView = singleRemoteView.findViewById(R.id.user_status); + if (statusView != null) { + statusView.setVisibility(View.GONE); + } + } + } + } + + /** + * 添加 远端视频流 至singleRemoteView 的FrameLayout中,并缓存最新的远端用户头像 + * + * @param userId 自定义流时,传入的是streamID + */ + void addRemoteVideo( + View singleRemoteView, SurfaceView video, String userId, boolean isStreamId) { + if (singleRemoteView == null) return; + String realUserId = userId; + String streamTag = RCRTCStream.RONG_TAG; + if (isStreamId) { + realUserId = Utils.parseUserId(userId); + streamTag = Utils.parseTag(userId); + } + FinLog.d(TAG, "addRemoteVideo realUserId = " + realUserId + " streamTag = " + streamTag); + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(realUserId); + FrameLayout remoteVideoView = + (FrameLayout) singleRemoteView.findViewById(R.id.viewlet_remote_video_user); + + remoteVideoView.removeAllViews(); + ImageView userPortraitView = (ImageView) singleRemoteView.findViewById(R.id.user_portrait); + if (userInfo != null) { + GlideUtils.showPortrait(getBaseContext(), userPortraitView, userInfo.getPortraitUri()); + } + if (video.getParent() != null) { + ((ViewGroup) video.getParent()).removeView(video); + } + video.setTag(CallKitUtils.getStitchedContent(userId, REMOTE_FURFACEVIEW_TAG)); + if (TextUtils.equals(RONG_TAG_CALL, streamTag)) { + ((RCRTCVideoView) video).setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL); + } else { + ((RCRTCVideoView) video).setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT); + } + remoteVideoView.addView( + video, + new FrameLayout.LayoutParams( + remoteUserViewWidth, remoteUserViewWidth, Gravity.CENTER)); + + TextView remoteNameTextView = (TextView) singleRemoteView.findViewById(R.id.user_name); + if (userInfo != null) { + remoteNameTextView.setText(userInfo.getName()); + } else { + remoteNameTextView.setText(userId); + } + CallKitUtils.textViewShadowLayer(remoteNameTextView, MultiVideoCallActivity.this); + remoteNameTextView.setVisibility(View.VISIBLE); + remoteVideoView.setVisibility(View.VISIBLE); + remoteVideoView.setTag(userId); + } + + /** + * 根据userid创建RemoteView显示头像,并添加至远端View容器 + * + * @param userId 用户id + * @param userType 加入用户的类型,1:正常用户,2:观察者。 + * @return + */ + View addSingleRemoteView(String userId, int userType) { + View singleRemoteView = inflater.inflate(R.layout.rc_voip_viewlet_remote_user, null); + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(userId); + singleRemoteView.setTag(CallKitUtils.getStitchedContent(userId, REMOTE_VIEW_TAG)); + TextView userStatus = (TextView) singleRemoteView.findViewById(R.id.user_status); + CallKitUtils.textViewShadowLayer(userStatus, MultiVideoCallActivity.this); + TextView nameView = (TextView) singleRemoteView.findViewById(R.id.user_name); + ImageView userPortraitView = (ImageView) singleRemoteView.findViewById(R.id.user_portrait); + if (userInfo != null) { + GlideUtils.showPortrait(getBaseContext(), userPortraitView, userInfo.getPortraitUri()); + if (!TextUtils.isEmpty(userInfo.getName())) { + nameView.setText(userInfo.getName()); + } + } + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(remoteUserViewWidth, remoteUserViewWidth); + params.setMargins( + 0, + 0, + CallKitUtils.dp2px(remoteUserViewMarginsRight, MultiVideoCallActivity.this), + 0); + remoteViewContainer2.addView(singleRemoteView, params); + if (userType == 2) { + singleRemoteView.setVisibility(View.GONE); + } + return singleRemoteView; + } + + /** + * 根据userid创建每个正常视频用户的RemoteView头像,并添加至远端View容器 观察者不显示头像 + * + * @param userId 用户id + * @param i 控制第一个头像边距位置 + */ + private void createAddSingleRemoteView(String userId, int i) { + View singleRemoteView = inflater.inflate(R.layout.rc_voip_viewlet_remote_user, null); + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(userId); + singleRemoteView.setTag(CallKitUtils.getStitchedContent(userId, REMOTE_VIEW_TAG)); + TextView userStatus = (TextView) singleRemoteView.findViewById(R.id.user_status); + CallKitUtils.textViewShadowLayer(userStatus, MultiVideoCallActivity.this); + ImageView userPortraitView = (ImageView) singleRemoteView.findViewById(R.id.user_portrait); + TextView nameView = (TextView) singleRemoteView.findViewById(R.id.user_name); + if (userInfo != null) { + GlideUtils.showPortrait(getBaseContext(), userPortraitView, userInfo.getPortraitUri()); + if (!TextUtils.isEmpty(userInfo.getName())) { + nameView.setText(userInfo.getName()); + } + } + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(remoteUserViewWidth, remoteUserViewWidth); + if (i == 0) { + params.setMargins( + CallKitUtils.dp2px(remoteUserViewMarginsLeft, MultiVideoCallActivity.this), + 0, + CallKitUtils.dp2px(remoteUserViewMarginsRight, MultiVideoCallActivity.this), + 0); + } else { + params.setMargins( + 0, + 0, + CallKitUtils.dp2px(remoteUserViewMarginsRight, MultiVideoCallActivity.this), + 0); + } + remoteViewContainer2.addView(singleRemoteView, params); + } + + @Override + public void onCallDisconnected( + RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) { + isFinishing = true; + if (reason == null || callSession == null) { + RLog.e(TAG, "onCallDisconnected. callSession is null!"); + postRunnableDelay( + new Runnable() { + @Override + public void run() { + finish(); + } + }); + return; + } + + MultiCallEndMessage multiCallEndMessage = new MultiCallEndMessage(); + multiCallEndMessage.setMediaType(IRongCoreEnum.MediaType.VIDEO); + multiCallEndMessage.setReason(reason); + long serverTime = System.currentTimeMillis() - RongIMClient.getInstance().getDeltaTime(); + IMCenter.getInstance() + .insertIncomingMessage( // + callSession.getConversationType(), // + callSession.getTargetId(), // + callSession.getCallerUserId(), // + CallKitUtils.getReceivedStatus(reason), // + multiCallEndMessage, // + serverTime, // + null); // + cancelTime(); + stopRing(); + postRunnableDelay( + new Runnable() { + @Override + public void run() { + finish(); + } + }); + super.onCallDisconnected(callSession, reason); + sendBroadcast(new Intent(DISCONNECT_ACTION).setPackage(getPackageName())); + } + + @Override + public void onRemoteCameraDisabled(String userId, boolean disabled) { + if (!disabled) { + if (localViewUserId.equals(userId)) { + localView.setBackgroundColor(Color.TRANSPARENT); + } else { + View remoteView = + remoteViewContainer.findViewWithTag( + CallKitUtils.getStitchedContent(userId, REMOTE_FURFACEVIEW_TAG)); + if (remoteView != null) { + remoteView.setBackgroundColor(Color.TRANSPARENT); + } + } + } else { + if (localViewUserId.equals(userId)) { + localView.setBackgroundColor(Color.BLACK); + } else { + View remoteView = + remoteViewContainer.findViewWithTag( + CallKitUtils.getStitchedContent(userId, REMOTE_FURFACEVIEW_TAG)); + if (remoteView != null) { + remoteView.setBackgroundColor(Color.BLACK); + } + } + } + View singleRemoteView = + remoteViewContainer2.findViewWithTag( + CallKitUtils.getStitchedContent(userId, REMOTE_VIEW_TAG)); + if (singleRemoteView != null) { + ImageView userPortraitView = + (ImageView) singleRemoteView.findViewById(R.id.user_portrait); + userPortraitView.setVisibility(disabled ? View.VISIBLE : View.GONE); + TextView tv = (TextView) singleRemoteView.findViewById(R.id.user_name); + tv.setVisibility(View.VISIBLE); + } else { + RLog.e(TAG, "onRemoteCameraDisabled->singleRemoteView is empty"); + } + } + + @Override + public void onNetworkSendLost(int lossRate, int delay) { + super.onNetworkSendLost(lossRate, delay); + } + + @Override + public void onNetworkReceiveLost(String userId, int lossRate) { + final int resId; + if (signalView != null) { + if (lossRate < 5) { + resId = R.drawable.rc_voip_signal_1; + } else if (lossRate < 15) { + resId = R.drawable.rc_voip_signal_2; + } else if (lossRate < 30) { + resId = R.drawable.rc_voip_signal_3; + } else if (lossRate < 45) { + resId = R.drawable.rc_voip_signal_4; + } else { + resId = R.drawable.rc_voip_signal_5; + } + signalView.post( + new Runnable() { + @Override + public void run() { + signalView.setImageResource(resId); + } + }); + } + } + + protected void initViews() { + RLog.i(TAG, "---------- initViews ---------------"); + inflater = LayoutInflater.from(this); + localViewContainer = (ContainerLayout) findViewById(R.id.rc_local_user_view); + remoteViewContainer = (LinearLayout) findViewById(R.id.rc_remote_user_container); + remoteViewContainer2 = (LinearLayout) findViewById(R.id.rc_remote_user_container_2); + topContainer = (LinearLayout) findViewById(R.id.rc_top_container); + topContainer.setVisibility(View.VISIBLE); + waitingContainer = (LinearLayout) findViewById(R.id.rc_waiting_container); + bottomButtonContainer = (LinearLayout) findViewById(R.id.rc_bottom_button_container); + participantPortraitContainer = + (LinearLayout) findViewById(R.id.rc_participant_portait_container); + portraitContainer1 = + (LinearLayout) + participantPortraitContainer.findViewById( + R.id.rc_participant_portait_container_1); + minimizeButton = (ImageView) findViewById(R.id.rc_voip_call_minimize); + rc_voip_multiVideoCall_minimize = + (ImageView) findViewById(R.id.rc_voip_multiVideoCall_minimize); + userPortrait = (ImageView) findViewById(R.id.rc_voip_user_portrait); + moreButton = (ImageView) findViewById(R.id.rc_voip_call_more); + switchCameraButton = (ImageView) findViewById(R.id.rc_voip_switch_camera); + iv_large_preview_mutilvideo = (ImageView) findViewById(R.id.iv_large_preview_mutilvideo); + iv_large_preview_Mask = (ImageView) findViewById(R.id.iv_large_preview_Mask); + + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + remoteUserViewWidth = (metrics.widthPixels - 50) / 4; + + localView = null; + localViewContainer.removeAllViews(); + remoteViewContainer2.removeAllViews(); + portraitContainer1.removeAllViews(); + + minimizeButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + MultiVideoCallActivity.super.onMinimizeClick(v); + } + }); + rc_voip_multiVideoCall_minimize.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + MultiVideoCallActivity.super.onMinimizeClick(v); + } + }); + } + + protected void setupIntent() { + Intent intent = getIntent(); + RongCallAction callAction = RongCallAction.valueOf(intent.getStringExtra("callAction")); + if (callAction == null || callAction.equals(RongCallAction.ACTION_RESUME_CALL)) { + return; + } + ArrayList invitedList = new ArrayList<>(); + ArrayList observerList = new ArrayList<>(); + if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + callSession = intent.getParcelableExtra("callSession"); + // 正常在收到呼叫后,RongCallClient 和 CallSession均不会为空 + if (RongCallClient.getInstance() == null + || RongCallClient.getInstance().getCallSession() == null) { + // 如果为空 表示通话已经结束 但依然启动了本页面,这样会导致页面无法销毁问题 + // 所以 需要在这里 finish 结束当前页面 推荐开发者在结束当前页面前跳转至APP主页或者其他页面 + RLog.e( + TAG, + "MultiVideoCallActivity#setupIntent()->RongCallClient or CallSession is empty---->finish()"); + finish(); + return; + } + + onIncomingCallRinging(callSession); + TextView callRemindInfoView = + (TextView) topContainer.findViewById(R.id.rc_voip_call_remind_info); + TextView userNameView = (TextView) topContainer.findViewById(R.id.rc_voip_user_name); + callRemindInfoView.setText(R.string.rc_voip_video_call_inviting); + if (callSession != null) { + if (!RongCallClient.getInstance().canCallContinued(callSession.getCallId())) { + RLog.w(TAG, "Already received hangup message before, finish current activity"); + ReportUtil.libStatus( + ReportUtil.TAG.ACTIVITYFINISH, "reason", "canCallContinued not"); + finish(); + return; + } + UserInfo userInfo = + RongUserInfoManager.getInstance() + .getUserInfo(callSession.getInviterUserId()); + userNameView.setTag( + CallKitUtils.getStitchedContent( + callSession.getInviterUserId(), VOIP_USERNAME_TAG)); + if (userInfo != null) { + userNameView.setText(CallKitUtils.nickNameRestrict(userInfo.getName())); + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + userInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + userPortrait); + userPortrait.setVisibility(View.VISIBLE); + // + // GlideUtils.showPortrait(MultiVideoCallActivity.this,iv_large_preview_mutilvideo,null!=userInfo?userInfo.getPortraitUri():null); + // iv_large_preview_mutilvideo.setVisibility(View.VISIBLE); + // iv_large_preview_Mask.setVisibility(View.VISIBLE); + } else { + userNameView.setText(callSession.getInviterUserId()); + } + invitedList = getInvitedList(); + RelativeLayout bottomButtonLayout = + (RelativeLayout) + inflater.inflate( + R.layout.rc_voip_call_bottom_incoming_button_layout, null); + ImageView answerV = + (ImageView) bottomButtonLayout.findViewById(R.id.rc_voip_call_answer_btn); + answerV.setImageResource(R.drawable.rc_voip_vedio_answer_selector); + bottomButtonContainer.removeAllViews(); + bottomButtonContainer.addView(bottomButtonLayout); + + for (int i = 0; i < invitedList.size(); i++) { + boolean bool = invitedList.get(i).equals(callSession.getCallerUserId()); + if (bool) { + continue; + } + View userPortraitView = inflater.inflate(R.layout.rc_voip_user_portrait, null); + ImageView portraitView = + (ImageView) userPortraitView.findViewById(R.id.rc_user_portrait); + userInfo = RongUserInfoManager.getInstance().getUserInfo(invitedList.get(i)); + if (userInfo != null) { + GlideUtils.showPortrait( + getBaseContext(), portraitView, userInfo.getPortraitUri()); + } + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + if (i == 0 && !bool) { + params.setMargins( + CallKitUtils.dp2px( + remoteUserViewMarginsLeft, MultiVideoCallActivity.this), + 0, + CallKitUtils.dp2px( + remoteUserViewMarginsRight, MultiVideoCallActivity.this), + 0); + } else { + params.setMargins( + 0, + 0, + CallKitUtils.dp2px( + remoteUserViewMarginsRight, MultiVideoCallActivity.this), + 0); + } + portraitContainer1.addView(userPortraitView, params); + userPortraitView.setTag( + CallKitUtils.getStitchedContent( + invitedList.get(i), VOIP_PARTICIPANT_PORTAIT_CONTAINER_TAG)); + } + } + + topContainer.setVisibility(View.VISIBLE); + minimizeButton.setVisibility(View.GONE); + rc_voip_multiVideoCall_minimize.setVisibility(View.GONE); + moreButton.setVisibility(View.GONE); + switchCameraButton.setVisibility(View.GONE); + waitingContainer.setVisibility(View.GONE); + remoteViewContainer.setVisibility(View.GONE); + participantPortraitContainer.setVisibility(View.VISIBLE); + bottomButtonContainer.setVisibility(View.VISIBLE); + incomingPreview(); + } else if (callAction.equals(RongCallAction.ACTION_OUTGOING_CALL)) { + Conversation.ConversationType conversationType = + Conversation.ConversationType.valueOf( + intent.getStringExtra("conversationType").toUpperCase(Locale.US)); + String targetId = intent.getStringExtra("targetId"); + ArrayList userIds = intent.getStringArrayListExtra("invitedUsers"); + ArrayList observerIds = intent.getStringArrayListExtra("observerUsers"); + if (observerIds != null && observerIds.size() > 0) { + observerList.addAll(observerIds); + } + + for (int i = 0; i < userIds.size(); i++) { + if (!userIds.get(i).equals(RongIMClient.getInstance().getCurrentUserId())) { + invitedList.add(userIds.get(i)); + String userId = userIds.get(i); + if (observerList.size() == 0 || !observerList.contains(userId)) { + createAddSingleRemoteView(userId, i); + } + } + } + + String groupName = ""; + Group group = RongUserInfoManager.getInstance().getGroupInfo(targetId); + if (group != null && !TextUtils.isEmpty(group.getName())) { + groupName = group.getName(); + } + RongCallClient.getInstance() + .setPushConfig( + DefaultPushConfig.getInviteConfig(this, false, false, groupName), + DefaultPushConfig.getHangupConfig(this, false, groupName)); + + RongCallClient.getInstance() + .startCall( + conversationType, + targetId, + invitedList, + observerList, + RongCallCommon.CallMediaType.VIDEO, + "multi"); + FrameLayout bottomButtonLayout; + if (CallKitUtils.findConfigurationLanguage(MultiVideoCallActivity.this, "ar")) { + bottomButtonLayout = + (FrameLayout) + inflater.inflate( + R.layout.rc_voip_multi_video_calling_bottom_view_rtl, null); + bottomButtonLayout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); + } else { + bottomButtonLayout = + (FrameLayout) + inflater.inflate( + R.layout.rc_voip_multi_video_calling_bottom_view, null); + } + + bottomButtonLayout.findViewById(R.id.rc_voip_call_mute).setVisibility(View.GONE); + bottomButtonLayout.findViewById(R.id.rc_voip_disable_camera).setVisibility(View.GONE); + bottomButtonLayout.findViewById(R.id.rc_voip_handfree).setVisibility(View.GONE); + RelativeLayout relativeHangup = bottomButtonLayout.findViewById(R.id.relativeHangup); + if (CallKitUtils.findConfigurationLanguage(MultiVideoCallActivity.this, "ar")) { + relativeHangup.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); + } + + bottomButtonContainer.removeAllViews(); + bottomButtonContainer.addView(bottomButtonLayout); + topContainer.setVisibility(View.GONE); + waitingContainer.setVisibility(View.VISIBLE); + remoteViewContainer.setVisibility(View.VISIBLE); + participantPortraitContainer.setVisibility(View.GONE); + bottomButtonContainer.setVisibility(View.VISIBLE); + rc_voip_multiVideoCall_minimize.setVisibility(View.GONE); + } + } + + /** 挂断通话 */ + public void onHangupBtnClick(View view) { + CallKitUtils.callConnected = false; + if (callSession == null || isFinishing) { + FinLog.e( + TAG, + "hangup call error: callSession=" + + (callSession == null) + + ",isFinishing=" + + isFinishing); + return; + } + stopRing(); + RongCallClient.getInstance().hangUpCall(callSession.getCallId()); + } + + /** 接听通话 */ + public void onReceiveBtnClick(View view) { + if (callSession == null || isFinishing) { + FinLog.e( + TAG, + "hangup call error: callSession=" + + (callSession == null) + + ",isFinishing=" + + isFinishing); + return; + } + RongCallClient.getInstance().acceptCall(callSession.getCallId()); + RongCallClient.getInstance().setEnableLocalAudio(true); + RongCallClient.getInstance().setEnableLocalVideo(true); + stopRing(); + } + + private void addButtionClickEvent() { + setShouldShowFloat(false); + + if (callSession.getConversationType().equals(Conversation.ConversationType.DISCUSSION)) { + RongDiscussionClient.getInstance() + .getDiscussion( + callSession.getTargetId(), + new IRongCoreCallback.ResultCallback() { + @Override + public void onSuccess(Discussion discussion) { + Intent intent = + new Intent( + MultiVideoCallActivity.this, + CallSelectMemberActivity.class); + ArrayList added = new ArrayList<>(); + List list = + RongCallClient.getInstance() + .getCallSession() + .getParticipantProfileList(); + for (CallUserProfile profile : list) { + added.add(profile.getUserId()); + } + List allObserver = + RongCallClient.getInstance() + .getCallSession() + .getObserverUserList(); + intent.putStringArrayListExtra( + "allObserver", new ArrayList<>(allObserver)); + intent.putStringArrayListExtra( + "allMembers", + (ArrayList) discussion.getMemberIdList()); + intent.putStringArrayListExtra("invitedMembers", added); + intent.putExtra( + "conversationType", + callSession.getConversationType().getValue()); + intent.putExtra( + "mediaType", + RongCallCommon.CallMediaType.VIDEO.getValue()); + startActivityForResult(intent, REQUEST_CODE_ADD_MEMBER); + } + + @Override + public void onError(IRongCoreEnum.CoreErrorCode e) {} + }); + } else if (callSession.getConversationType().equals(Conversation.ConversationType.GROUP)) { + Intent intent = new Intent(MultiVideoCallActivity.this, CallSelectMemberActivity.class); + ArrayList added = new ArrayList<>(); + List list = + RongCallClient.getInstance().getCallSession().getParticipantProfileList(); + for (CallUserProfile profile : list) { + added.add(profile.getUserId()); + } + List allObserver = + RongCallClient.getInstance().getCallSession().getObserverUserList(); + intent.putStringArrayListExtra("allObserver", new ArrayList<>(allObserver)); + intent.putStringArrayListExtra("invitedMembers", added); + intent.putExtra("callId", callSession.getCallId()); + intent.putExtra("groupId", callSession.getTargetId()); + intent.putExtra("conversationType", callSession.getConversationType().getValue()); + intent.putExtra("mediaType", RongCallCommon.CallMediaType.VIDEO.getValue()); + startActivityForResult(intent, REQUEST_CODE_ADD_MEMBER); + } else { + ArrayList added = new ArrayList<>(); + List list = + RongCallClient.getInstance().getCallSession().getParticipantProfileList(); + for (CallUserProfile profile : list) { + added.add(profile.getUserId()); + } + addMember(added); + } + } + + public void onMoreButtonClick(View view) { + optionMenu = new CallOptionMenu(MultiVideoCallActivity.this); + optionMenu.setHandUpvisibility( + callSession.getUserType() == RongCallCommon.CallUserType.OBSERVER); + optionMenu.setOnItemClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + int i = v.getId(); + if (i == R.id.voipItemAdd) { + addButtionClickEvent(); + } + optionMenu.dismiss(); + } + }); + optionMenu.showAsDropDown(moreButton, (int) moreButton.getX(), 0); + } + + @Override + protected void onAddMember(List newMemberIds) { + if (newMemberIds == null || newMemberIds.isEmpty()) { + return; + } + ArrayList added = new ArrayList<>(); + List participants = new ArrayList<>(); + List list = + RongCallClient.getInstance().getCallSession().getParticipantProfileList(); + for (CallUserProfile profile : list) { + participants.add(profile.getUserId()); + } + for (String id : newMemberIds) { + if (participants.contains(id)) { + continue; + } else { + added.add(id); + } + } + if (added.isEmpty()) { + return; + } + + RongCallClient.getInstance().addParticipants(callSession.getCallId(), added, null); + } + + public void onSwitchCameraClick(View view) { + RongCallClient.getInstance().switchCamera(); + } + + public void onMuteButtonClick(View view) { + RongCallClient.getInstance().setEnableLocalAudio(view.isSelected()); + view.setSelected(!view.isSelected()); + isMuteMIC = view.isSelected(); + } + + public void onDisableCameraBtnClick(View view) { + TextView text = + (TextView) bottomButtonContainer.findViewById(R.id.rc_voip_disable_camera_text); + String currentUserId = RongIMClient.getInstance().getCurrentUserId(); + + // false:摄像头已关闭 true:摄像头已打开 + boolean isSelected = view.isSelected(); + RongCallClient.getInstance().setEnableLocalVideo(isSelected); + RLog.d( + "onDisableCameraBtnClick", + "isSelected: " + + isSelected + + " ,localViewUserId : " + + localViewUserId + + " , currentUserId : " + + currentUserId); + + if (isSelected) { + text.setText(R.string.rc_voip_disable_camera); + if (localViewUserId.equals(currentUserId)) { + localView.setVisibility(View.VISIBLE); + } else { + remoteViewContainer + .findViewWithTag(currentUserId) + .findViewWithTag( + CallKitUtils.getStitchedContent( + currentUserId, REMOTE_FURFACEVIEW_TAG)) + .setVisibility(View.VISIBLE); + + View singleRemoteView = + remoteViewContainer2.findViewWithTag( + CallKitUtils.getStitchedContent(currentUserId, REMOTE_VIEW_TAG)); + if (singleRemoteView != null) { + ImageView userPortraitView = + (ImageView) singleRemoteView.findViewById(R.id.user_portrait); + if (userPortraitView != null) { + userPortraitView.setVisibility(View.GONE); + } + } + } + } else { + text.setText(R.string.rc_voip_enable_camera); + if (localViewUserId.equals(currentUserId)) { + localView.setVisibility(View.INVISIBLE); + } else { + remoteViewContainer + .findViewWithTag(currentUserId) + .findViewWithTag( + CallKitUtils.getStitchedContent( + currentUserId, REMOTE_FURFACEVIEW_TAG)) + .setVisibility(View.INVISIBLE); + + View singleRemoteView = + remoteViewContainer2.findViewWithTag( + CallKitUtils.getStitchedContent(currentUserId, REMOTE_VIEW_TAG)); + if (singleRemoteView != null) { + ImageView userPortraitView = + (ImageView) singleRemoteView.findViewById(R.id.user_portrait); + if (userPortraitView != null) { + userPortraitView.setVisibility(View.VISIBLE); + } + } + } + } + isMuteCamera = !isSelected; + view.setSelected(isMuteCamera); + } + + /** + * 小窗口FrameLayout点击事件 + * + * @param view + */ + public void onSwitchRemoteUsers(View view) { + String from = (String) view.getTag(); + RLog.i(TAG, "onSwitchRemoteUsers->from = " + from); + if (from == null) return; + String to = (String) localView.getTag(); + RLog.i(TAG, "onSwitchRemoteUsers->to = " + to); + to = to.substring(0, to.length() - REMOTE_FURFACEVIEW_TAG.length()); + FrameLayout frameLayout = (FrameLayout) view; + SurfaceView fromView = (SurfaceView) frameLayout.getChildAt(0); + SurfaceView toSurfaceView = localView; + if (fromView == null || toSurfaceView == null) { + return; + } + // 大屏删除frameLayout和observerLayout + localViewContainer.removeAllViews(); + // 清空小屏装载SurfaceView的FrameLayout + frameLayout.removeAllViews(); + + /** 从远端容器中取出被点击的小屏装载视频流和头像的View,并将头像修改成大屏的 */ + View singleRemoteView = + remoteViewContainer2.findViewWithTag( + CallKitUtils.getStitchedContent(from, REMOTE_VIEW_TAG)); + ImageView userPortraitView = (ImageView) singleRemoteView.findViewById(R.id.user_portrait); + + String fromUid = from; + if (from.contains(RCRTCStream.RONG_TAG) || from.contains(RONG_TAG_CALL)) { + fromUid = Utils.parseUserId(from); + } + String toUid = to; + if (to.contains(RCRTCStream.RONG_TAG) || to.contains(RONG_TAG_CALL)) { + toUid = Utils.parseUserId(to); + } + mUserProfileOrderManager.exchange(from, toUid); + + RLog.i(TAG, "onSwitchRemoteUsers->getUserInfo->fromUid : " + fromUid + " toUid : " + toUid); + UserInfo toUserInfo = RongUserInfoManager.getInstance().getUserInfo(toUid); + UserInfo fromUserInfo = RongUserInfoManager.getInstance().getUserInfo(fromUid); + String toTag = Utils.parseTag(to); + if (TextUtils.equals(toTag, RONG_TAG_CALL)) { + userPortraitView.setVisibility(View.VISIBLE); + } else { + userPortraitView.setVisibility(View.GONE); + } + + if (RongCallClient.getInstance().getCallSession() != null) { + String currentUserId = RongIMClient.getInstance().getCurrentUserId(); + for (CallUserProfile userProfile : + RongCallClient.getInstance().getCallSession().getParticipantProfileList()) { + if (TextUtils.equals(currentUserId, toUid)) { + RLog.e(TAG, "onSwitchRemoteUsers->isMuteCamera: " + isMuteCamera); + userPortraitView.setVisibility(isMuteCamera ? View.VISIBLE : View.GONE); + ImageView imageView = + bottomButtonContainer.findViewById(R.id.rc_voip_disable_camera_btn); + imageView.setSelected(isMuteCamera); + } else if (TextUtils.equals(userProfile.getUserId(), toUid)) { + userPortraitView.setVisibility( + userProfile.isCameraDisabled() ? View.VISIBLE : View.GONE); + } + } + } + + if (toUserInfo != null) { + Uri portraitUri = toUserInfo.getPortraitUri(); + GlideUtils.showPortrait(getBaseContext(), userPortraitView, portraitUri); + RLog.d( + TAG, + "onSwitchRemoteUsers-> getKitImageEngine->PortraitUri: " + + portraitUri.toString() + + " , userPortraitViewVisibility : " + + (userPortraitView.getVisibility() == View.VISIBLE) + + " , wxh: " + + userPortraitView.getWidth() + + " x " + + userPortraitView.getHeight()); + } else { + RLog.e( + TAG, + "onSwitchRemoteUsers-> toUserInfo is empty or userPortraitViewVisibility : " + + userPortraitView.getVisibility()); + } + + fromView.setZOrderOnTop(false); + fromView.setZOrderMediaOverlay(false); + localViewContainer.addView(fromView); // 将点击的小屏视频流添加至本地大容器中 + fromView.setVisibility(View.INVISIBLE); + /** 本地容器添加观察者图层 */ + getObserverLayout(); + localViewContainer.addView(observerLayout); + + if (RongCallClient.getInstance().getCallSession() != null + && RongCallClient.getInstance().getCallSession().getSelfUserId().equals(from) + && callSession.getUserType().getValue() + == RongCallCommon.CallUserType.OBSERVER.getValue()) { + observerLayout.setVisibility(View.VISIBLE); + RLog.d(TAG, "onSwitchRemoteUsers->observerLayout VISIBLE"); + } else { + observerLayout.setVisibility(View.GONE); + } + + /** 将原大屏视频流添加到小屏的FrameLayout上 */ + singleRemoteView.setTag(CallKitUtils.getStitchedContent(to, REMOTE_VIEW_TAG)); + toSurfaceView.setZOrderOnTop(true); + toSurfaceView.setZOrderMediaOverlay(true); + toSurfaceView.setTag(CallKitUtils.getStitchedContent(to, REMOTE_FURFACEVIEW_TAG)); + frameLayout.addView( + toSurfaceView, + new FrameLayout.LayoutParams( + remoteUserViewWidth, remoteUserViewWidth, Gravity.CENTER)); + + TextView tv = (TextView) singleRemoteView.findViewById(R.id.user_name); + if (toUserInfo != null && !TextUtils.isEmpty(toUserInfo.getName())) { + tv.setText(toUserInfo.getName()); + } else { + FinLog.e(TAG, "onSwitchRemoteUsers->toUserInfo or getName is empty"); + tv.setText(to); + } + CallKitUtils.textViewShadowLayer(tv, MultiVideoCallActivity.this); + tv.setVisibility(View.VISIBLE); + + TextView topUserNameView = (TextView) topContainer.findViewById(R.id.rc_voip_user_name); + CallKitUtils.textViewShadowLayer(topUserNameView, MultiVideoCallActivity.this); + + topUserNameView.setTag(CallKitUtils.getStitchedContent(from, VOIP_USERNAME_TAG)); + topUserNameView.setLines(1); + topUserNameView.setEllipsize(TextUtils.TruncateAt.END); + if (fromUserInfo != null && !TextUtils.isEmpty(fromUserInfo.getName())) { + topUserNameView.setText(CallKitUtils.nickNameRestrict(fromUserInfo.getName())); + } else { + topUserNameView.setText(from); + } + topUserName = topUserNameView.getText().toString(); + topUserNameTag = topUserNameView.getTag().toString(); + frameLayout.setTag(to); + localView = fromView; + localView.setTag(CallKitUtils.getStitchedContent(from, REMOTE_FURFACEVIEW_TAG)); + localViewUserId = from; + + Handler handler = new Handler(); + handler.postDelayed( + new Runnable() { + @Override + public void run() { + localView.setVisibility(View.VISIBLE); + } + }, + 30); + } + + @Override + public void onBackPressed() { + return; + } + + @Override + public void onUserUpdate(UserInfo userInfo) { + if (isFinishing() || inflater == null) { + return; + } + if (participantPortraitContainer.getVisibility() == View.VISIBLE) { + View participantView = + participantPortraitContainer.findViewWithTag( + CallKitUtils.getStitchedContent( + userInfo.getUserId(), VOIP_PARTICIPANT_PORTAIT_CONTAINER_TAG)); + if (participantView != null) { + ImageView portraitView = + (ImageView) participantView.findViewById(R.id.rc_user_portrait); + GlideUtils.showPortrait(getBaseContext(), portraitView, userInfo.getPortraitUri()); + } + } + if (remoteViewContainer.getVisibility() == View.VISIBLE) { + View remoteView = + remoteViewContainer.findViewWithTag( + CallKitUtils.getStitchedContent(userInfo.getUserId(), REMOTE_VIEW_TAG)); + if (remoteView != null) { + ImageView portraitView = (ImageView) remoteView.findViewById(R.id.user_portrait); + GlideUtils.showPortrait(getBaseContext(), portraitView, userInfo.getPortraitUri()); + } + } + if (topContainer.getVisibility() == View.VISIBLE) { + TextView nameView = + (TextView) + topContainer.findViewWithTag( + CallKitUtils.getStitchedContent( + userInfo.getUserId(), VOIP_USERNAME_TAG)); + if (nameView != null && userInfo.getName() != null) + nameView.setText(userInfo.getName()); + } + } + + public void onHeadsetPlugUpdate(HeadsetInfo headsetInfo) { + if (headsetInfo == null || !BluetoothUtil.isForground(MultiVideoCallActivity.this)) { + FinLog.v(TAG, "MultiVideoCallActivity is not in the foreground!!"); + return; + } + RLog.i( + TAG, + "Insert=" + + headsetInfo.isInsert() + + ",headsetInfo.getType=" + + headsetInfo.getType().getValue()); + try { + if (headsetInfo.isInsert()) { + RongCallClient.getInstance().setEnableSpeakerphone(false); + ImageView handFreeV = null; + if (null != bottomButtonContainer) { + handFreeV = bottomButtonContainer.findViewById(R.id.rc_voip_handfree_btn); + } + if (handFreeV != null) { + handFreeV.setSelected(false); + handFreeV.setEnabled(false); + handFreeV.setClickable(false); + } + if (headsetInfo.getType() == HeadsetInfo.HeadsetType.BluetoothA2dp) { + AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + am.setMode(AudioManager.MODE_IN_COMMUNICATION); + am.startBluetoothSco(); + am.setBluetoothScoOn(true); + am.setSpeakerphoneOn(false); + } + } else { + if (headsetInfo.getType() == HeadsetInfo.HeadsetType.WiredHeadset + && BluetoothUtil.hasBluetoothA2dpConnected()) { + return; + } + RongCallClient.getInstance().setEnableSpeakerphone(true); + ImageView handFreeV = bottomButtonContainer.findViewById(R.id.rc_voip_handfree_btn); + if (handFreeV != null) { + handFreeV.setSelected(true); + handFreeV.setEnabled(true); + handFreeV.setClickable(true); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private RelativeLayout getObserverLayout() { + observerLayout = (RelativeLayout) inflater.inflate(R.layout.rc_voip_observer_hint, null); + RelativeLayout.LayoutParams param = + new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.MATCH_PARENT); + observerLayout.setGravity(Gravity.CENTER); + observerLayout.setLayoutParams(param); + return observerLayout; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/PickupDetector.java b/callkit/src/main/java/io/rong/callkit/PickupDetector.java new file mode 100644 index 000000000..dae818003 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/PickupDetector.java @@ -0,0 +1,62 @@ +package io.rong.callkit; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +public class PickupDetector { + + private SensorManager manager; + private Sensor mProximitysensor; + + private boolean isPickUp; + private PickupDetectListener listener; + + public PickupDetector(Context context) { + + manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + if (manager != null) { + mProximitysensor = manager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + } + } + + SensorEventListener sensorEventListener = + new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + + if (mProximitysensor == null) return; + + float value = sensorEvent.values[0]; + isPickUp = value < sensorEvent.sensor.getMaximumRange(); + // 打开或者关闭屏幕 + if (listener != null) { + listener.onPickupDetected(isPickUp); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) {} + }; + + public void register(PickupDetectListener listener) { + this.listener = listener; + if (manager != null) { + manager.registerListener( + sensorEventListener, mProximitysensor, SensorManager.SENSOR_DELAY_FASTEST); + } + } + + public void unRegister() { + if (manager != null) { + manager.unregisterListener(sensorEventListener); + } + listener = null; // 释放引用。 + } + + public interface PickupDetectListener { + void onPickupDetected(boolean isPickingUp); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/RongCallAction.java b/callkit/src/main/java/io/rong/callkit/RongCallAction.java new file mode 100644 index 000000000..b2f867297 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/RongCallAction.java @@ -0,0 +1,25 @@ +package io.rong.callkit; + +/** Created by weiqinxiao on 16/3/15. */ +public enum RongCallAction { + ACTION_OUTGOING_CALL(1, "ACTION_OUTGOING_CALL"), + ACTION_INCOMING_CALL(2, "ACTION_INCOMING_CALL"), + ACTION_ADD_MEMBER(3, "ACTION_ADD_MEMBER"), + ACTION_RESUME_CALL(4, "ACTION_RESUME_CALL"); + + int value; + String msg; + + RongCallAction(int v, String msg) { + this.value = v; + this.msg = msg; + } + + public int getValue() { + return value; + } + + public String getName() { + return msg; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/RongCallCustomerHandlerListener.java b/callkit/src/main/java/io/rong/callkit/RongCallCustomerHandlerListener.java new file mode 100644 index 000000000..4f0a49c2e --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/RongCallCustomerHandlerListener.java @@ -0,0 +1,25 @@ +package io.rong.callkit; + +import android.content.Context; +import android.content.Intent; +import android.view.SurfaceView; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import java.util.ArrayList; +import java.util.List; + +public interface RongCallCustomerHandlerListener { + + List handleActivityResult(int requestCode, int resultCode, Intent data); + + void addMember(Context context, ArrayList currentMemberIds); + + void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType); + + void onCallConnected(RongCallSession callSession, SurfaceView localVideo); + + void onCallDisconnected( + RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason); + + void onCallMissed(RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason); +} diff --git a/callkit/src/main/java/io/rong/callkit/RongCallKit.java b/callkit/src/main/java/io/rong/callkit/RongCallKit.java new file mode 100644 index 000000000..769f32cb2 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/RongCallKit.java @@ -0,0 +1,379 @@ +package io.rong.callkit; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.widget.Toast; +import io.rong.callkit.util.RongCallPermissionUtil; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallMissedListener; +import io.rong.calllib.RongCallSession; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.model.Conversation; +import java.util.ArrayList; + +public class RongCallKit { + + public enum CallMediaType { + CALL_MEDIA_TYPE_AUDIO, + CALL_MEDIA_TYPE_VIDEO + } + + public interface ICallUsersProvider { + void onGotUserList(ArrayList userIds); + } + + private static GroupMembersProvider mGroupMembersProvider; + + private static RongCallCustomerHandlerListener customerHandlerListener; + private static GlideCallKitImageEngine kitImageEngine = new GlideCallKitImageEngine(); + + /** + * 发起单人通话。 + * + * @param context 上下文 + * @param targetId 目标会话 id ,单人通话为对方 UserId ,群组通话为 GroupId ,如果实现的是不基于群组的通话,那此参数无意义,传 null 即可 + * @param mediaType 会话媒体类型 + */ + public static void startSingleCall(Context context, String targetId, CallMediaType mediaType) { + startSingleCallInternal(context, targetId, mediaType, RongCallCommon.RoomType.NORMAL); + } + + /** + * 发起单人跨APP通话。 + * + * @param context 上下文 + * @param targetId 目标会话 id ,单人通话为对方 UserId + * @param mediaType 会话媒体类型 + */ + public static void startSingleCrossCall( + Context context, String targetId, CallMediaType mediaType) { + startSingleCallInternal(context, targetId, mediaType, RongCallCommon.RoomType.CROSS); + } + + private static void startSingleCallInternal( + Context context, + String targetId, + CallMediaType mediaType, + RongCallCommon.RoomType roomType) { + String action; + if (mediaType.equals(CallMediaType.CALL_MEDIA_TYPE_AUDIO)) { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEAUDIO; + } else { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEVIDEO; + } + Intent intent = new Intent(action); + intent.putExtra( + "conversationType", Conversation.ConversationType.PRIVATE.getName().toLowerCase()); + intent.putExtra("targetId", targetId); + intent.putExtra("roomType", roomType); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.setPackage(context.getPackageName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + /** + * 发起多人通话 + * + * @param context 上下文 + * @param conversationType 会话类型 + * @param targetId 目标会话 id ,单人通话为对方 UserId ,群组通话为 GroupId ,如果实现的是不基于群组的通话,那此参数无意义,传 null 即可 + * @param mediaType 会话媒体类型 + * @param userIds 参与者 id 列表 + */ + public static void startMultiCall( + Context context, + Conversation.ConversationType conversationType, + String targetId, + CallMediaType mediaType, + ArrayList userIds) { + if (checkEnvironment(context, mediaType)) { + String action; + if (mediaType.equals(CallMediaType.CALL_MEDIA_TYPE_AUDIO)) { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIAUDIO; + } else { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIVIDEO; + } + + Intent intent = new Intent(action); + userIds.add(RongIMClient.getInstance().getCurrentUserId()); + intent.putExtra("conversationType", conversationType.getName().toLowerCase()); + intent.putExtra("targetId", targetId); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.setPackage(context.getPackageName()); + intent.putStringArrayListExtra("invitedUsers", userIds); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + } + + /** + * 开始多人通话。 返回当前会话用户列表提供者对象,用户拿到该对象后,异步从服务器取出当前会话用户列表后, 调用提供者中的 onGotUserList 方法,填充 + * ArrayList userIds 后,就会自动发起多人通话。 + * + * @param context 上下文 + * @param conversationType 会话类型 + * @param targetId 目标会话 id ,单人通话为对方 UserId ,群组通话为 GroupId ,如果实现的是不基于群组的通话,那此参数无意义,传 null 即可 + * @param mediaType 通话的媒体类型:CALL_MEDIA_TYPE_AUDIO, CALL_MEDIA_TYPE_VIDEO + * @return 返回当前会话用户列表提供者对象 + */ + public static ICallUsersProvider startMultiCall( + final Context context, + final Conversation.ConversationType conversationType, + final String targetId, + final CallMediaType mediaType) { + return new ICallUsersProvider() { + @Override + public void onGotUserList(ArrayList userIds) { + String action; + if (mediaType.equals(CallMediaType.CALL_MEDIA_TYPE_AUDIO)) { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIAUDIO; + } else { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIVIDEO; + } + Intent intent = new Intent(action); + userIds.add(RongIMClient.getInstance().getCurrentUserId()); + intent.putExtra("conversationType", conversationType.getName().toLowerCase()); + intent.putExtra("targetId", targetId); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.setPackage(context.getPackageName()); + intent.putStringArrayListExtra("invitedUsers", userIds); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + }; + } + + /** + * 发起的多人通话,不依赖群、讨论组等 + * + * @param context + * @param userIds 邀请的成员 + * @param oberverIds 邀请的以观察者身份加入房间的成员 + * @param mediaType + */ + public static void startMultiCall( + final Context context, + ArrayList userIds, + ArrayList oberverIds, + final CallMediaType mediaType) { + String action; + if (mediaType.equals(CallMediaType.CALL_MEDIA_TYPE_AUDIO)) { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIAUDIO; + } else { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIVIDEO; + } + Intent intent = new Intent(action); + userIds.add(RongIMClient.getInstance().getCurrentUserId()); + intent.putExtra( + "conversationType", Conversation.ConversationType.NONE.getName().toLowerCase()); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.putStringArrayListExtra("invitedUsers", userIds); + intent.putStringArrayListExtra("observerUsers", oberverIds); + intent.setPackage(context.getPackageName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + /** + * 发起的多人通话,不依赖群、讨论组等 + * + *

如何实现不基于于群组的voip + * + * @param context + * @param mediaType + * @return + */ + public static void startMultiCall( + final Context context, ArrayList userIds, final CallMediaType mediaType) { + String action; + if (mediaType.equals(CallMediaType.CALL_MEDIA_TYPE_AUDIO)) { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIAUDIO; + } else { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIVIDEO; + } + Intent intent = new Intent(action); + userIds.add(RongIMClient.getInstance().getCurrentUserId()); + intent.putExtra( + "conversationType", Conversation.ConversationType.NONE.getName().toLowerCase()); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.putStringArrayListExtra("invitedUsers", userIds); + intent.setPackage(context.getPackageName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + /** + * 检查应用音视频授权信息 检查网络连接状态 检查是否在通话中 + * + * @param context 启动的 activity + * @param mediaType 启动音视频的媒体类型 + * @return 是否允许启动通话界面 + */ + private static boolean checkEnvironment(Context context, CallMediaType mediaType) { + if (context instanceof Activity) { + boolean result = + RongCallPermissionUtil.checkPermissionByType( + context, + mediaType == CallMediaType.CALL_MEDIA_TYPE_AUDIO + ? RongCallCommon.CallMediaType.AUDIO + : RongCallCommon.CallMediaType.VIDEO); + if (!result) { + return false; + } + } + + if (isInVoipCall(context)) { + return false; + } + if (!RongIMClient.getInstance() + .getCurrentConnectionStatus() + .equals(RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTED)) { + Toast.makeText( + context, + context.getResources().getString(R.string.rc_voip_call_network_error), + Toast.LENGTH_SHORT) + .show(); + return false; + } + return true; + } + + /** + * 是否在VOIP通话中 + * + * @param context + * @return 是否在VOIP通话中 + */ + public static boolean isInVoipCall(Context context) { + RongCallSession callSession = RongCallClient.getInstance().getCallSession(); + if (callSession != null && callSession.getStartTime() > 0) { + Toast.makeText( + context, + callSession.getMediaType() == RongCallCommon.CallMediaType.AUDIO + ? context.getResources() + .getString(R.string.rc_voip_call_audio_start_fail) + : context.getResources() + .getString(R.string.rc_voip_call_video_start_fail), + Toast.LENGTH_SHORT) + .show(); + return true; + } + return false; + } + + /** 群组成员提供者。 CallKit 本身不保存群组成员,如果在聊天中需要使用群组成员,CallKit 将调用此 Provider 获取群组成员。 */ + public interface GroupMembersProvider { + /** + * 获取群组成员列表,用户根据groupId返回对应的群组成员列表。 + * + * @param groupId 群组id + * @param result getMemberList可以同步返回,也可以异步返回。 同步返回的情况下,直接返回成员列表。 异步返回的情况下,需要在异步返回的时候调用{@link + * OnGroupMembersResult#onGotMemberList(ArrayList)} 来通知CallKit刷新列表。 + * @return 同步返回的时候返回列表,异步返回直接返回null。 + */ + ArrayList getMemberList(String groupId, OnGroupMembersResult result); + } + + /** 群组成员提供者的异步回调接口。 */ + public interface OnGroupMembersResult { + /** + * 群组成员提供者的异步回调接口。 + * + * @param members 成员列表。 + */ + void onGotMemberList(ArrayList members); + } + + /** + * 设置群组成员的提供者。 + * + *

设置后,当 {@link CallSelectMemberActivity} 界面展示群组成员时,会回调 {@link + * GroupMembersProvider#getMemberList(String, OnGroupMembersResult)}, 使用者只需要根据对应的 groupId + * 提供对应的群组成员。 如果需要异步从服务器获取群组成员,使用者可以在此方法中发起异步请求,然后返回 null 信息。 在异步请求结果返回后,根据返回的结果调用 {@link + * OnGroupMembersResult#onGotMemberList(ArrayList)} 刷新信息。 + * + * @param groupMembersProvider 群组成员提供者。 + */ + public static void setGroupMemberProvider(GroupMembersProvider groupMembersProvider) { + mGroupMembersProvider = groupMembersProvider; + } + + /** + * 获取群组成员提供者。 + * + * @return 群组成员提供者。 + */ + public static GroupMembersProvider getGroupMemberProvider() { + return mGroupMembersProvider; + } + + /** + * 设置通话时用户自定义操作监听。 + * + *

CallKit中的Activity是通过action隐式启动,如果用户想继承现有的Activity自定义操作,子类Activity在 + * AndroidManifest.xml声明后启动该Activity时会弹出提示框让用户选择,这个问题解决方式开发者可以直接把 + * callKit/AndroidManifest.xml中对应的Activity声明去掉,此Listener提供了另一种实现方案, + * RongCallCustomerHandlerListener中并没有定义很多方法,开发者如果需要,可以新增自己的方法 + */ + public static void setCustomerHandlerListener( + RongCallCustomerHandlerListener callCustomerHandlerListener) { + customerHandlerListener = callCustomerHandlerListener; + } + + /** 通话过程中用户自定义操作。 */ + public static RongCallCustomerHandlerListener getCustomerHandlerListener() { + return customerHandlerListener; + } + + public static void setRongCallMissedListener( + final RongCallMissedListener rongCallMissedListener) { + RongCallModule.setMissedCallListener(rongCallMissedListener); + } + + // TODO 由于最新CallKit中已经将 RongCallModule#mViewLoaded 默认值改为true,所以不在需要此方法 + // /** + // * 防止 voip 通话页面被会话列表、会话页面或者开发者 app 层页面覆盖。 使用 maven 接入 callkit 的开发者在 app 层主页面的 onCreate + // 调用此方法即可。 + // * 针对导入 callkit 源码的开发者,不使用会话列表和会话页面我们建议在 {@link RongCallModule#onCreate(Context)}方法中设置 + // * mViewLoaded 为 true 即可。 + // */ + // public static void onViewCreated() { + // } + + /** + * 忽略 voip 来电,不弹出来电界面,直接挂断。 + * + * @param ignore true 时忽略来电,false 恢复默认值接收来电,弹出来电界面。 此接口针对音视频会议过程中不能被 voip 打断等的细分场景 + */ + public static void ignoreIncomingCall(boolean ignore) { + RongCallModule.ignoreIncomingCall(ignore); + } + + public static void setMainPageActivityClass(String[] className) { + RongCallModule.setMainPageActivity(className); + } + + public static String getVersion() { + return BuildConfig.VERSION_NAME; + } + + public static void setKitImageEngine(GlideCallKitImageEngine kitImageEngine) { + RongCallKit.kitImageEngine = kitImageEngine; + } + + /** + * 获取自定义头像engine + * + * @return + */ + public static GlideCallKitImageEngine getKitImageEngine() { + return kitImageEngine; + } + + public static GroupMembersProvider getmGroupMembersProvider() { + return mGroupMembersProvider; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/RongCallModule.java b/callkit/src/main/java/io/rong/callkit/RongCallModule.java new file mode 100644 index 000000000..cb6c7d861 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/RongCallModule.java @@ -0,0 +1,485 @@ +package io.rong.callkit; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.Application; +import android.app.Application.ActivityLifecycleCallbacks; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import androidx.fragment.app.Fragment; +import cn.rongcloud.rtc.api.RCRTCAudioRouteManager; +import io.rong.callkit.util.ActivityStartCheckUtils; +import io.rong.callkit.util.CallKitUtils; +import io.rong.calllib.IRongReceivedCallListener; +import io.rong.calllib.ReportUtil; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallMissedListener; +import io.rong.calllib.RongCallSession; +import io.rong.calllib.message.CallSTerminateMessage; +import io.rong.calllib.message.MultiCallEndMessage; +import io.rong.common.RLog; +import io.rong.imkit.IMCenter; +import io.rong.imkit.config.RongConfigCenter; +import io.rong.imkit.conversation.extension.IExtensionModule; +import io.rong.imkit.conversation.extension.RongExtension; +import io.rong.imkit.conversation.extension.component.emoticon.IEmoticonTab; +import io.rong.imkit.conversation.extension.component.plugin.IPluginModule; +import io.rong.imlib.IRongCoreEnum; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.model.Conversation; +import io.rong.imlib.model.Message; +import io.rong.push.RongPushClient; +import io.rong.push.notification.PushNotificationMessage; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** Created by weiqinxiao on 16/8/15. */ +public class RongCallModule implements IExtensionModule { + private static final String TAG = "RongCallModule"; + + private RongCallSession mCallSession; + private boolean mStartForCheckPermissions; + private static boolean mViewLoaded = true; + private Context mContext; + private static RongCallMissedListener missedListener; + private static boolean ignoreIncomingCall; + private Application mApplication; + + public RongCallModule() { + RLog.i(TAG, "Constructor"); + } + + private void initMissedCallListener() { + RongCallClient.setMissedCallListener( + new RongCallMissedListener() { + @Override + public void onRongCallMissed( + RongCallSession callSession, + RongCallCommon.CallDisconnectedReason reason) { + if (!TextUtils.isEmpty(callSession.getInviterUserId())) { + long insertTime = callSession.getEndTime(); + if (insertTime == 0) { + insertTime = callSession.getStartTime(); + } + if (callSession.getConversationType() + == Conversation.ConversationType.PRIVATE) { + CallSTerminateMessage message = new CallSTerminateMessage(); + message.setReason(reason); + message.setMediaType(callSession.getMediaType()); + + String extra; + long time = + (callSession.getEndTime() - callSession.getStartTime()) + / 1000; + if (time >= 3600) { + extra = + String.format( + Locale.ROOT, + "%d:%02d:%02d", + time / 3600, + (time % 3600) / 60, + (time % 60)); + } else { + extra = + String.format( + Locale.ROOT, + "%02d:%02d", + (time % 3600) / 60, + (time % 60)); + } + message.setExtra(extra); + + String senderId = callSession.getInviterUserId(); + if (senderId.equals(callSession.getSelfUserId())) { + message.setDirection("MO"); + IMCenter.getInstance() + .insertOutgoingMessage( + Conversation.ConversationType.PRIVATE, + callSession.getTargetId(), + io.rong.imlib.model.Message.SentStatus.SENT, + message, + insertTime, + null); + } else { + message.setDirection("MT"); + io.rong.imlib.model.Message.ReceivedStatus receivedStatus = + new io.rong.imlib.model.Message.ReceivedStatus(0); + IMCenter.getInstance() + .insertIncomingMessage( + Conversation.ConversationType.PRIVATE, + callSession.getTargetId(), + senderId, + CallKitUtils.getReceivedStatus(reason), + message, + insertTime, + null); + } + } else if (callSession.getConversationType() + == Conversation.ConversationType.GROUP) { + MultiCallEndMessage multiCallEndMessage = new MultiCallEndMessage(); + multiCallEndMessage.setReason(reason); + if (callSession.getMediaType() + == RongCallCommon.CallMediaType.AUDIO) { + multiCallEndMessage.setMediaType(IRongCoreEnum.MediaType.AUDIO); + } else if (callSession.getMediaType() + == RongCallCommon.CallMediaType.VIDEO) { + multiCallEndMessage.setMediaType(IRongCoreEnum.MediaType.VIDEO); + } + IMCenter.getInstance() + .insertIncomingMessage( + callSession.getConversationType(), + callSession.getTargetId(), + callSession.getCallerUserId(), + CallKitUtils.getReceivedStatus(reason), + multiCallEndMessage, + insertTime, + null); + } + } + if (missedListener != null) { + missedListener.onRongCallMissed(callSession, reason); + } + } + }); + } + + public static void setMissedCallListener(RongCallMissedListener listener) { + missedListener = listener; + } + + /** + * 启动通话界面 + * + * @param context 上下文 + * @param callSession 通话实体 + * @param startForCheckPermissions android6.0需要实时获取应用权限。 + * 当需要实时获取权限时,设置startForCheckPermissions为true, 其它情况下设置为false。 + */ + private void startVoIPActivity( + Context context, final RongCallSession callSession, boolean startForCheckPermissions) { + RLog.d( + TAG, + "startVoIPActivity.ignoreIncomingCall : " + + ignoreIncomingCall + + " , AndroidVersion :" + + Build.VERSION.SDK_INT + + " ,startForCheckPermissions : " + + startForCheckPermissions); + if (ignoreIncomingCall) { + RongCallClient.getInstance().hangUpCall(); + return; + } + ReportUtil.appStatus( + ReportUtil.TAG.RECEIVE_CALL_LISTENER, + callSession, + "state|desc", + "startVoIPActivity", + Build.VERSION.SDK_INT); + // 在 Android 10 以上版本不再允许后台运行 Activity + if (Build.VERSION.SDK_INT < 29 || isAppOnForeground(context)) { + context.startActivity(createVoIPIntent(context, callSession, startForCheckPermissions)); + } else { + onSendBroadcast(context, callSession, startForCheckPermissions); + } + mCallSession = null; + } + + private void onSendBroadcast( + Context context, RongCallSession callSession, boolean startForCheckPermissions) { + RLog.d(TAG, "onSendBroadcast"); + Intent intent = new Intent(); + intent.setPackage(context.getPackageName()); + // intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + intent.putExtra("message", transformToPushMessage(context, callSession)); + intent.putExtra("callsession", callSession); + intent.putExtra("checkPermissions", startForCheckPermissions); + intent.setAction(VoIPBroadcastReceiver.ACTION_CALLINVITEMESSAGE); + context.sendBroadcast(intent); + } + + public static Intent createVoIPIntent( + Context context, RongCallSession callSession, boolean startForCheckPermissions) { + Intent intent; + String action; + if (callSession.getConversationType().equals(Conversation.ConversationType.DISCUSSION) + || callSession.getConversationType().equals(Conversation.ConversationType.GROUP) + || callSession.getConversationType().equals(Conversation.ConversationType.NONE)) { + if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.VIDEO)) { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIVIDEO; + } else { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIAUDIO; + } + intent = new Intent(action); + intent.putExtra("callSession", callSession); + intent.putExtra("callAction", RongCallAction.ACTION_INCOMING_CALL.getName()); + if (startForCheckPermissions) { + intent.putExtra("checkPermissions", true); + } else { + intent.putExtra("checkPermissions", false); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setPackage(context.getPackageName()); + } else { + if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.VIDEO)) { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEVIDEO; + } else { + action = RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEAUDIO; + } + intent = new Intent(action); + intent.putExtra("callSession", callSession); + intent.putExtra("callAction", RongCallAction.ACTION_INCOMING_CALL.getName()); + if (startForCheckPermissions) { + intent.putExtra("checkPermissions", true); + } else { + intent.putExtra("checkPermissions", false); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setPackage(context.getPackageName()); + } + return intent; + } + + /** + * 将 RongCallSession 转换为 PushNotificationMessage + * + * @param session + * @return + */ + private PushNotificationMessage transformToPushMessage( + Context context, RongCallSession session) { + PushNotificationMessage pushMsg = new PushNotificationMessage(); + // pushMsg.setPushContent(session.getMediaType() == + // RongCallCommon.CallMediaType.AUDIO ? "音频电话呼叫" : "视频电话呼叫"); + pushMsg.setPushTitle( + (String) + context.getPackageManager() + .getApplicationLabel(context.getApplicationInfo())); + pushMsg.setConversationType( + RongPushClient.ConversationType.setValue(session.getConversationType().getValue())); + pushMsg.setTargetId(session.getTargetId()); + pushMsg.setTargetUserName(""); + pushMsg.setSenderId(session.getCallerUserId()); + pushMsg.setSenderName(""); + pushMsg.setObjectName("RC:VCInvite"); + pushMsg.setPushFlag("false"); + pushMsg.setToId(RongIMClient.getInstance().getCurrentUserId()); + pushMsg.setSourceType(PushNotificationMessage.PushSourceType.LOCAL_MESSAGE); + // pushMsg.setPushId(session.getUId()); + return pushMsg; + } + + /** + * 判断应用是否处于前台 + * + * @param context + * @return + */ + private boolean isAppOnForeground(Context context) { + if (context == null) return false; + ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List appProcesses = + activityManager.getRunningAppProcesses(); + if (appProcesses == null) return false; + String apkName = context.getPackageName(); + + for (ActivityManager.RunningAppProcessInfo app : appProcesses) { + if (TextUtils.equals(apkName, app.processName) + && ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + == app.importance) return true; + } + return false; + } + + public static void ignoreIncomingCall(boolean ignore) { + ignoreIncomingCall = ignore; + } + + @Override + public void onInit(Context context, String appKey) { + RLog.d(TAG, "onInit"); + mContext = context.getApplicationContext(); + registerLifecycleCallbacks(mContext); + RongConfigCenter.conversationConfig().addMessageProvider(new CallEndMessageItemProvider()); + RongConfigCenter.conversationConfig().addMessageProvider(new MultiCallEndMessageProvider()); + initMissedCallListener(); + + IRongReceivedCallListener callListener = + new IRongReceivedCallListener() { + @Override + public void onReceivedCall(final RongCallSession callSession) { + ReportUtil.appStatus( + ReportUtil.TAG.RECEIVED_CALL, + "mViewLoaded|session", + mViewLoaded, + callSession); + RLog.d(TAG, "onReceivedCall.mViewLoaded :" + mViewLoaded); + if (mViewLoaded) { + startVoIPActivity(mContext, callSession, false); + } else { + mCallSession = callSession; + } + RCRTCAudioRouteManager.getInstance().init(mContext.getApplicationContext()); + } + + @Override + public void onCheckPermission(RongCallSession callSession) { + ReportUtil.appStatus( + ReportUtil.TAG.CHECK_PERMISSION, + "mViewLoaded|session", + mViewLoaded, + callSession); + RLog.d(TAG, "onCheckPermissions.mViewLoaded : " + mViewLoaded); + mCallSession = callSession; + if (mViewLoaded) { + startVoIPActivity(mContext, callSession, true); + } else { + mStartForCheckPermissions = true; + } + } + }; + + RongCallClient.setReceivedCallListener(callListener); + ActivityStartCheckUtils.getInstance().registerActivityLifecycleCallbacks(context); + IMCenter.getInstance() + .addConnectStatusListener( + new RongIMClient.ConnectCallback() { + @Override + public void onSuccess(String t) { + if (RongCallClient.getInstance() != null) { + RongCallClient.getInstance() + .setVoIPCallListener(RongCallProxy.getInstance()); + } + } + + @Override + public void onError(RongIMClient.ConnectionErrorCode e) { + if (RongCallClient.getInstance() != null) { + RongCallClient.getInstance() + .setVoIPCallListener(RongCallProxy.getInstance()); + } + } + + @Override + public void onDatabaseOpened(RongIMClient.DatabaseOpenStatus code) {} + }); + } + + private void registerLifecycleCallbacks(Context context) { + RLog.d(TAG, "registerLifecycleCallbacks"); + mApplication = (Application) context; + + if (mApplication == null) { + return; + } + + mApplication.registerActivityLifecycleCallbacks(myActivityLifecycleCallbacks); + } + + private ActivityLifecycleCallbacks myActivityLifecycleCallbacks = + new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + RLog.d(TAG, "onActivityCreated ---- : " + activity); + + if (mActivities == null || mActivities.size() == 0) { + RLog.d(TAG, "onActivityCreated . mainPageClass is empty."); + return; + } + String className1 = activity.getClass().getName(); + mActivities.remove(className1); + if (mActivities.size() == 0) { + retryStartVoIPActivity(); + } + } + + @Override + public void onActivityStarted(Activity activity) {} + + @Override + public void onActivityResumed(Activity activity) {} + + @Override + public void onActivityPaused(Activity activity) {} + + @Override + public void onActivityStopped(Activity activity) {} + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + + @Override + public void onActivityDestroyed(Activity activity) {} + }; + + private void retryStartVoIPActivity() { + RLog.i( + TAG, + "Find the exact class, change mViewLoaded as true . mCallSession ==null ?" + + (mCallSession == null)); + mViewLoaded = true; + if (mCallSession != null) { + startVoIPActivity(mContext, mCallSession, mStartForCheckPermissions); + mStartForCheckPermissions = false; + } + } + + @Override + public void onAttachedToExtension(Fragment fragment, RongExtension extension) { + RLog.d(TAG, "onAttachedToExtension"); + } + + @Override + public void onDetachedFromExtension() { + RLog.d(TAG, "onDetachedFromExtension"); + } + + @Override + public void onReceivedMessage(Message message) {} + + @Override + public List getPluginModules(Conversation.ConversationType conversationType) { + RLog.d(TAG, "getPluginModules"); + List pluginModules = new ArrayList<>(); + try { + if (RongCallClient.getInstance().isVoIPEnabled(mContext)) { + pluginModules.add(new AudioPlugin()); + pluginModules.add(new VideoPlugin()); + } + } catch (Exception e) { + e.printStackTrace(); + RLog.i(TAG, "getPlugins()->Error :" + e.getMessage()); + } + return pluginModules; + } + + @Override + public List getEmoticonTabs() { + return null; + } + + @Override + public void onDisconnect() { + RLog.d(TAG, "onDisconnect"); + } + + private static ArrayList mActivities; + + /** 设置可能会覆盖音视频通话页面的类,比如主页面。设置后,如果此页面尚未打开,即使收到音视频呼叫,也会暂缓唤起页面,会再设置的页面启动成功后,再尝试启动音视频通话页面。 */ + public static void setMainPageActivity(String[] className) { + if (className != null && className.length > 0) { + int length = className.length; + RLog.i(TAG, "setMainPageActivity.length :" + length); + mActivities = new ArrayList<>(); + mViewLoaded = false; + for (int i = 0; i < length; i++) { + mActivities.add(className[i]); + } + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/RongCallProxy.java b/callkit/src/main/java/io/rong/callkit/RongCallProxy.java new file mode 100644 index 000000000..5158d533f --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/RongCallProxy.java @@ -0,0 +1,422 @@ +package io.rong.callkit; + +import android.text.TextUtils; +import android.view.SurfaceView; +import cn.rongcloud.rtc.api.RCRTCAudioRouteManager; +import io.rong.callkit.util.CallKitUtils; +import io.rong.callkit.util.IncomingCallExtraHandleUtil; +import io.rong.calllib.IRongCallListener; +import io.rong.calllib.ReportUtil; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallCommon.CallMediaType; +import io.rong.calllib.RongCallSession; +import io.rong.calllib.message.CallSTerminateMessage; +import io.rong.calllib.message.MultiCallEndMessage; +import io.rong.common.RLog; +import io.rong.imkit.IMCenter; +import io.rong.imlib.IRongCoreEnum; +import io.rong.imlib.model.Conversation; +import java.util.HashMap; +import java.util.Locale; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; + +/** Created by jiangecho on 2016/10/27. */ +public class RongCallProxy implements IRongCallListener { + + private static final String TAG = "RongCallProxy"; + private IRongCallListener mCallListener; + private Queue mCachedCallQueue; + private static RongCallProxy mInstance; + + private RongCallProxy() { + mCachedCallQueue = new LinkedBlockingQueue<>(); + } + + public static synchronized RongCallProxy getInstance() { + if (mInstance == null) { + mInstance = new RongCallProxy(); + } + return mInstance; + } + + public void setCallListener(IRongCallListener listener) { + RLog.d(TAG, "setCallListener listener = " + listener); + this.mCallListener = listener; + // if (listener != null) { + // CallDisconnectedInfo callDisconnectedInfo = mCachedCallQueue.poll(); + // if (callDisconnectedInfo != null) { + // listener.onCallDisconnected(callDisconnectedInfo.mCallSession, + // callDisconnectedInfo.mReason); + // } + // } + } + + @Override + public void onCallIncoming(RongCallSession callSession, SurfaceView localVideo) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + callSession, + "state|desc", + "onCallIncoming", + getDescription()); + if (mCallListener != null) { + mCallListener.onCallIncoming(callSession, localVideo); + } + if (RongCallClient.getInstance().getContext() != null) { + RCRTCAudioRouteManager.getInstance() + .init(RongCallClient.getInstance().getContext().getApplicationContext()); + } + } + + @Override + public void onCallOutgoing(RongCallSession callSession, SurfaceView localVideo) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + callSession, + "state|desc", + "onCallOutgoing", + getDescription()); + if (mCallListener != null) { + mCallListener.onCallOutgoing(callSession, localVideo); + } + if (RongCallClient.getInstance().getContext() != null) { + RCRTCAudioRouteManager.getInstance() + .init(RongCallClient.getInstance().getContext().getApplicationContext()); + } + } + + @Override + public void onCallConnected(RongCallSession callSession, SurfaceView localVideo) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + callSession, + "state|desc", + "onCallConnected", + getDescription()); + if (mCallListener != null) { + mCallListener.onCallConnected(callSession, localVideo); + } + } + + @Override + public void onCallDisconnected( + RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) { + RLog.d(TAG, "RongCallProxy onCallDisconnected mCallListener = " + mCallListener); + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + callSession, + "state|reason|desc", + "onCallDisconnected", + reason.getValue(), + getDescription()); + if (mCallListener != null) { + mCallListener.onCallDisconnected(callSession, reason); + } else if (!IncomingCallExtraHandleUtil.needNotify()) { + mCachedCallQueue.offer(new CallDisconnectedInfo(callSession, reason)); + } else { // android 10 后台来电,被叫端不响应,主叫挂断时 mCallListener 为空 ,需要生成通话记录 + insertCallLogMessage(callSession, reason); + } + // 取消耳机监听 + RCRTCAudioRouteManager.getInstance().unInit(); + } + + @Override + public void onRemoteUserRinging(String userId) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserRinging", + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteUserRinging(userId); + } + } + + @Override + public void onRemoteUserAccept(String userId, CallMediaType mediaType) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserAccept", + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteUserAccept(userId, mediaType); + } + } + + @Override + public void onRemoteUserJoined( + String userId, + RongCallCommon.CallMediaType mediaType, + int userType, + SurfaceView remoteVideo) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserJoined", + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteUserJoined(userId, mediaType, userType, remoteVideo); + } + } + + @Override + public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onRemoteUserInvited", + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteUserInvited(userId, mediaType); + } + } + + @Override + public void onRemoteUserLeft(String userId, RongCallCommon.CallDisconnectedReason reason) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|reason|desc", + userId, + "onRemoteUserLeft", + reason.getValue(), + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteUserLeft(userId, reason); + } + } + + @Override + public void onMediaTypeChanged( + String userId, RongCallCommon.CallMediaType mediaType, SurfaceView video) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|mediaType|desc", + userId, + "onMediaTypeChanged", + mediaType.getValue(), + getDescription()); + if (mCallListener != null) { + mCallListener.onMediaTypeChanged(userId, mediaType, video); + } + } + + @Override + public void onError(RongCallCommon.CallErrorCode errorCode) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "state|code|desc", + "onError", + errorCode.getValue(), + getDescription()); + if (mCallListener != null) { + mCallListener.onError(errorCode); + } + } + + @Override + public void onRemoteCameraDisabled(String userId, boolean disabled) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|disabled|desc", + userId, + "onRemoteCameraDisabled", + disabled, + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteCameraDisabled(userId, disabled); + } + } + + @Override + public void onRemoteMicrophoneDisabled(String userId, boolean disabled) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|disabled|desc", + userId, + "onRemoteMicrophoneDisabled", + disabled, + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteMicrophoneDisabled(userId, disabled); + } + } + + @Override + public void onNetworkSendLost(int lossRate, int delay) { + if (mCallListener != null) { + mCallListener.onNetworkSendLost(lossRate, delay); + } + } + + @Override + public void onFirstRemoteVideoFrame(String userId, int height, int width) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onFirstRemoteVideoFrame", + getDescription()); + if (mCallListener != null) { + mCallListener.onFirstRemoteVideoFrame(userId, height, width); + } + } + + @Override + public void onFirstRemoteAudioFrame(String userId) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|desc", + userId, + "onFirstRemoteAudioFrame", + getDescription()); + if (mCallListener != null) { + mCallListener.onFirstRemoteAudioFrame(userId); + } + } + + @Override + public void onAudioLevelSend(String audioLevel) { + if (mCallListener != null) { + mCallListener.onAudioLevelSend(audioLevel); + } + } + + public void onRemoteUserPublishVideoStream( + String userId, String streamId, String tag, SurfaceView surfaceView) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|streamId|desc", + userId, + "onRemoteUserPublishVideoStream", + streamId, + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteUserPublishVideoStream(userId, streamId, tag, surfaceView); + } + } + + @Override + public void onAudioLevelReceive(HashMap audioLevel) { + if (mCallListener != null) { + mCallListener.onAudioLevelReceive(audioLevel); + } + } + + public void onRemoteUserUnpublishVideoStream(String userId, String streamId, String tag) { + ReportUtil.appStatus( + ReportUtil.TAG.CALL_LISTENER, + "userId|state|streamId|desc", + userId, + "onRemoteUserUnpublishVideoStream", + streamId, + getDescription()); + if (mCallListener != null) { + mCallListener.onRemoteUserUnpublishVideoStream(userId, streamId, tag); + } + } + + @Override + public void onNetworkReceiveLost(String userId, int lossRate) { + if (mCallListener != null) { + mCallListener.onNetworkReceiveLost(userId, lossRate); + } + } + + private static class CallDisconnectedInfo { + RongCallSession mCallSession; + RongCallCommon.CallDisconnectedReason mReason; + + public CallDisconnectedInfo( + RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) { + this.mCallSession = callSession; + this.mReason = reason; + } + } + + private String getDescription() { + if (mCallListener != null) { + return mCallListener.getClass().getSimpleName(); + } + return "no callListener set"; + } + + private void insertCallLogMessage( + RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) { + if (!TextUtils.isEmpty(callSession.getInviterUserId())) { + long insertTime = callSession.getEndTime(); + if (insertTime == 0) { + insertTime = callSession.getStartTime(); + } + if (callSession.getConversationType() == Conversation.ConversationType.PRIVATE) { + CallSTerminateMessage message = new CallSTerminateMessage(); + message.setReason(reason); + message.setMediaType(callSession.getMediaType()); + + String extra; + long time = (callSession.getEndTime() - callSession.getStartTime()) / 1000; + if (time >= 3600) { + extra = + String.format( + Locale.ROOT, + "%d:%02d:%02d", + time / 3600, + (time % 3600) / 60, + (time % 60)); + } else { + extra = + String.format( + Locale.ROOT, "%02d:%02d", (time % 3600) / 60, (time % 60)); + } + message.setExtra(extra); + + String senderId = callSession.getInviterUserId(); + if (senderId.equals(callSession.getSelfUserId())) { + message.setDirection("MO"); + IMCenter.getInstance() + .insertOutgoingMessage( + Conversation.ConversationType.PRIVATE, + callSession.getTargetId(), + io.rong.imlib.model.Message.SentStatus.SENT, + message, + insertTime, + null); + } else { + message.setDirection("MT"); + IMCenter.getInstance() + .insertIncomingMessage( + Conversation.ConversationType.PRIVATE, + callSession.getTargetId(), + senderId, + CallKitUtils.getReceivedStatus(reason), + message, + insertTime, + null); + } + } else if (callSession.getConversationType() == Conversation.ConversationType.GROUP) { + MultiCallEndMessage multiCallEndMessage = new MultiCallEndMessage(); + multiCallEndMessage.setReason(reason); + if (callSession.getMediaType() == RongCallCommon.CallMediaType.AUDIO) { + multiCallEndMessage.setMediaType(IRongCoreEnum.MediaType.AUDIO); + } else if (callSession.getMediaType() == RongCallCommon.CallMediaType.VIDEO) { + multiCallEndMessage.setMediaType(IRongCoreEnum.MediaType.VIDEO); + } + IMCenter.getInstance() + .insertIncomingMessage( + callSession.getConversationType(), + callSession.getTargetId(), + callSession.getCallerUserId(), + CallKitUtils.getReceivedStatus(reason), + multiCallEndMessage, + insertTime, + null); + } + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/RongIncomingCallService.java b/callkit/src/main/java/io/rong/callkit/RongIncomingCallService.java new file mode 100644 index 000000000..23ca4e65d --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/RongIncomingCallService.java @@ -0,0 +1,317 @@ +package io.rong.callkit; + +import android.annotation.SuppressLint; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.os.SystemClock; +import android.view.View; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import io.rong.callkit.util.CallRingingUtil; +import io.rong.callkit.util.IncomingCallExtraHandleUtil; +import io.rong.callkit.util.RingingMode; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallSession; +import io.rong.common.RLog; +import io.rong.push.notification.PushNotificationMessage; +import java.util.concurrent.atomic.AtomicBoolean; + +/** @author gusd @Date 2021/09/01 */ +public class RongIncomingCallService { + private static final String TAG = "IncomingCallService"; + public static final int ACCEPT_REQUEST_CODE = 145679; + public static final int HANGUP_REQUEST_CODE = 145678; + private static int notificationId = 4000; + + public static final String KEY_MESSAGE = "message"; + public static final String KEY_CALL_SESSION = "callsession"; + public static final String KEY_CHECK_PERMISSIONS = "checkPermissions"; + public static final String KEY_NEED_AUTO_ANSWER = "needAutoAnswer"; + + /** 该服务最长存活时间,60 秒 */ + private static final long SERVICE_MAX_ALIVE_TIME = 60 * 1000L; + + private static final String TAG_KILL_INCOMING_SERVICE = "TAG_KILL_INCOMING_SERVICE"; + + public static final String ACTION_CALLINVITEMESSAGE_CLICKED = + "action.push.CallInviteMessage.CLICKED"; + + private Handler mHandler; + private AtomicBoolean isRinging = new AtomicBoolean(false); + + private RongIncomingCallService() {} + + public static RongIncomingCallService getInstance() { + return RongIncomingCallHolder.instance; + } + + private static class RongIncomingCallHolder { + static RongIncomingCallService instance = new RongIncomingCallService(); + } + + public boolean isRinging() { + return isRinging.get(); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + public void startRing(final Context context, Intent intent) { + RLog.d(TAG, "onStartCommand: "); + if (isRinging.get()) { + return; + } + + PushNotificationMessage message = intent.getParcelableExtra(KEY_MESSAGE); + RongCallSession callSession = intent.getParcelableExtra(KEY_CALL_SESSION); + boolean checkPermission = intent.getBooleanExtra(KEY_CHECK_PERMISSIONS, false); + if (message == null || callSession == null) { + return; + } + RLog.d(TAG, "onStartCommand : " + "callId = " + callSession.getCallId()); + + mHandler = new Handler(Looper.myLooper()); + mHandler.postAtTime( + new Runnable() { + @Override + public void run() { + stopRinging(context); + } + }, + TAG_KILL_INCOMING_SERVICE, + SystemClock.uptimeMillis() + SERVICE_MAX_ALIVE_TIME); + + wakeUpAndUnlock(context); + + try { + PendingIntent answerPendingIntent = + createAnswerIntent( + context, + message, + callSession, + checkPermission, + IncomingCallExtraHandleUtil.VOIP_REQUEST_CODE, + false); + PendingIntent hangupPendingIntent = createHangupIntent(context, callSession); + PendingIntent openAppIntent = + createOpenAppPendingIntent( + context, + message, + callSession, + checkPermission, + IncomingCallExtraHandleUtil.VOIP_REQUEST_CODE, + false); + CallRingingUtil.getInstance().createNotificationChannel(context); + + int smallIcon = + context.getResources() + .getIdentifier( + "notification_small_icon", + "drawable", + context.getPackageName()); + if (smallIcon <= 0) { + smallIcon = context.getApplicationInfo().icon; + } + androidx.media.app.NotificationCompat.MediaStyle mediaStyle = + new androidx.media.app.NotificationCompat.MediaStyle(); + mediaStyle.setShowCancelButton(false); + mediaStyle.setCancelButtonIntent(hangupPendingIntent); + mediaStyle.setShowActionsInCompactView(0, 1); + + NotificationCompat.Builder notificationBuilder = + new NotificationCompat.Builder( + context, + CallRingingUtil.getInstance().getNotificationChannelId()) + .setContentText(message.getPushContent()) + .setContentTitle(message.getPushTitle()) + .setSmallIcon(smallIcon) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setCategory(NotificationCompat.CATEGORY_CALL) + .setAutoCancel(true) + .setOngoing(true) + .setStyle(mediaStyle) + .setContentIntent(openAppIntent) + .setFullScreenIntent(openAppIntent, true); + + int notificationHangupIcon = CallRingingUtil.getInstance().getNotificationHangupIcon(); + if (notificationHangupIcon != View.NO_ID && notificationHangupIcon != 0) { + notificationBuilder.addAction( + R.drawable.rc_voip_notification_hangup, + context.getString(R.string.rc_voip_hangup), + hangupPendingIntent); + } + + int notificationAnswerIcon = CallRingingUtil.getInstance().getNotificationAnswerIcon(); + if (notificationAnswerIcon != View.NO_ID && notificationAnswerIcon != 0) { + notificationBuilder.addAction( + R.drawable.rc_voip_notification_answer, + context.getString(R.string.rc_voip_answer), + answerPendingIntent); + } + + Notification notification = notificationBuilder.build(); + + NotificationManager notificationManager = + context.getSystemService(NotificationManager.class); + if (notificationManager != null) { + notificationManager.notify(++notificationId, notification); + } + CallRingingUtil.getInstance().startRinging(context, RingingMode.Incoming); + isRinging.set(true); + } catch (Exception e) { + RLog.e(TAG, "onStartCommand = " + e.getMessage()); + e.printStackTrace(); + } + } + + public void stopRinging(Context context) { + isRinging.set(false); + CallRingingUtil.getInstance().stopRinging(); + try { + NotificationManager notificationManager = null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + notificationManager = context.getSystemService(NotificationManager.class); + } + if (notificationManager != null) { + notificationManager.cancel(notificationId); + } + } catch (Exception e) { + e.printStackTrace(); + } + Handler handler = mHandler; + if (handler != null) { + handler.removeCallbacksAndMessages(TAG_KILL_INCOMING_SERVICE); + } + } + + private PendingIntent createOpenAppPendingIntent( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions, + int requestCode, + boolean isMulti) { + Intent intent = + createOpenAppIntent( + context, message, callSession, checkPermissions, requestCode, isMulti); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return PendingIntent.getActivity( + context, 2314412, intent, PendingIntent.FLAG_IMMUTABLE); + } else { + return PendingIntent.getBroadcast( + context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + } + + private Intent createOpenAppIntent( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions, + int requestCode, + boolean isMulti) { + return createIntentForAndroidS(context, callSession, checkPermissions); + } + + private PendingIntent createAnswerIntent( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions, + int requestCode, + boolean isMulti) { + Intent intent = + createOpenAppIntent( + context, message, callSession, checkPermissions, requestCode, isMulti); + intent.putExtra(KEY_NEED_AUTO_ANSWER, callSession.getCallId()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return PendingIntent.getActivity( + context, 12345664, intent, PendingIntent.FLAG_IMMUTABLE); + } else { + intent.setClass(context, VoIPBroadcastReceiver.class); + return PendingIntent.getBroadcast( + context, ACCEPT_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + } + + private Intent createIntentForAndroidS( + Context context, RongCallSession callSession, boolean checkPermissions) { + Intent intent; + // 如果进程被杀 RongCallClient.getInstance() 返回Null + if (RongCallClient.getInstance() != null + && RongCallClient.getInstance().getCallSession() != null + && callSession != null) { + intent = RongCallModule.createVoIPIntent(context, callSession, checkPermissions); + io.rong.push.common.RLog.d(TAG, "handleNotificationClickEvent: start call activity"); + } else { + intent = createConversationListIntent(context); + io.rong.push.common.RLog.d( + TAG, "handleNotificationClickEvent: start conversation activity"); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setPackage(context.getPackageName()); + return intent; + } + + private static Intent createConversationListIntent(Context context) { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Uri uri = + Uri.parse("rong://" + context.getPackageName()) + .buildUpon() + .appendPath("conversationlist") + .build(); + intent.setData(uri); + intent.setPackage(context.getPackageName()); + return intent; + } + + private PendingIntent createHangupIntent(Context context, RongCallSession callSession) { + Intent hangupIntent = new Intent(); + hangupIntent.setAction(VoIPBroadcastReceiver.ACTION_CALL_HANGUP_CLICKED); + hangupIntent.putExtra(KEY_CALL_SESSION, callSession); + hangupIntent.setPackage(context.getPackageName()); + hangupIntent.setClass(context, VoIPBroadcastReceiver.class); + // KNOTE: 2021/9/29 PendingIntent + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return PendingIntent.getBroadcast( + context, + HANGUP_REQUEST_CODE, + hangupIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + return PendingIntent.getBroadcast( + context, HANGUP_REQUEST_CODE, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT); + } + } + + // 唤醒屏幕并解锁 + public void wakeUpAndUnlock(Context context) { + try { + KeyguardManager km = + (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + KeyguardManager.KeyguardLock kl = km.newKeyguardLock("unLock"); + // 获取电源管理器对象 + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + // 获取PowerManager.WakeLock对象,后面的参数|表示同时传入两个值,最后的是LogCat里用的Tag + @SuppressLint("InvalidWakeLockTag") + PowerManager.WakeLock wl = + pm.newWakeLock( + PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK, + "bright"); + // 点亮屏幕 + wl.acquire(5000); + // 释放 + wl.release(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/RongVoIPIntent.java b/callkit/src/main/java/io/rong/callkit/RongVoIPIntent.java new file mode 100644 index 000000000..9c1df929d --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/RongVoIPIntent.java @@ -0,0 +1,19 @@ +package io.rong.callkit; + +/** Created by weiqinxiao on 16/3/18. */ +public class RongVoIPIntent { + public static final String RONG_INTENT_VOIP_CATEGORY = "io.rong.intent.category.voip"; + + public static final String RONG_INTENT_ACTION_VOIP_MULTIAUDIO = + "io.rong.intent.action.voip.MULTIAUDIO"; + public static final String RONG_INTENT_ACTION_VOIP_MULTIVIDEO = + "io.rong.intent.action.voip.MULTIVIDEO"; + public static final String RONG_INTENT_ACTION_VOIP_SINGLEAUDIO = + "io.rong.intent.action.voip.SINGLEAUDIO"; + public static final String RONG_INTENT_ACTION_VOIP_SINGLEVIDEO = + "io.rong.intent.action.voip.SINGLEVIDEO"; + public static final String RONG_INTENT_ACTION_VOIP_INIT = "io.rong.intent.action.SDK_INIT"; + public static final String RONG_INTENT_ACTION_VOIP_UI_READY = "io.rong.intent.action.UI_READY"; + public static final String RONG_INTENT_ACTION_VOIP_CONNECTED = + "io.rong.intent.action.SDK_CONNECTED"; +} diff --git a/callkit/src/main/java/io/rong/callkit/SingleCallActivity.java b/callkit/src/main/java/io/rong/callkit/SingleCallActivity.java new file mode 100644 index 000000000..f578e6382 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/SingleCallActivity.java @@ -0,0 +1,1336 @@ +package io.rong.callkit; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.SoundPool; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.yunbao.common.utils.ToastUtil; + +import cn.rongcloud.rtc.api.RCRTCEngine; +import cn.rongcloud.rtc.audioroute.RCAudioRouteType; +import cn.rongcloud.rtc.utils.FinLog; +import io.rong.callkit.util.BluetoothUtil; +import io.rong.callkit.util.CallKitUtils; +import io.rong.callkit.util.DefaultPushConfig; +import io.rong.callkit.util.GlideUtils; +import io.rong.callkit.util.HeadsetInfo; +import io.rong.callkit.util.RingingMode; +import io.rong.callkit.util.RongCallPermissionUtil; +import io.rong.calllib.CallUserProfile; +import io.rong.calllib.ReportUtil; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallCommon.RoomType; +import io.rong.calllib.RongCallSession; +import io.rong.calllib.StartIncomingPreviewCallback; +import io.rong.calllib.message.CallSTerminateMessage; +import io.rong.common.RLog; +import io.rong.imkit.IMCenter; +import io.rong.imkit.userinfo.RongUserInfoManager; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.model.Conversation; +import io.rong.imlib.model.UserInfo; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class SingleCallActivity extends BaseCallActivity implements Handler.Callback { + private static final String TAG = "VoIPSingleActivity"; + private static final int LOSS_RATE_ALARM = 20; + private LayoutInflater inflater; + private RongCallSession callSession; + private RelativeLayout mLPreviewContainer; + private FrameLayout mSPreviewContainer; + private FrameLayout mButtonContainer; + private LinearLayout mUserInfoContainer; + private TextView mConnectionStateTextView; + private Boolean isInformationShow = false; + private SurfaceView mLocalVideo = null; + private boolean muted = false; + private boolean handFree = false; + private boolean startForCheckPermissions = false; + private boolean isReceiveLost = false; + private boolean isSendLost = false; + private SoundPool mSoundPool = null; + + private int EVENT_FULL_SCREEN = 1; + + private String targetId = null; + private RongCallCommon.CallMediaType mediaType; + + @Override + public final boolean handleMessage(Message msg) { + if (msg.what == EVENT_FULL_SCREEN) { + hideVideoCallInformation(); + return true; + } + return false; + } + + @Override + @TargetApi(23) + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.rc_voip_activity_single_call); + RLog.i( + "AudioPlugin", + "savedInstanceState != null=" + + (savedInstanceState != null) + + ",,,RongCallClient.getInstance() == null" + + (RongCallClient.getInstance() == null)); + if (savedInstanceState != null && RongCallClient.getInstance() == null) { + // 音视频请求权限时,用户在设置页面取消权限,导致应用重启,退出当前activity. + RLog.i("AudioPlugin", "音视频请求权限时,用户在设置页面取消权限,导致应用重启,退出当前activity"); + finish(); + return; + } + Intent intent = getIntent(); + mLPreviewContainer = (RelativeLayout) findViewById(R.id.rc_voip_call_large_preview); + mSPreviewContainer = (FrameLayout) findViewById(R.id.rc_voip_call_small_preview); + mButtonContainer = (FrameLayout) findViewById(R.id.rc_voip_btn); + mUserInfoContainer = (LinearLayout) findViewById(R.id.rc_voip_user_info); + mConnectionStateTextView = findViewById(R.id.rc_tv_connection_state); + + if (CallKitUtils.findConfigurationLanguage(SingleCallActivity.this, "ar")) { + // android:layout_gravity="right|top" + FrameLayout.LayoutParams params = (LayoutParams) mSPreviewContainer.getLayoutParams(); + params.gravity = Gravity.LEFT | Gravity.TOP; + mSPreviewContainer.setLayoutParams(params); + } + + startForCheckPermissions = intent.getBooleanExtra("checkPermissions", false); + RongCallAction callAction = RongCallAction.valueOf(intent.getStringExtra("callAction")); + + String receivedCallId = ""; + if (callAction.equals(RongCallAction.ACTION_OUTGOING_CALL)) { + if (intent.getAction().equals(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEAUDIO)) { + mediaType = RongCallCommon.CallMediaType.AUDIO; + } else { + mediaType = RongCallCommon.CallMediaType.VIDEO; + } + } else if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + callSession = intent.getParcelableExtra("callSession"); + mediaType = callSession.getMediaType(); + receivedCallId = callSession.getCallId(); + // 正常在收到呼叫后,RongCallClient 和 CallSession均不会为空 + if (RongCallClient.getInstance() == null + || RongCallClient.getInstance().getCallSession() == null) { + // 如果为空 表示通话已经结束 但依然启动了本页面,这样会导致页面无法销毁问题 + // 所以 需要在这里 finish 结束当前页面 推荐开发者在结束当前页面前跳转至APP主页或者其他页面 + RLog.e( + TAG, + "SingleCallActivity#onCreate()->RongCallClient or CallSession is empty---->finish()"); + finish(); + return; + } + } else { + callSession = RongCallClient.getInstance().getCallSession(); + if (callSession != null) { + mediaType = callSession.getMediaType(); + receivedCallId = callSession.getCallId(); + } + } + if (!RongCallClient.getInstance().canCallContinued(receivedCallId)) { + RLog.w(TAG, "Already received hangup message before, finish current activity"); + ReportUtil.libStatus(ReportUtil.TAG.ACTIVITYFINISH, "reason", "canCallContinued not"); + finish(); + return; + } + if (mediaType != null) { + inflater = LayoutInflater.from(this); + initView(mediaType, callAction); + + if (requestCallPermissions(mediaType, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)) { + setupIntent(); + } + } else { + RLog.w(TAG, "remote already hangup, finish current activity"); + setShouldShowFloat(false); + CallFloatBoxView.hideFloatBox(); + finish(); + } + } + + @Override + protected void onNewIntent(Intent intent) { + startForCheckPermissions = intent.getBooleanExtra("checkPermissions", false); + RongCallAction callAction = RongCallAction.valueOf(intent.getStringExtra("callAction")); + if (callAction == null) { + return; + } + if (callAction.equals(RongCallAction.ACTION_OUTGOING_CALL)) { + if (intent.getAction().equals(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEAUDIO)) { + mediaType = RongCallCommon.CallMediaType.AUDIO; + } else { + mediaType = RongCallCommon.CallMediaType.VIDEO; + } + } else if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + callSession = intent.getParcelableExtra("callSession"); + mediaType = callSession.getMediaType(); + } else { + callSession = RongCallClient.getInstance().getCallSession(); + mediaType = callSession.getMediaType(); + } + super.onNewIntent(intent); + + if (requestCallPermissions(mediaType, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)) { + setupIntent(); + } + } + + @TargetApi(23) + @Override + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + switch (requestCode) { + case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: + if (RongCallPermissionUtil.checkPermissionByType(this, mediaType)) { + if (startForCheckPermissions) { + startForCheckPermissions = false; + RongCallClient.getInstance().onPermissionGranted(); + } else { + setupIntent(); + } + } else { + StringBuilder builder = new StringBuilder(); + for (String str : permissions) { + if (str.equals("android.permission.CAMERA")) { + builder.append(getString(R.string.rc_android_permission_CAMERA)); + } else if (str.equals("android.permission.RECORD_AUDIO")) { + builder.append(getString(R.string.rc_android_permission_RECORD_AUDIO)); + } else if (str.equals("android.permission.BLUETOOTH_CONNECT")) { + builder.append( + getString(R.string.rc_android_permission_BLUETOOTH_CONNECT)); + } + builder.append(","); + } + + String rets = + builder.length() > 0 ? builder.substring(0, builder.length() - 1) : ""; + String msg = + String.format( + "%s (%s)", + getString(R.string.rc_permission_grant_needed), rets); + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); + if (startForCheckPermissions) { + startForCheckPermissions = false; + RongCallClient.getInstance().onPermissionDenied(); + } else { + RLog.i("AudioPlugin", "--onRequestPermissionsResult--finish"); + finish(); + } + } + break; + default: + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS) { + if (RongCallPermissionUtil.checkPermissionByType(this, mediaType)) { + if (startForCheckPermissions) { + RongCallClient.getInstance().onPermissionGranted(); + } else { + setupIntent(); + } + } else { + if (startForCheckPermissions) { + RongCallClient.getInstance().onPermissionDenied(); + } else { + RLog.i("AudioPlugin", "onActivityResult finish"); + finish(); + } + } + + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + + private void setupIntent() { + RongCallCommon.CallMediaType mediaType; + Intent intent = getIntent(); + RongCallAction callAction = RongCallAction.valueOf(intent.getStringExtra("callAction")); + // if (callAction.equals(RongCallAction.ACTION_RESUME_CALL)) { + // return; + // } + if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + callSession = intent.getParcelableExtra("callSession"); + mediaType = callSession.getMediaType(); + targetId = callSession.getInviterUserId(); + RongCallClient.getInstance() + .startIncomingPreview( + new StartIncomingPreviewCallback() { + @Override + public void onDone(boolean isFront, SurfaceView localVideo) { + if (callSession + .getMediaType() + .equals(RongCallCommon.CallMediaType.VIDEO)) { + mLPreviewContainer.setVisibility(View.VISIBLE); + localVideo.setTag(callSession.getSelfUserId()); + mLPreviewContainer.addView(localVideo, mLargeLayoutParams); + } + } + + @Override + public void onError(int errorCode) {} + }); + } else if (callAction.equals(RongCallAction.ACTION_OUTGOING_CALL)) { + if (intent.getAction().equals(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEAUDIO)) { + mediaType = RongCallCommon.CallMediaType.AUDIO; + } else { + mediaType = RongCallCommon.CallMediaType.VIDEO; + } + Conversation.ConversationType conversationType = + Conversation.ConversationType.valueOf( + intent.getStringExtra("conversationType").toUpperCase(Locale.US)); + targetId = intent.getStringExtra("targetId"); + RongCallCommon.RoomType roomType = RongCallCommon.RoomType.NORMAL; + if (intent.hasExtra("roomType")) { + try { + roomType = (RoomType) intent.getSerializableExtra("roomType"); + } catch (Exception e) { + e.printStackTrace(); + } + } + List userIds = new ArrayList<>(); + userIds.add(targetId); + + RongCallClient.setPushConfig( + DefaultPushConfig.getInviteConfig( + this, mediaType == RongCallCommon.CallMediaType.AUDIO, true, ""), + DefaultPushConfig.getHangupConfig(this, true, "")); + + if (isCrossCall(targetId)) { + roomType = RongCallCommon.RoomType.CROSS; + } else { + roomType = RongCallCommon.RoomType.NORMAL; + } + FinLog.i(TAG, "call type: " + roomType.name() + " targetId" + targetId); + + if (roomType == RongCallCommon.RoomType.NORMAL) { + RongCallClient.getInstance() + .startCall(conversationType, targetId, userIds, null, mediaType, null); + } else { + RongCallClient.getInstance() + .startCrossCall(conversationType, targetId, userIds, null, mediaType, null); + } + } else { // resume call + callSession = RongCallClient.getInstance().getCallSession(); + mediaType = callSession.getMediaType(); + } + + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + handFree = false; + } else if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) { + handFree = true; + } + + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(targetId); + if (userInfo != null) { + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO) + || callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + ImageView userPortrait = + (ImageView) mUserInfoContainer.findViewById(R.id.rc_voip_user_portrait); + if (userPortrait != null && userInfo.getPortraitUri() != null) { + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + userInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + userPortrait); + } + TextView userName = + (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name); + userName.setText(CallKitUtils.nickNameRestrict(userInfo.getName())); + } + } + if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL) && userInfo != null) { + ImageView iv_icoming_backgroud = + (ImageView) mUserInfoContainer.findViewById(R.id.iv_icoming_backgroud); + if (iv_icoming_backgroud != null) { + iv_icoming_backgroud.setVisibility(View.VISIBLE); + GlideUtils.showBlurTransformation( + SingleCallActivity.this, iv_icoming_backgroud, userInfo.getPortraitUri()); + } + } + createPickupDetector(); + } + + private boolean isCrossCall(String targetId) { + if (!TextUtils.isEmpty(targetId) && targetId.contains("_")) { + String[] pairs = targetId.split("_"); + if (pairs.length == 2 && pairs[0].length() == 13) { + return true; + } + } + return false; + } + + @Override + protected void onResume() { + super.onResume(); + RLog.d(TAG, "---single activity onResume---"); + if (pickupDetector != null && mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + pickupDetector.register(this); + } + } + + @Override + protected void onPause() { + super.onPause(); + RLog.d(TAG, "---single activity onPause---"); + if (pickupDetector != null) { + pickupDetector.unRegister(); + } + } + + private void initView(RongCallCommon.CallMediaType mediaType, RongCallAction callAction) { + RelativeLayout buttonLayout = + (RelativeLayout) + inflater.inflate( + R.layout.rc_voip_call_bottom_connected_button_layout, null); + RelativeLayout userInfoLayout = null; + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + userInfoLayout = + (RelativeLayout) + inflater.inflate(R.layout.rc_voip_audio_call_user_info_incoming, null); + userInfoLayout.findViewById(R.id.iv_large_preview_Mask).setVisibility(View.VISIBLE); + } else { + // 单人视频 or 拨打 界面 + userInfoLayout = + (RelativeLayout) inflater.inflate(R.layout.rc_voip_audio_call_user_info, null); + TextView callInfo = + (TextView) userInfoLayout.findViewById(R.id.rc_voip_call_remind_info); + CallKitUtils.textViewShadowLayer(callInfo, SingleCallActivity.this); + } + + if (callAction.equals(RongCallAction.ACTION_RESUME_CALL) && CallKitUtils.isDial) { + try { + ImageView button = buttonLayout.findViewById(R.id.rc_voip_call_mute_btn); + button.setEnabled(false); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (callAction.equals(RongCallAction.ACTION_OUTGOING_CALL)) { + RelativeLayout layout = buttonLayout.findViewById(R.id.rc_voip_call_mute); + layout.setVisibility(View.VISIBLE); + ImageView button = buttonLayout.findViewById(R.id.rc_voip_call_mute_btn); + button.setEnabled(true); + buttonLayout.findViewById(R.id.rc_voip_handfree).setVisibility(View.VISIBLE); + } + + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + findViewById(R.id.rc_voip_call_information) + .setBackgroundColor(getResources().getColor(R.color.rc_voip_background_color)); + mLPreviewContainer.setVisibility(View.GONE); + mSPreviewContainer.setVisibility(View.GONE); + + if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + buttonLayout = + (RelativeLayout) + inflater.inflate( + R.layout.rc_voip_call_bottom_incoming_button_layout, null); + ImageView iv_answerBtn = + (ImageView) buttonLayout.findViewById(R.id.rc_voip_call_answer_btn); + iv_answerBtn.setBackground( + CallKitUtils.BackgroundDrawable( + R.drawable.rc_voip_audio_answer_selector_new, + SingleCallActivity.this)); + + TextView callInfo = + (TextView) userInfoLayout.findViewById(R.id.rc_voip_call_remind_info); + CallKitUtils.textViewShadowLayer(callInfo, SingleCallActivity.this); + callInfo.setText(R.string.rc_voip_audio_call_inviting); + onIncomingCallRinging(callSession); + } + } else if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) { + if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + findViewById(R.id.rc_voip_call_information) + .setBackgroundColor(getResources().getColor(android.R.color.transparent)); + buttonLayout = + (RelativeLayout) + inflater.inflate( + R.layout.rc_voip_call_bottom_incoming_button_layout, null); + ImageView iv_answerBtn = + (ImageView) buttonLayout.findViewById(R.id.rc_voip_call_answer_btn); + iv_answerBtn.setBackground( + CallKitUtils.BackgroundDrawable( + R.drawable.rc_voip_vedio_answer_selector_new, + SingleCallActivity.this)); + + TextView callInfo = + (TextView) userInfoLayout.findViewById(R.id.rc_voip_call_remind_info); + CallKitUtils.textViewShadowLayer(callInfo, SingleCallActivity.this); + callInfo.setText(R.string.rc_voip_video_call_inviting); + onIncomingCallRinging(callSession); + } + } + mButtonContainer.removeAllViews(); + mButtonContainer.addView(buttonLayout); + mUserInfoContainer.removeAllViews(); + mUserInfoContainer.addView(userInfoLayout); + } + + @Override + public void onCallOutgoing(RongCallSession callSession, SurfaceView localVideo) { + super.onCallOutgoing(callSession, localVideo); + this.callSession = callSession; + try { + UserInfo InviterUserIdInfo = RongUserInfoManager.getInstance().getUserInfo(targetId); + UserInfo SelfUserInfo = + RongUserInfoManager.getInstance().getUserInfo(callSession.getSelfUserId()); + if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.VIDEO)) { + mLPreviewContainer.setVisibility(View.VISIBLE); + localVideo.setTag(callSession.getSelfUserId()); + mLPreviewContainer.addView(localVideo, mLargeLayoutParams); + if (null != SelfUserInfo && null != SelfUserInfo.getName()) { + // 单人视频 + TextView callkit_voip_user_name_signleVideo = + (TextView) + mUserInfoContainer.findViewById( + R.id.callkit_voip_user_name_signleVideo); + // topUserName = SelfUserInfo.getName(); + callkit_voip_user_name_signleVideo.setText( + CallKitUtils.nickNameRestrict(SelfUserInfo.getName())); + } + } else if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.AUDIO)) { + if (null != InviterUserIdInfo && null != InviterUserIdInfo.getPortraitUri()) { + ImageView iv_icoming_backgroud = + mUserInfoContainer.findViewById(R.id.iv_icoming_backgroud); + GlideUtils.showBlurTransformation( + SingleCallActivity.this, + iv_icoming_backgroud, + InviterUserIdInfo.getPortraitUri()); + iv_icoming_backgroud.setVisibility(View.VISIBLE); + mUserInfoContainer + .findViewById(R.id.iv_large_preview_Mask) + .setVisibility(View.VISIBLE); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + callRinging(RingingMode.Outgoing); + } + + @Override + public void onCallConnected(RongCallSession callSession, SurfaceView localVideo) { + super.onCallConnected(callSession, localVideo); + this.callSession = callSession; + RLog.d(TAG, "onCallConnected----mediaType=" + callSession.getMediaType().getValue()); + if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.AUDIO)) { + findViewById(R.id.rc_voip_call_minimize).setVisibility(View.VISIBLE); + RelativeLayout btnLayout = + (RelativeLayout) + inflater.inflate( + R.layout.rc_voip_call_bottom_connected_button_layout, null); + ImageView button = btnLayout.findViewById(R.id.rc_voip_call_mute_btn); + button.setEnabled(true); + mButtonContainer.removeAllViews(); + mButtonContainer.addView(btnLayout); + RCRTCEngine.getInstance().enableSpeaker(handFree); + } else { + mConnectionStateTextView.setVisibility(View.VISIBLE); + mConnectionStateTextView.setText(R.string.rc_voip_connecting); + // 二人视频通话接通后 mUserInfoContainer 中更换为无头像的布局 + mUserInfoContainer.removeAllViews(); + inflater.inflate(R.layout.rc_voip_video_call_user_info, mUserInfoContainer); + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(targetId); + if (userInfo != null) { + TextView userName = mUserInfoContainer.findViewById(R.id.rc_voip_user_name); + userName.setText(CallKitUtils.nickNameRestrict(userInfo.getName())); + // userName.setShadowLayer(16F, 0F, 2F, + // getResources().getColor(R.color.rc_voip_reminder_shadow));//callkit_shadowcolor + CallKitUtils.textViewShadowLayer(userName, SingleCallActivity.this); + } + mLocalVideo = localVideo; + mLocalVideo.setTag(callSession.getSelfUserId()); + RCRTCEngine.getInstance().enableSpeaker(true); + } + TextView tv_rc_voip_call_remind_info = + (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_call_remind_info); + CallKitUtils.textViewShadowLayer(tv_rc_voip_call_remind_info, SingleCallActivity.this); + tv_rc_voip_call_remind_info.setVisibility(View.GONE); + TextView remindInfo = null; + if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.AUDIO)) { + remindInfo = mUserInfoContainer.findViewById(R.id.tv_setupTime); + } else { + remindInfo = mUserInfoContainer.findViewById(R.id.tv_setupTime_video); + } + if (remindInfo == null) { + remindInfo = tv_rc_voip_call_remind_info; + } + setupTime(remindInfo); + + RongCallClient.getInstance().setEnableLocalAudio(!muted); + View muteV = mButtonContainer.findViewById(R.id.rc_voip_call_mute); + if (muteV != null) { + muteV.setSelected(muted); + } + + stopRing(); + } + + protected void resetHandFreeStatus(RCAudioRouteType type) { + ImageView handFreeV = null; + if (null != mButtonContainer) { + handFreeV = mButtonContainer.findViewById(R.id.rc_voip_handfree_btn); + } + if (handFreeV != null) { + if (type == RCAudioRouteType.HEADSET || type == RCAudioRouteType.HEADSET_BLUETOOTH) { + // 耳机态下不在将扬声器设为关闭 + // handFreeV.setSelected(false); + } else { + // 非耳机状态 + handFreeV.setSelected(type == RCAudioRouteType.SPEAKER_PHONE); + } + } + } + + @Override + protected void onDestroy() { + RLog.d(TAG, "---single activity onDestroy---"); + stopRing(); + super.onDestroy(); + } + + private RongCallCommon.CallMediaType remoteMediaType; + int userType; + SurfaceView remoteVideo; + String remoteUserId; + /** 远端首帧是否到来, 音频帧跟视频帧其中一个到来就更改该标记, 从而更新连接状态 */ + boolean isFirstRemoteFrame = false; + + @Override + public void onRemoteUserJoined( + final String userId, + RongCallCommon.CallMediaType mediaType, + int userType, + SurfaceView remoteVideo) { + super.onRemoteUserJoined(userId, mediaType, userType, remoteVideo); + RLog.v( + TAG, + "onRemoteUserJoined userID=" + + userId + + ",mediaType=" + + mediaType.name() + + " , userType=" + + (userType == 1 ? "Normal" : "Observer")); + this.remoteMediaType = mediaType; + this.userType = userType; + this.remoteVideo = remoteVideo; + this.remoteUserId = userId; + } + + @Override + public void onFirstRemoteAudioFrame(String userId) { + super.onFirstRemoteAudioFrame(userId); + RLog.v(TAG, "onFirstRemoteAudioFrame "); + if (!isFirstRemoteFrame) { + changeToConnectedState(userId, remoteMediaType, userType, remoteVideo); + isFirstRemoteFrame = true; + } + } + + @Override + public void onRemoteUserPublishVideoStream( + String userId, String streamId, String tag, SurfaceView surfaceView) { + super.onRemoteUserPublishVideoStream(userId, streamId, tag, surfaceView); + RLog.v(TAG, "onRemoteUserPublishVideoStream userID=" + userId + ",streamId=" + streamId); + this.remoteVideo = surfaceView; + addRemoteVideoView(userId, remoteVideo); + } + + private void changeToConnectedState( + String userId, + RongCallCommon.CallMediaType mediaType, + int userType, + SurfaceView remoteVideo) { + mConnectionStateTextView.setVisibility(View.GONE); + if (RongCallCommon.CallMediaType.VIDEO.equals(mediaType)) { + if (remoteVideo != null) { + addRemoteVideoView(userId, remoteVideo); + } + mSPreviewContainer.setVisibility(View.VISIBLE); + mSPreviewContainer.removeAllViews(); + RLog.d(TAG, "onRemoteUserJoined mLocalVideo != null=" + (mLocalVideo != null)); + if (mLocalVideo != null) { + mLocalVideo.setZOrderMediaOverlay(true); + mLocalVideo.setZOrderOnTop(true); + mSPreviewContainer.addView(mLocalVideo); + } + /** 小窗口点击事件 * */ + mSPreviewContainer.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + final SurfaceView fromView = + (SurfaceView) mSPreviewContainer.getChildAt(0); + SurfaceView toView = (SurfaceView) mLPreviewContainer.getChildAt(0); + fromView.setVisibility(View.INVISIBLE); + + mLPreviewContainer.removeAllViews(); + mSPreviewContainer.removeAllViews(); + fromView.setZOrderOnTop(false); + fromView.setZOrderMediaOverlay(false); + mLPreviewContainer.addView(fromView, mLargeLayoutParams); + toView.setZOrderOnTop(true); + toView.setZOrderMediaOverlay(true); + mSPreviewContainer.addView(toView); + mSPreviewContainer.postDelayed( + new Runnable() { + @Override + public void run() { + fromView.setVisibility(View.VISIBLE); + } + }, + 30); + if (null != fromView.getTag() + && !TextUtils.isEmpty(fromView.getTag().toString())) { + UserInfo userInfo = + RongUserInfoManager.getInstance() + .getUserInfo(fromView.getTag().toString()); + TextView userName = + (TextView) + mUserInfoContainer.findViewById( + R.id.rc_voip_user_name); + // topUserName = + // userInfo.getName(); + userName.setText( + CallKitUtils.nickNameRestrict(userInfo.getName())); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + mButtonContainer.setVisibility(View.GONE); + mUserInfoContainer.setVisibility(View.GONE); + } + } + + /** + * 当前的布局中是否包含了 RemoteVideoView + * + * @param remoteVideo + * @return + */ + protected boolean hasRemoteVideoView(SurfaceView remoteVideo) { + int count = mLPreviewContainer.getChildCount(); + if (count == 0) { + return false; + } + for (int i = 0; i < count; i++) { + View view = mLPreviewContainer.getChildAt(i); + if (view == remoteVideo) { + return true; + } + } + return false; + } + + private void addRemoteVideoView(String userId, SurfaceView remoteVideo) { + if (remoteVideo == null) { + RLog.e(TAG, "addRemoteVideoView: remoteVideo is null!"); + return; + } + if (hasRemoteVideoView(remoteVideo)) { + RLog.v(TAG, "onRemoteUserJoined hasRemoteVideoView"); + return; + } + + if (remoteVideo.getParent() != null) { + RLog.v(TAG, "onRemoteUserJoined remoteVideo.getParent() != null"); + return; + } + + findViewById(R.id.rc_voip_call_information) + .setBackgroundColor(getResources().getColor(android.R.color.transparent)); + mLPreviewContainer.setVisibility(View.VISIBLE); + mLPreviewContainer.removeAllViews(); + remoteVideo.setTag(userId); + RLog.v(TAG, "onRemoteUserJoined mLPreviewContainer.addView(remoteVideo)"); + mLPreviewContainer.addView(remoteVideo, mLargeLayoutParams); + mLPreviewContainer.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + RLog.v(TAG, "setOnClickListener. isInformationShow : " + isInformationShow); + if (isInformationShow) { + hideVideoCallInformation(); + } else { + showVideoCallInformation(); + handler.sendEmptyMessageDelayed(EVENT_FULL_SCREEN, 5 * 1000); + } + } + }); + } + + /** + * 当通话中的某一个参与者切换通话类型,例如由 audio 切换至 video,回调 onMediaTypeChanged。 + * + * @param userId 切换者的 userId。 + * @param mediaType 切换者,切换后的媒体类型。 + * @param video 切换着,切换后的 camera 信息,如果由 video 切换至 audio,则为 null。 + */ + @Override + public void onMediaTypeChanged( + String userId, RongCallCommon.CallMediaType mediaType, SurfaceView video) { + if (callSession.getSelfUserId().equals(userId)) { + showShortToast(getString(R.string.rc_voip_switched_to_audio)); + } else { + if (callSession.getMediaType() != RongCallCommon.CallMediaType.AUDIO) { + RongCallClient.getInstance() + .changeCallMediaType(RongCallCommon.CallMediaType.AUDIO); + callSession.setMediaType(RongCallCommon.CallMediaType.AUDIO); + showShortToast(getString(R.string.rc_voip_remote_switched_to_audio)); + } + } + initAudioCallView(); + handler.removeMessages(EVENT_FULL_SCREEN); + mButtonContainer.findViewById(R.id.rc_voip_call_mute).setSelected(muted); + } + + @Override + public void onNetworkReceiveLost(String userId, int lossRate) { + // RLog.d(TAG, "onNetworkReceiveLost : userId =" + userId + " lossRate=" + + // lossRate); + isReceiveLost = lossRate > LOSS_RATE_ALARM; + handler.post( + new Runnable() { + @Override + public void run() { + refreshConnectionState(); + } + }); + } + + @Override + public void onNetworkSendLost(int lossRate, int delay) { + // RLog.d(TAG, "onNetworkSendLost : rate =" + lossRate + " delay=" + delay); + isSendLost = lossRate > LOSS_RATE_ALARM; + handler.post( + new Runnable() { + @Override + public void run() { + refreshConnectionState(); + } + }); + } + + @Override + public void onFirstRemoteVideoFrame(String userId, int height, int width) { + RLog.d(TAG, "onFirstRemoteVideoFrame for user::" + userId); + if (userId.equals(remoteUserId)) { + // mConnectionStateTextView.setVisibility(View.GONE); + if (!isFirstRemoteFrame) { + changeToConnectedState(userId, remoteMediaType, userType, remoteVideo); + isFirstRemoteFrame = true; + } + } + } + + /** 视频转语音 * */ + private void initAudioCallView() { + mLPreviewContainer.removeAllViews(); + mLPreviewContainer.setVisibility(View.GONE); + mSPreviewContainer.removeAllViews(); + mSPreviewContainer.setVisibility(View.GONE); + // 显示全屏底色 + findViewById(R.id.rc_voip_call_information) + .setBackgroundColor(getResources().getColor(R.color.rc_voip_background_color)); + findViewById(R.id.rc_voip_audio_chat).setVisibility(View.GONE); // 隐藏语音聊天按钮 + + View userInfoView = inflater.inflate(R.layout.rc_voip_audio_call_user_info_incoming, null); + TextView tv_rc_voip_call_remind_info = + (TextView) userInfoView.findViewById(R.id.rc_voip_call_remind_info); + tv_rc_voip_call_remind_info.setVisibility(View.GONE); + + TextView timeView = (TextView) userInfoView.findViewById(R.id.tv_setupTime); + setupTime(timeView); + + mUserInfoContainer.removeAllViews(); + mUserInfoContainer.addView(userInfoView); + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(targetId); + if (userInfo != null) { + TextView userName = (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name); + userName.setText(CallKitUtils.nickNameRestrict(userInfo.getName())); + if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.AUDIO)) { + ImageView userPortrait = + (ImageView) mUserInfoContainer.findViewById(R.id.rc_voip_user_portrait); + if (userPortrait != null) { + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + userInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + userPortrait); + // Glide.with(this) + // .load(userInfo.getPortraitUri()) + // .placeholder(R.drawable.rc_default_portrait) + // .apply(RequestOptions.bitmapTransform(new + // CircleCrop())) + // .into(userPortrait); + } + } else { // 单人视频接听layout + ImageView iv_large_preview = mUserInfoContainer.findViewById(R.id.iv_large_preview); + iv_large_preview.setVisibility(View.VISIBLE); + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + userInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + iv_large_preview); + } + } + mUserInfoContainer.setVisibility(View.VISIBLE); + mUserInfoContainer.findViewById(R.id.rc_voip_call_minimize).setVisibility(View.VISIBLE); + + View button = inflater.inflate(R.layout.rc_voip_call_bottom_connected_button_layout, null); + mButtonContainer.removeAllViews(); + mButtonContainer.addView(button); + mButtonContainer.setVisibility(View.VISIBLE); + // 视频转音频时默认不开启免提 + handFree = false; + RongCallClient.getInstance().setEnableSpeakerphone(false); + View handFreeV = mButtonContainer.findViewById(R.id.rc_voip_handfree); + handFreeV.setSelected(handFree); + + ImageView iv_large_preview_Mask = + (ImageView) userInfoView.findViewById(R.id.iv_large_preview_Mask); + iv_large_preview_Mask.setVisibility(View.VISIBLE); + + /** 视频切换成语音 全是语音界面的ui* */ + ImageView iv_large_preview = mUserInfoContainer.findViewById(R.id.iv_icoming_backgroud); + + if (null != userInfo + && callSession.getMediaType().equals(RongCallCommon.CallMediaType.AUDIO)) { + GlideUtils.showBlurTransformation( + SingleCallActivity.this, iv_large_preview, userInfo.getPortraitUri()); + iv_large_preview.setVisibility(View.VISIBLE); + } + + if (pickupDetector != null) { + pickupDetector.register(this); + } + } + + public void onHangupBtnClick(View view) { + // unRegisterHeadsetplugReceiver(); + RongCallSession session = RongCallClient.getInstance().getCallSession(); + if (session == null || isFinishing) { + finish(); + RLog.e( + TAG, + "hangup call error: callSession=" + + (callSession == null) + + ",isFinishing=" + + isFinishing); + return; + } + RongCallClient.getInstance().hangUpCall(session.getCallId()); + stopRing(); + } + + public void onReceiveBtnClick(View view) { + RongCallSession session = RongCallClient.getInstance().getCallSession(); + if (session == null || isFinishing) { + RLog.e( + TAG, + "hangup call error: callSession=" + + (callSession == null) + + ",isFinishing=" + + isFinishing); + finish(); + return; + } + RongCallClient.getInstance().acceptCall(session.getCallId()); + } + + public void hideVideoCallInformation() { + isInformationShow = false; + mUserInfoContainer.setVisibility(View.GONE); + mButtonContainer.setVisibility(View.GONE); + findViewById(R.id.rc_voip_audio_chat).setVisibility(View.GONE); + } + + public void showVideoCallInformation() { + isInformationShow = true; + mUserInfoContainer.setVisibility(View.VISIBLE); + + mUserInfoContainer.findViewById(R.id.rc_voip_call_minimize).setVisibility(View.VISIBLE); + mButtonContainer.setVisibility(View.VISIBLE); + RelativeLayout btnLayout = + (RelativeLayout) + inflater.inflate( + R.layout.rc_voip_call_bottom_connected_button_layout, null); + btnLayout.findViewById(R.id.rc_voip_call_mute).setSelected(muted); + btnLayout.findViewById(R.id.rc_voip_handfree).setVisibility(View.GONE); + btnLayout.findViewById(R.id.rc_voip_camera).setVisibility(View.VISIBLE); + mButtonContainer.removeAllViews(); + mButtonContainer.addView(btnLayout); + View view = findViewById(R.id.rc_voip_audio_chat); + // view.setVisibility(View.VISIBLE); + view.setVisibility(View.GONE); // 隐藏语音聊天按钮 + view.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (RongIMClient.getInstance().getCurrentConnectionStatus() + == RongIMClient.ConnectionStatusListener.ConnectionStatus + .CONNECTED) { + RongCallClient.getInstance() + .changeCallMediaType(RongCallCommon.CallMediaType.AUDIO); + callSession.setMediaType(RongCallCommon.CallMediaType.AUDIO); + initAudioCallView(); + } else { + showShortToast(getString(R.string.rc_voip_im_connection_abnormal)); + } + } + }); + } + + public void onHandFreeButtonClick(View view) { + CallKitUtils.speakerphoneState = !view.isSelected(); + RongCallClient.getInstance() + .setEnableSpeakerphone(!view.isSelected()); // true:打开免提 false:关闭免提 + view.setSelected(!view.isSelected()); + handFree = view.isSelected(); + } + + public void onMuteButtonClick(View view) { + RongCallClient.getInstance().setEnableLocalAudio(view.isSelected()); + view.setSelected(!view.isSelected()); + muted = view.isSelected(); + } + + @Override + public void onCallDisconnected( + RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) { + super.onCallDisconnected(callSession, reason); + + String senderId; + String extra = ""; + + isFinishing = true; + if (callSession == null) { + RLog.e(TAG, "onCallDisconnected. callSession is null!"); + postRunnableDelay( + new Runnable() { + @Override + public void run() { + finish(); + } + }); + return; + } + senderId = callSession.getInviterUserId(); + long time = getTime(callSession.getActiveTime()); + if (time > 0) { + if (time >= 3600) { + extra = + String.format( + Locale.ROOT, + "%d:%02d:%02d", + time / 3600, + (time % 3600) / 60, + (time % 60)); + } else { + extra = String.format(Locale.ROOT, "%02d:%02d", (time % 3600) / 60, (time % 60)); + } + } + cancelTime(); + + if (!TextUtils.isEmpty(senderId)) { + CallSTerminateMessage message = new CallSTerminateMessage(); + message.setReason(reason); + message.setMediaType(callSession.getMediaType()); + message.setExtra(extra); + long serverTime = + System.currentTimeMillis() - RongIMClient.getInstance().getDeltaTime(); + if (senderId.equals(callSession.getSelfUserId())) { + message.setDirection("MO"); + IMCenter.getInstance() + .insertOutgoingMessage( + Conversation.ConversationType.PRIVATE, + callSession.getTargetId(), + io.rong.imlib.model.Message.SentStatus.SENT, + message, + serverTime, + null); + } else { + message.setDirection("MT"); + IMCenter.getInstance() + .insertIncomingMessage( + Conversation.ConversationType.PRIVATE, + callSession.getTargetId(), + senderId, + CallKitUtils.getReceivedStatus(reason), + message, + serverTime, + null); + } + } + postRunnableDelay( + new Runnable() { + @Override + public void run() { + finish(); + } + }); + } + + private Runnable mCheckConnectionStableTask = + new Runnable() { + @Override + public void run() { + boolean isConnectionStable = !isSendLost && !isReceiveLost; + if (isConnectionStable) { + mConnectionStateTextView.setVisibility(View.GONE); + } + } + }; + + private void refreshConnectionState() { + if (isSendLost || isReceiveLost) { + if (mConnectionStateTextView.getVisibility() == View.GONE) { + mConnectionStateTextView.setText(R.string.rc_voip_unstable_call_connection); + mConnectionStateTextView.setVisibility(View.VISIBLE); + if (mSoundPool != null) { + mSoundPool.release(); + } + mSoundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0); + mSoundPool.load(this, R.raw.voip_network_error_sound, 0); + mSoundPool.setOnLoadCompleteListener( + new SoundPool.OnLoadCompleteListener() { + @Override + public void onLoadComplete( + SoundPool soundPool, int sampleId, int status) { + soundPool.play(sampleId, 1F, 1F, 0, 0, 1F); + } + }); + } + mConnectionStateTextView.removeCallbacks(mCheckConnectionStableTask); + mConnectionStateTextView.postDelayed(mCheckConnectionStableTask, 3000); + } + } + + @Override + public void onRestoreFloatBox(Bundle bundle) { + super.onRestoreFloatBox(bundle); + RLog.d(TAG, "---single activity onRestoreFloatBox---"); + if (bundle == null) return; + muted = bundle.getBoolean("muted"); + handFree = bundle.getBoolean("handFree"); + // topUserName=bundle.getString(EXTRA_BUNDLE_KEY_USER_TOP_NAME); + + setShouldShowFloat(true); + callSession = RongCallClient.getInstance().getCallSession(); + if (callSession == null) { + setShouldShowFloat(false); + finish(); + return; + } + RongCallCommon.CallMediaType mediaType = callSession.getMediaType(); + RongCallAction callAction = + RongCallAction.valueOf(getIntent().getStringExtra("callAction")); + inflater = LayoutInflater.from(this); + initView(mediaType, callAction); + targetId = callSession.getTargetId(); + UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(targetId); + if (userInfo != null) { + TextView userName = (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name); + userName.setText(CallKitUtils.nickNameRestrict(userInfo.getName())); + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + ImageView userPortrait = + (ImageView) mUserInfoContainer.findViewById(R.id.rc_voip_user_portrait); + if (userPortrait != null) { + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + userInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + userPortrait); + } + } else if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) { + if (null != callAction && callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) { + ImageView iv_large_preview = + mUserInfoContainer.findViewById(R.id.iv_large_preview); + iv_large_preview.setVisibility(View.VISIBLE); + Uri imgUri = userInfo == null ? null : userInfo.getPortraitUri(); + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + imgUri, + R.drawable.rc_default_portrait, + iv_large_preview); + } + } + } + SurfaceView localVideo = null; + SurfaceView remoteVideo = null; + String remoteUserId = null; + for (CallUserProfile profile : callSession.getParticipantProfileList()) { + if (profile.getUserId().equals(RongIMClient.getInstance().getCurrentUserId())) { + localVideo = profile.getVideoView(); + } else { + remoteVideo = profile.getVideoView(); + remoteUserId = profile.getUserId(); + } + } + if (localVideo != null && localVideo.getParent() != null) { + ((ViewGroup) localVideo.getParent()).removeView(localVideo); + } + onCallOutgoing(callSession, localVideo); + if (!(boolean) bundle.get("isDial")) { + onCallConnected(callSession, localVideo); + } + if (remoteVideo != null) { + if (remoteVideo.getParent() != null) { + ((ViewGroup) remoteVideo.getParent()).removeView(remoteVideo); + } + changeToConnectedState(remoteUserId, mediaType, 1, remoteVideo); + } + } + + @Override + public String onSaveFloatBoxState(Bundle bundle) { + super.onSaveFloatBoxState(bundle); + callSession = RongCallClient.getInstance().getCallSession(); + if (callSession == null) { + return null; + } + bundle.putBoolean("muted", muted); + bundle.putBoolean("handFree", handFree); + bundle.putInt("mediaType", callSession.getMediaType().getValue()); + // bundle.putString(EXTRA_BUNDLE_KEY_USER_TOP_NAME, topUserName); + return getIntent().getAction(); + } + + public void onMinimizeClick(View view) { + super.onMinimizeClick(view); + } + + public void onSwitchCameraClick(View view) { + RongCallClient.getInstance().switchCamera(); + } + + @Override + public void onBackPressed() { + return; + // List participantProfiles = + // callSession.getParticipantProfileList(); + // RongCallCommon.CallStatus callStatus = null; + // for (CallUserProfile item : participantProfiles) { + // if (item.getUserId().equals(callSession.getSelfUserId())) { + // callStatus = item.getCallStatus(); + // break; + // } + // } + // if (callStatus != null && callStatus.equals(RongCallCommon.CallStatus.CONNECTED)) + // { + // super.onBackPressed(); + // } else { + // RongCallClient.getInstance().hangUpCall(callSession.getCallId()); + // } + } + + @Override + public void onUserUpdate(UserInfo info) { + if (isFinishing()) { + return; + } + if (targetId != null && targetId.equals(info.getUserId())) { + TextView userName = (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name); + if (info.getName() != null) + userName.setText(CallKitUtils.nickNameRestrict(info.getName())); + + ImageView userPortrait = + (ImageView) mUserInfoContainer.findViewById(R.id.rc_voip_user_portrait); + if (userPortrait != null + && info.getPortraitUri() != null + && userPortrait.getVisibility() == View.VISIBLE) { + RongCallKit.getKitImageEngine() + .loadPortrait( + getBaseContext(), + info.getPortraitUri(), + R.drawable.rc_default_portrait, + userPortrait); + } + } + } + + public void onHeadsetPlugUpdate(HeadsetInfo headsetInfo) { + if (headsetInfo == null || !BluetoothUtil.isForground(SingleCallActivity.this)) { + RLog.v(TAG, "SingleCallActivity 不在前台!"); + return; + } + RLog.v( + TAG, + "Insert=" + + headsetInfo.isInsert() + + ",headsetInfo.getType=" + + headsetInfo.getType().getValue()); + try { + if (headsetInfo.isInsert()) { + RongCallClient.getInstance().setEnableSpeakerphone(false); + ImageView handFreeV = null; + if (null != mButtonContainer) { + handFreeV = mButtonContainer.findViewById(R.id.rc_voip_handfree_btn); + } + if (handFreeV != null) { + handFreeV.setSelected(false); + handFreeV.setEnabled(false); + handFreeV.setClickable(false); + } + if (headsetInfo.getType() == HeadsetInfo.HeadsetType.BluetoothA2dp) { + AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + am.setMode(AudioManager.MODE_IN_COMMUNICATION); + am.startBluetoothSco(); + am.setBluetoothScoOn(true); + am.setSpeakerphoneOn(false); + } + } else { + if (headsetInfo.getType() == HeadsetInfo.HeadsetType.WiredHeadset + && BluetoothUtil.hasBluetoothA2dpConnected()) { + return; + } + RongCallClient.getInstance().setEnableSpeakerphone(true); + ImageView handFreeV = mButtonContainer.findViewById(R.id.rc_voip_handfree_btn); + if (handFreeV != null) { + handFreeV.setSelected(true); + handFreeV.setEnabled(true); + handFreeV.setClickable(true); + } + } + } catch (Exception e) { + e.printStackTrace(); + RLog.d(TAG, "SingleCallActivity->onHeadsetPlugUpdate Error=" + e.getMessage()); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/VideoPlugin.java b/callkit/src/main/java/io/rong/callkit/VideoPlugin.java new file mode 100644 index 000000000..c279a15a0 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/VideoPlugin.java @@ -0,0 +1,189 @@ +package io.rong.callkit; + +import static io.rong.callkit.BaseCallActivity.REQUEST_CODE_ADD_MEMBER; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import io.rong.callkit.util.CallKitUtils; +import io.rong.callkit.util.RongCallPermissionUtil; +import io.rong.callkit.util.permission.PermissionType; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import io.rong.common.RLog; +import io.rong.imkit.conversation.extension.RongExtension; +import io.rong.imkit.conversation.extension.component.plugin.IPluginModule; +import io.rong.imkit.conversation.extension.component.plugin.IPluginRequestPermissionResultCallback; +import io.rong.imlib.IRongCoreCallback; +import io.rong.imlib.IRongCoreEnum; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.discussion.base.RongDiscussionClient; +import io.rong.imlib.discussion.model.Discussion; +import io.rong.imlib.model.Conversation; +import java.util.ArrayList; +import java.util.Locale; + +/** Created by weiqinxiao on 16/8/16. */ +public class VideoPlugin implements IPluginModule, IPluginRequestPermissionResultCallback { + private static final String TAG = "VideoPlugin"; + private ArrayList allMembers; + private Context context; + + private Conversation.ConversationType conversationType; + private String targetId; + + @Override + public Drawable obtainDrawable(Context context) { + return context.getResources().getDrawable(R.drawable.rc_ic_video_selector); + } + + @Override + public String obtainTitle(Context context) { + return context.getString(R.string.rc_voip_video); + } + + @Override + public void onClick(Fragment currentFragment, RongExtension extension, int index) { + context = currentFragment.getActivity().getApplicationContext(); + conversationType = extension.getConversationType(); + targetId = extension.getTargetId(); + + PermissionType[] audioCallPermissions = + RongCallPermissionUtil.getVideoCallPermissions(context); + String[] permissions = new String[audioCallPermissions.length]; + for (int i = 0; i < audioCallPermissions.length; i++) { + permissions[i] = audioCallPermissions[i].getPermissionName(); + } + if (RongCallPermissionUtil.checkPermissions(currentFragment.getActivity(), permissions)) { + startVideoActivity(extension); + } else { + extension.requestPermissionForPluginResult( + permissions, + IPluginRequestPermissionResultCallback.REQUEST_CODE_PERMISSION_PLUGIN, + this); + } + } + + private void startVideoActivity(final RongExtension extension) { + + RongCallSession profile = RongCallClient.getInstance().getCallSession(); + if (profile != null && profile.getStartTime() > 0) { + Toast.makeText( + context, + profile.getMediaType() == RongCallCommon.CallMediaType.AUDIO + ? context.getString(R.string.rc_voip_call_audio_start_fail) + : context.getString(R.string.rc_voip_call_video_start_fail), + Toast.LENGTH_SHORT) + .show(); + return; + } + if (!CallKitUtils.isNetworkAvailable(context)) { + Toast.makeText( + context, + context.getString(R.string.rc_voip_call_network_error), + Toast.LENGTH_SHORT) + .show(); + return; + } + if (conversationType.equals(Conversation.ConversationType.PRIVATE)) { + Intent intent = new Intent(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEVIDEO); + intent.putExtra("conversationType", conversationType.getName().toLowerCase(Locale.US)); + intent.putExtra("targetId", targetId); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setPackage(context.getPackageName()); + context.getApplicationContext().startActivity(intent); + } else if (conversationType.equals(Conversation.ConversationType.DISCUSSION)) { + RongDiscussionClient.getInstance() + .getDiscussion( + targetId, + new IRongCoreCallback.ResultCallback() { + @Override + public void onSuccess(Discussion discussion) { + + Intent intent = + new Intent(context, CallSelectMemberActivity.class); + allMembers = (ArrayList) discussion.getMemberIdList(); + intent.putStringArrayListExtra("allMembers", allMembers); + String myId = RongIMClient.getInstance().getCurrentUserId(); + ArrayList invited = new ArrayList<>(); + invited.add(myId); + intent.putStringArrayListExtra("invitedMembers", invited); + intent.putExtra( + "conversationType", conversationType.getValue()); + intent.putExtra( + "mediaType", + RongCallCommon.CallMediaType.VIDEO.getValue()); + extension.startActivityForPluginResult( + intent, 110, VideoPlugin.this); + } + + @Override + public void onError(IRongCoreEnum.CoreErrorCode e) { + RLog.d(TAG, "get discussion errorCode = " + e.getValue()); + } + }); + } else if (conversationType.equals(Conversation.ConversationType.GROUP)) { + Intent intent = new Intent(context, CallSelectMemberActivity.class); + String myId = RongIMClient.getInstance().getCurrentUserId(); + ArrayList invited = new ArrayList<>(); + invited.add(myId); + intent.putStringArrayListExtra("invitedMembers", invited); + intent.putExtra("groupId", targetId); + intent.putExtra("conversationType", conversationType.getValue()); + intent.putExtra("mediaType", RongCallCommon.CallMediaType.VIDEO.getValue()); + extension.startActivityForPluginResult(intent, 110, this); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK) { + return; + } + + if (requestCode == REQUEST_CODE_ADD_MEMBER) { + if (resultCode == Activity.RESULT_OK) { + if (data.getBooleanExtra("remote_hangup", false)) { + RLog.d(TAG, "Remote exit, end the call."); + return; + } + } + } + + Intent intent = new Intent(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIVIDEO); + ArrayList userIds = data.getStringArrayListExtra("invited"); + ArrayList observerIds = data.getStringArrayListExtra("observers"); + userIds.add(RongIMClient.getInstance().getCurrentUserId()); + intent.putExtra("conversationType", conversationType.getName().toLowerCase(Locale.US)); + intent.putExtra("targetId", targetId); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + intent.putStringArrayListExtra("invitedUsers", userIds); + intent.putStringArrayListExtra("observerUsers", observerIds); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setPackage(context.getPackageName()); + context.getApplicationContext().startActivity(intent); + } + + @Override + public boolean onRequestPermissionResult( + Fragment fragment, + RongExtension extension, + int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults) { + Context context = fragment.getContext(); + if (RongCallPermissionUtil.checkPermissions(context, permissions)) { + startVideoActivity(extension); + } else { + RongCallPermissionUtil.showRequestPermissionFailedAlter( + context, permissions, grantResults); + } + return true; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/VoIPBroadcastReceiver.java b/callkit/src/main/java/io/rong/callkit/VoIPBroadcastReceiver.java new file mode 100644 index 000000000..08013ce79 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/VoIPBroadcastReceiver.java @@ -0,0 +1,526 @@ +package io.rong.callkit; + +import static android.content.Context.NOTIFICATION_SERVICE; + +import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import androidx.core.app.NotificationCompat; +import io.rong.callkit.util.CallRingingUtil; +import io.rong.callkit.util.IncomingCallExtraHandleUtil; +import io.rong.callkit.util.RingingMode; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import io.rong.common.fwlog.FwLog; +import io.rong.imkit.userinfo.RongUserInfoManager; +import io.rong.imlib.model.AndroidConfig; +import io.rong.imlib.model.Conversation.ConversationType; +import io.rong.imlib.model.Group; +import io.rong.imlib.model.MessagePushConfig; +import io.rong.imlib.model.UserInfo; +import io.rong.push.common.PushConst; +import io.rong.push.common.RLog; +import io.rong.push.notification.PushNotificationMessage; +import io.rong.push.notification.RongNotificationInterface; +import java.util.HashMap; +import java.util.Map; + +/** + * 为解决在 Android 10 以上版本不再允许后台运行 Activity,音视频的离线推送呼叫消息将由通知栏的形式展示给用户 Created by wangw on 2019-12-09. + */ +public class VoIPBroadcastReceiver extends BroadcastReceiver { + + public static final int DEFAULT_FCM_NOTIFICATION_ID = 5000; + private static final String HANGUP = "RC:VCHangup"; + private static final String INVITE = "RC:VCInvite"; + public static final String ACTION_CALLINVITEMESSAGE = "action.push.CallInviteMessage"; + public static final String ACTION_CALLINVITEMESSAGE_CLICKED = + "action.push.CallInviteMessage.CLICKED"; + public static final String ACTION_CALL_HANGUP_CLICKED = "action.push.voip.hangup.click"; + private static final String TAG = "VoIPBroadcastReceiver"; + public static final String ACTION_CLEAR_VOIP_NOTIFICATION = "action.voip.notification.clear"; + private static Map notificationCache = new HashMap<>(); + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + RLog.d(TAG, "onReceive.action:" + action); + + // 通知栏挂断按钮事件响应 + if (ACTION_CALL_HANGUP_CLICKED.equals(action)) { + RongCallSession session = + intent.getParcelableExtra(RongIncomingCallService.KEY_CALL_SESSION); + stopIncomingService(context); + if (session == null) { + RongCallClient.getInstance().hangUpCall(); + } else { + RongCallClient.getInstance().hangUpCall(session.getCallId()); + } + return; + } else if (ACTION_CLEAR_VOIP_NOTIFICATION.equals(action)) { + // 针对 Android 12 的业务逻辑 + IncomingCallExtraHandleUtil.removeNotification(context); + IncomingCallExtraHandleUtil.clear(); + clearNotificationCache(); + return; + } + + PushNotificationMessage message = intent.getParcelableExtra(PushConst.MESSAGE); + // bug fixed : https://rc-jira.rongcloud.net/browse/AC-903 + if (message == null) { + return; + } + RongCallSession callSession = null; + boolean checkPermissions = false; + if (intent.hasExtra("callsession")) { + callSession = intent.getParcelableExtra("callsession"); + checkPermissions = intent.getBooleanExtra("checkPermissions", false); + } + + if (!needShowNotification(context, message)) { + return; + } + + if (TextUtils.equals(ACTION_CALLINVITEMESSAGE, action)) { + if (callSession == null) { + RLog.d(TAG, "callSession is null: " + message); + // fcm voip 走的是透传,处理一下这种情况下,单独弹起一个通知拉起应用 + fcmShowNotification(context, message); + return; + } + clearFcmNotification(context); + String objName = message.getObjectName(); + if (TextUtils.equals(objName, INVITE)) { + IncomingCallExtraHandleUtil.cacheCallSession(callSession, checkPermissions); + UserInfo userInfo = + RongUserInfoManager.getInstance() + .getUserInfo(callSession.getCallerUserId()); + sendNotification(context, message, callSession, checkPermissions, userInfo); + } else { + IncomingCallExtraHandleUtil.clear(); + UserInfo userInfo = + RongUserInfoManager.getInstance() + .getUserInfo(callSession.getCallerUserId()); + sendNotification(context, message, callSession, checkPermissions, userInfo); + } + } else if (TextUtils.equals(ACTION_CALLINVITEMESSAGE_CLICKED, action)) { + IncomingCallExtraHandleUtil.removeNotification(context); + IncomingCallExtraHandleUtil.clear(); + clearNotificationCache(); + handleNotificationClickEvent( + context, + message, + callSession, + checkPermissions, + intent.getStringExtra(RongIncomingCallService.KEY_NEED_AUTO_ANSWER)); + } + } + + private void clearFcmNotification(Context context) { + RLog.d(TAG, "clearFcmNotification"); + NotificationManager nm = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (nm != null) { + nm.cancel(DEFAULT_FCM_NOTIFICATION_ID); + } + } + + public void fcmShowNotification(final Context context, PushNotificationMessage message) { + + try { + CallRingingUtil.getInstance().createNotificationChannel(context); + int smallIcon = + context.getResources() + .getIdentifier( + "notification_small_icon", + "drawable", + context.getPackageName()); + if (smallIcon <= 0) { + smallIcon = context.getApplicationInfo().icon; + } + Uri uri = + Uri.parse("rong://" + context.getPackageName()) + .buildUpon() + .appendPath("conversationlist") + .build(); + Intent intent = new Intent(); + intent.setData(uri); + intent.setPackage(context.getPackageName()); + // 目标activity的包名和类名 + PendingIntent pendingIntent; + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + pendingIntent = + PendingIntent.getActivity( + context, + 1000, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + pendingIntent = + PendingIntent.getActivity( + context, 1000, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + NotificationCompat.Builder notificationBuilder = + new NotificationCompat.Builder( + context, + CallRingingUtil.getInstance().getNotificationChannelId()) + .setContentText(message.getPushContent()) + .setContentTitle(message.getPushTitle()) + .setSmallIcon(smallIcon) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setCategory(NotificationCompat.CATEGORY_CALL) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .setOngoing(true); + Notification notification = notificationBuilder.build(); + NotificationManager notificationManager = null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + notificationManager = context.getSystemService(NotificationManager.class); + } + if (notificationManager != null) { + notificationManager.notify(DEFAULT_FCM_NOTIFICATION_ID, notification); + } + if (HANGUP.equals(message.getObjectName())) { + CallRingingUtil.getInstance().stopRinging(); + } else { + CallRingingUtil.getInstance().startRinging(context, RingingMode.Incoming); + } + + } catch (Exception e) { + io.rong.common.RLog.e(TAG, "onStartCommand = " + e.getMessage()); + e.printStackTrace(); + } + } + + private boolean needShowNotification(Context context, PushNotificationMessage message) { + if (message == null || context == null) { + return false; + } + if (INVITE.equals(message.getObjectName()) + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + // Android 10 以下允许后台运行,直接交由会话列表界面拉取消息 + RLog.d(TAG, "handle VoIP event."); + try { + Intent newIntent = new Intent(); + newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Uri uri = + Uri.parse("rong://" + context.getPackageName()) + .buildUpon() + .appendPath("conversationlist") + .appendQueryParameter("isFromPush", "false") + .build(); + newIntent.setData(uri); + newIntent.setPackage(context.getPackageName()); + context.startActivity(newIntent); + } catch (Exception e) { + e.printStackTrace(); + return true; + } + return false; + } + return true; + } + + @SuppressLint("QueryPermissionsNeeded") + private void handleNotificationClickEvent( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions, + String needAutoAnswerCallId) { + Intent intent; + // 如果进程被杀 RongCallClient.getInstance() 返回Null + if (RongCallClient.getInstance() != null + && RongCallClient.getInstance().getCallSession() != null + && callSession != null) { + intent = RongCallModule.createVoIPIntent(context, callSession, checkPermissions); + RLog.d(TAG, "handleNotificationClickEvent: start call activity"); + } else { + intent = createConversationListIntent(context); + RLog.d(TAG, "handleNotificationClickEvent: start conversation activity"); + } + if (callSession != null && !TextUtils.isEmpty(needAutoAnswerCallId)) { + intent.putExtra(RongIncomingCallService.KEY_NEED_AUTO_ANSWER, callSession.getCallId()); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setPackage(context.getPackageName()); + // bug fixed : https://rc-jira.rongcloud.net/browse/AC-864 + if (intent.resolveActivity(context.getPackageManager()) != null) { + context.startActivity(intent); + } else { + StringBuilder builder = new StringBuilder("start activity with scheme : "); + if (intent.getData() != null) { + builder.append(intent.getData().toString()); + } + FwLog.write(FwLog.I, FwLog.IM, "L-VoIP_notify_scheme", "scheme", builder.toString()); + } + } + + private void sendNotification( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions, + UserInfo userInfo) { + RLog.d(TAG, "sendNotification: " + message.getObjectName()); + String pushContent; + boolean isAudio = callSession.getMediaType() == RongCallCommon.CallMediaType.AUDIO; + if (HANGUP.equals(message.getObjectName())) { + pushContent = + context.getResources().getString(R.string.rc_voip_call_terminalted_notify); + if (callSession.getConversationType().equals(ConversationType.GROUP) + && RongCallClient.getInstance().getCallSession() != null) { + return; // 群组消息,getCallSession不为空,说明收到的hangup并不是最后一个人发出的,此时不需要生成通知 + } + } else { + pushContent = + context.getResources() + .getString( + isAudio + ? R.string.rc_voip_audio_call_inviting + : R.string.rc_voip_video_call_inviting); + } + message.setPushContent(pushContent); + if (callSession.getConversationType().equals(ConversationType.PRIVATE)) { + if (userInfo != null && !TextUtils.isEmpty(userInfo.getName())) { + message.setPushTitle(userInfo.getName()); + } + } else if (callSession.getConversationType().equals(ConversationType.GROUP)) { + Group group = RongUserInfoManager.getInstance().getGroupInfo(callSession.getTargetId()); + if (group != null && !TextUtils.isEmpty(group.getName())) { + message.setPushTitle(group.getName()); + } + } + if (callSession != null && callSession.getPushConfig() != null) { + MessagePushConfig messagePushConfig = callSession.getPushConfig(); + if (!TextUtils.isEmpty(messagePushConfig.getPushTitle())) { + message.setPushTitle(messagePushConfig.getPushTitle()); + } + if (!TextUtils.isEmpty(messagePushConfig.getPushContent()) + && !messagePushConfig.getPushContent().equals("voip")) { + // message.setPushContent(messagePushConfig.getPushContent()); + } + if (messagePushConfig.isForceShowDetailContent()) { + message.setShowDetail(messagePushConfig.isForceShowDetailContent()); + } + AndroidConfig androidConfig = messagePushConfig.getAndroidConfig(); + if (androidConfig != null) { + message.setChannelIdHW(androidConfig.getChannelIdHW()); + message.setChannelIdMi(androidConfig.getChannelIdMi()); + message.setChannelIdOPPO(androidConfig.getChannelIdOPPO()); + message.setNotificationId(androidConfig.getNotificationId()); + } + } + sendNotification(context, message, callSession, checkPermissions); + } + + private void startIncomingService( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Bundle bundle = new Bundle(); + bundle.putParcelable(RongIncomingCallService.KEY_MESSAGE, message); + bundle.putParcelable(RongIncomingCallService.KEY_CALL_SESSION, callSession); + bundle.putBoolean(RongIncomingCallService.KEY_CHECK_PERMISSIONS, checkPermissions); + CallRingingUtil.getInstance().startRingingService(context, bundle); + } + } + + private void stopIncomingService(Context context) { + CallRingingUtil.getInstance().stopService(context); + } + + private void sendNotification( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions) { + String objName = message.getObjectName(); + if (TextUtils.isEmpty(objName)) { + return; + } + + String title; + String content; + int notificationId = IncomingCallExtraHandleUtil.VOIP_NOTIFICATION_ID; + RLog.i( + TAG, + "sendNotification() messageType: " + + message.getConversationType() + + " messagePushContent: " + + message.getPushContent() + + " messageObjectName: " + + message.getObjectName() + + " notificationId: " + + message.getNotificationId()); + + // Android 10 以上走新逻辑,通过服务启动 + if (Build.VERSION.SDK_INT >= 29) { + if (INVITE.equals(objName)) { + startIncomingService(context, message, callSession, checkPermissions); + } else if (HANGUP.equals(objName)) { + stopIncomingService(context); + sendHangupNotification(context, message, callSession, checkPermissions); + } + return; + } + + if (objName.equals(INVITE) || objName.equals(HANGUP)) { + content = message.getPushContent(); + title = message.getPushTitle(); + } else { + return; + } + + Notification notification = + RongNotificationInterface.createNotification( + context, + title, + createPendingIntent( + context, + message, + callSession, + checkPermissions, + IncomingCallExtraHandleUtil.VOIP_REQUEST_CODE, + false), + content, + RongNotificationInterface.SoundType.VOIP, + message.isShowDetail()); + NotificationManager nm = + (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + int importance = NotificationManager.IMPORTANCE_HIGH; + String channelName = CallRingingUtil.getInstance().getNotificationChannelName(context); + NotificationChannel notificationChannel = + new NotificationChannel( + CallRingingUtil.getInstance().getNotificationChannelId(), + channelName, + importance); + notificationChannel.enableLights(true); + notificationChannel.setLightColor(Color.GREEN); + if (notification != null && notification.sound != null) { + notificationChannel.setSound(notification.sound, null); + } + nm.createNotificationChannel(notificationChannel); + } + if (notification != null) { + RLog.i( + TAG, + "sendNotification() real notify! notificationId: " + + notificationId + + " notification: " + + notification.toString()); + if (INVITE.equals(message.getObjectName())) { + notificationCache.put(callSession.getCallId(), notificationId); + nm.notify(notificationId, notification); + IncomingCallExtraHandleUtil.VOIP_NOTIFICATION_ID++; + } else if (notificationCache.containsKey(callSession.getCallId())) { + notificationId = notificationCache.get(callSession.getCallId()); + nm.notify(notificationId, notification); + } + } + } + + private void sendHangupNotification( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions) { + try { + String content = + context.getResources().getString(R.string.rc_voip_call_terminalted_notify); + String title = message.getPushTitle(); + int notificationId = IncomingCallExtraHandleUtil.VOIP_NOTIFICATION_ID; + Notification notification = + RongNotificationInterface.createNotification( + context, + title, + createPendingIntent( + context, + message, + callSession, + checkPermissions, + IncomingCallExtraHandleUtil.VOIP_REQUEST_CODE, + false), + content, + RongNotificationInterface.SoundType.VOIP, + message.isShowDetail()); + if (notification == null) { + return; + } + NotificationManager nm = + (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + CallRingingUtil.getInstance().createNotificationChannel(context); + RLog.i( + TAG, + "sendNotification() real notify! notificationId: " + + notificationId + + " notification: " + + notification.toString()); + notification.defaults = Notification.DEFAULT_ALL; + notification.sound = null; + nm.notify(notificationId, notification); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static PendingIntent createPendingIntent( + Context context, + PushNotificationMessage message, + RongCallSession callSession, + boolean checkPermissions, + int requestCode, + boolean isMulti) { + Intent intent = new Intent(); + intent.setAction(ACTION_CALLINVITEMESSAGE_CLICKED); + intent.putExtra(PushConst.MESSAGE, message); + intent.putExtra("callsession", callSession); + intent.putExtra("checkPermissions", checkPermissions); + intent.putExtra(PushConst.IS_MULTI, isMulti); + intent.setPackage(context.getPackageName()); + // KNOTE: 2021/9/29 PendingIntent + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return PendingIntent.getBroadcast( + context, + requestCode, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + return PendingIntent.getBroadcast( + context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + } + + private static Intent createConversationListIntent(Context context) { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Uri uri = + Uri.parse("rong://" + context.getPackageName()) + .buildUpon() + .appendPath("conversationlist") + .build(); + intent.setData(uri); + intent.setPackage(context.getPackageName()); + return intent; + } + + public static void clearNotificationCache() { + notificationCache.clear(); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/ActivityStartCheckUtils.java b/callkit/src/main/java/io/rong/callkit/util/ActivityStartCheckUtils.java new file mode 100644 index 000000000..7871e355d --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/ActivityStartCheckUtils.java @@ -0,0 +1,156 @@ +package io.rong.callkit.util; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import io.rong.callkit.RongCallModule; +import io.rong.callkit.RongVoIPIntent; + +public class ActivityStartCheckUtils { + public interface ActivityStartResultCallback { + void onStartActivityResult(boolean isActivityStarted); + } + + private static final int TIME_DELAY = 3000; + private boolean mPostDelayIsRunning; + private String mClassName; + private Handler mHandler = new Handler(); + private Activity topActivity; + private Context mAppContext; + private ActivityStartResultCallback activityStartResultCallback; + private static final String[] CALL_ACTIONS = { + RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIVIDEO, + RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIAUDIO, + RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEVIDEO, + RongVoIPIntent.RONG_INTENT_ACTION_VOIP_SINGLEAUDIO + }; + + private static class SingletonHolder { + + static ActivityStartCheckUtils sInstance = new ActivityStartCheckUtils(); + } + + private ActivityStartCheckUtils() {} + + public static ActivityStartCheckUtils getInstance() { + return SingletonHolder.sInstance; + } + + public void registerActivityLifecycleCallbacks(Context context) { + + mAppContext = context.getApplicationContext(); + Application application = (Application) mAppContext; + + if (application == null) return; + + application.registerActivityLifecycleCallbacks( + new Application.ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} + + @Override + public void onActivityStarted(Activity activity) {} + + @Override + public void onActivityResumed(Activity activity) { + topActivity = activity; + handleIncomingCallNotify(topActivity); + } + + @Override + public void onActivityPaused(Activity activity) {} + + @Override + public void onActivityStopped(Activity activity) { + if (topActivity == activity) { + topActivity = null; + } + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + + @Override + public void onActivityDestroyed(Activity activity) {} + }); + } + + public String getTopActivity() { + if (topActivity == null) { + return null; + } + + return topActivity.getClass().getSuperclass().getSimpleName(); + } + + public void startActivity( + Context context, + Intent intent, + String className, + ActivityStartResultCallback callback) { + if (context == null || intent == null || TextUtils.isEmpty(className)) { + return; + } + context.startActivity(intent); + mClassName = className; + if (mPostDelayIsRunning) { + mHandler.removeCallbacks(mRunnable); + } + mPostDelayIsRunning = true; + activityStartResultCallback = callback; + mHandler.postDelayed(mRunnable, TIME_DELAY); + } + + private boolean isActivityOnTop() { + boolean result = false; + String topActivityName = getTopActivity(); + if (!TextUtils.isEmpty(topActivityName)) { + if (topActivityName.contains(mClassName)) { + result = true; + } + } + return result; + } + + private Runnable mRunnable = + new Runnable() { + @Override + public void run() { + mPostDelayIsRunning = false; + boolean isOnTop = isActivityOnTop(); + if (activityStartResultCallback != null) { + activityStartResultCallback.onStartActivityResult(isOnTop); + } + } + }; + + /** + * Android 10 以上禁止后台启动 Activity callKit 适配方案是后台来电时弹通知栏通知,但是如果用户不点击通知栏, + * 通过桌面图标打开应用,需要增加一种补偿机制启动来电界面 + */ + private void handleIncomingCallNotify(Activity activity) { + if (activity != null && IncomingCallExtraHandleUtil.needNotify()) { + String action = activity.getIntent().getAction(); + if (!TextUtils.isEmpty(action)) { + for (String ac : CALL_ACTIONS) { + if (TextUtils.equals(ac, action)) { + IncomingCallExtraHandleUtil.clear(); // 解决鸿蒙3.0设备 mate 10 重复启动通话界面问题 + return; + } + } + } + + IncomingCallExtraHandleUtil.removeNotification(mAppContext); + activity.startActivity( + RongCallModule.createVoIPIntent( + mAppContext, + IncomingCallExtraHandleUtil.getCallSession(), + IncomingCallExtraHandleUtil.isCheckPermissions())); + IncomingCallExtraHandleUtil.clear(); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/BluetoothUtil.java b/callkit/src/main/java/io/rong/callkit/util/BluetoothUtil.java new file mode 100644 index 000000000..19443a134 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/BluetoothUtil.java @@ -0,0 +1,305 @@ +package io.rong.callkit.util; + +import static android.content.Context.AUDIO_SERVICE; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityManager; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothProfile; +import android.content.ComponentName; +import android.content.Context; +import android.media.AudioManager; +import android.text.TextUtils; +import io.rong.common.RLog; +import java.util.List; + +/** Created by degnxudong on 2018/8/24. */ +public class BluetoothUtil { + + private static final String TAG = "BluetoothUtil"; + + /** + * 是否连接了蓝牙耳机 + * + * @return + */ + @SuppressLint("WrongConstant") + public static boolean hasBluetoothA2dpConnected() { + boolean bool = false; + BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter != null && mAdapter.isEnabled()) { + int a2dp = mAdapter.getProfileConnectionState(BluetoothProfile.A2DP); + if (a2dp == BluetoothProfile.STATE_CONNECTED) { + bool = true; + } + } + return bool; + } + + /** + * 是否插入了有线耳机 + * + * @param context + * @return + */ + public static boolean isWiredHeadsetOn(Context context) { + AudioManager audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE); + return audioManager.isWiredHeadsetOn(); + } + + private static String getStyleContent(int styleMajor) { + String content = "未知...."; + switch (styleMajor) { + case BluetoothClass.Device.Major.AUDIO_VIDEO: // 音频设备 + content = "音配设备"; + break; + case BluetoothClass.Device.Major.COMPUTER: // 电脑 + content = "电脑"; + break; + case BluetoothClass.Device.Major.HEALTH: // 健康状况 + content = "健康状况"; + break; + case BluetoothClass.Device.Major.IMAGING: // 镜像,映像 + content = "镜像"; + break; + case BluetoothClass.Device.Major.MISC: // 麦克风 + content = "麦克风"; + break; + case BluetoothClass.Device.Major.NETWORKING: // 网络 + content = "网络"; + break; + case BluetoothClass.Device.Major.PERIPHERAL: // 外部设备 + content = "外部设备"; + break; + case BluetoothClass.Device.Major.PHONE: // 电话 + content = "电话"; + break; + case BluetoothClass.Device.Major.TOY: // 玩具 + content = "玩具"; + break; + case BluetoothClass.Device.Major.UNCATEGORIZED: // 未知的 + content = "未知的"; + break; + case BluetoothClass.Device.Major.WEARABLE: // 穿戴设备 + content = "穿戴设备"; + break; + } + return content; + } + + private static boolean getDeviceClass(int deviceClass) { + boolean bool = false; + switch (deviceClass) { + case BluetoothClass.Device.AUDIO_VIDEO_CAMCORDER: // 录像机 + // "录像机"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: + // "车载设备"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: + // "蓝牙耳机"; + bool = true; + break; + case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: + // "扬声器"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE: + // "麦克风"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: + // "打印机"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX: + // "BOX"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED: + // "未知的"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_VCR: + // "录像机"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CAMERA: + // "照相机录像机"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING: + // "conferencing"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER: + // "显示器和扬声器"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_GAMING_TOY: + // "游戏"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_MONITOR: + // "显示器"; + break; + case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: + // "可穿戴设备"; + bool = true; + break; + case BluetoothClass.Device.PHONE_CELLULAR: + // "手机"; + break; + case BluetoothClass.Device.PHONE_CORDLESS: + // "无线电设备"; + break; + case BluetoothClass.Device.PHONE_ISDN: + // "手机服务数据网"; + break; + case BluetoothClass.Device.PHONE_MODEM_OR_GATEWAY: + // "手机调节器"; + break; + case BluetoothClass.Device.PHONE_SMART: + // "手机卫星"; + break; + case BluetoothClass.Device.PHONE_UNCATEGORIZED: + // "未知手机"; + break; + case BluetoothClass.Device.WEARABLE_GLASSES: + // "可穿戴眼睛"; + break; + case BluetoothClass.Device.WEARABLE_HELMET: + // "可穿戴头盔"; + break; + case BluetoothClass.Device.WEARABLE_JACKET: + // "可穿戴上衣"; + break; + case BluetoothClass.Device.WEARABLE_PAGER: + // "客串点寻呼机"; + break; + case BluetoothClass.Device.WEARABLE_UNCATEGORIZED: + // "未知的可穿戴设备"; + break; + case BluetoothClass.Device.WEARABLE_WRIST_WATCH: + // "手腕监听设备"; + break; + case BluetoothClass.Device.TOY_CONTROLLER: + // "可穿戴设备"; + break; + case BluetoothClass.Device.TOY_DOLL_ACTION_FIGURE: + // "玩具doll_action_figure"; + break; + case BluetoothClass.Device.TOY_GAME: + // "游戏"; + break; + case BluetoothClass.Device.TOY_ROBOT: + // "玩具遥控器"; + break; + case BluetoothClass.Device.TOY_UNCATEGORIZED: + // "玩具未知设备"; + break; + case BluetoothClass.Device.TOY_VEHICLE: + // "vehicle"; + break; + case BluetoothClass.Device.HEALTH_BLOOD_PRESSURE: + // "健康状态-血压"; + break; + case BluetoothClass.Device.HEALTH_DATA_DISPLAY: + // "健康状态数据"; + break; + case BluetoothClass.Device.HEALTH_GLUCOSE: + // "健康状态葡萄糖"; + break; + case BluetoothClass.Device.HEALTH_PULSE_OXIMETER: + // "健康状态脉搏血氧计"; + break; + case BluetoothClass.Device.HEALTH_PULSE_RATE: + // "健康状态脉搏速率"; + break; + case BluetoothClass.Device.HEALTH_THERMOMETER: + // "健康状态体温计"; + break; + case BluetoothClass.Device.HEALTH_WEIGHING: + // "健康状态体重"; + break; + case BluetoothClass.Device.HEALTH_UNCATEGORIZED: + // "未知健康状态设备"; + break; + case BluetoothClass.Device.COMPUTER_DESKTOP: + // "电脑桌面"; + break; + case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA: + // "手提电脑或Pad"; + break; + case BluetoothClass.Device.COMPUTER_LAPTOP: + // "便携式电脑"; + break; + case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA: + // "微型电脑"; + break; + case BluetoothClass.Device.COMPUTER_SERVER: + // "电脑服务"; + break; + case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: + // "未知的电脑设备"; + break; + case BluetoothClass.Device.COMPUTER_WEARABLE: + /// "可穿戴的电脑"; + break; + } + return bool; + } + + public static boolean isForground(Activity activity) { + return isForground(activity, activity.getClass().getName()); + } + + private static boolean isForground(Context context, String className) { + if (context == null || TextUtils.isEmpty(className)) { + return false; + } + ActivityManager activityManager = + (ActivityManager) context.getSystemService(context.ACTIVITY_SERVICE); + List list = activityManager.getRunningTasks(1); + if (null != list && list.size() > 0) { + ComponentName componentName = list.get(0).topActivity; + if (className.equals(componentName.getClassName())) { + return true; + } + } + return false; + } + + /** + * 是否支持蓝牙 + * + * @return + */ + public static boolean isSupportBluetooth() { + boolean bool = false; + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (null != bluetoothAdapter) { + bool = true; + } + RLog.i(TAG, "isSupportBluetooth = " + bool); + return bool; + } + + public static void startBlueToothSco(Context context) { + AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + if (am != null) { + if (am.getMode() != AudioManager.MODE_IN_COMMUNICATION) { + am.setMode(AudioManager.MODE_IN_COMMUNICATION); + } + if (!am.isBluetoothScoOn()) { + am.startBluetoothSco(); + am.setBluetoothScoOn(false); + } + } + } + + public static void stopBlueToothSco(Context context) { + AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + if (am != null) { + // if (am.getMode() != AudioManager.MODE_IN_COMMUNICATION) { + // am.setMode(AudioManager.MODE_IN_COMMUNICATION); + // } + if (am.isBluetoothScoOn()) { + am.stopBluetoothSco(); + am.setBluetoothScoOn(false); + } + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/BlurBitmapUtil.java b/callkit/src/main/java/io/rong/callkit/util/BlurBitmapUtil.java new file mode 100644 index 000000000..5d572a745 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/BlurBitmapUtil.java @@ -0,0 +1,59 @@ +package io.rong.callkit.util; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Build; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; + +/** Created by dengxudong on 2018/5/18. */ +public class BlurBitmapUtil { + + private static class SingletonHolder { + + static BlurBitmapUtil sInstance = new BlurBitmapUtil(); + } + + private BlurBitmapUtil() {} + + public static BlurBitmapUtil instance() { + return SingletonHolder.sInstance; + } + /** + * @param context 上下文对象 + * @param image 需要模糊的图片 + * @param outWidth 输入出的宽度 + * @param outHeight 输出的高度 + * @return 模糊处理后的Bitmap + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public Bitmap blurBitmap( + Context context, Bitmap image, float blurRadius, int outWidth, int outHeight) { + // 将缩小后的图片做为预渲染的图片 + Bitmap inputBitmap = Bitmap.createScaledBitmap(image, outWidth, outHeight, false); + // 创建一张渲染后的输出图片 + Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap); + // 创建RenderScript内核对象 + RenderScript rs = RenderScript.create(context); + // 创建一个模糊效果的RenderScript的工具对象 + ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + // 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间 + // 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去 + Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap); + Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap); + // 设置渲染的模糊程度, 25f是最大模糊度 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + blurScript.setRadius(blurRadius); + } + // 设置blurScript对象的输入内存 + blurScript.setInput(tmpIn); + // 将输出数据保存到输出内存中 + blurScript.forEach(tmpOut); + // 将数据填充到Allocation中 + tmpOut.copyTo(outputBitmap); + return outputBitmap; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/CallKitSearchBarListener.java b/callkit/src/main/java/io/rong/callkit/util/CallKitSearchBarListener.java new file mode 100644 index 000000000..8eab8e8c0 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/CallKitSearchBarListener.java @@ -0,0 +1,16 @@ +package io.rong.callkit.util; + +public interface CallKitSearchBarListener { + /** + * 开始搜索 EditText 中输入内容后,会触发此回调 + * + * @param keyword 搜索关键字 + */ + void onSearchStart(String keyword); + + /** 软键盘中"搜索"被点击后,触发此回调 此回调被触发后,仅收起软键盘 */ + void onSoftSearchKeyClick(); + + /** 搜索控件中,点击"清除"后,触发此回调 */ + void onClearButtonClick(); +} diff --git a/callkit/src/main/java/io/rong/callkit/util/CallKitSearchBarView.java b/callkit/src/main/java/io/rong/callkit/util/CallKitSearchBarView.java new file mode 100644 index 000000000..d15420617 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/CallKitSearchBarView.java @@ -0,0 +1,153 @@ +package io.rong.callkit.util; + +import android.content.Context; +import android.os.Handler; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import io.rong.callkit.R; + +public class CallKitSearchBarView extends RelativeLayout { + + private EditText editSearch; + private View clearBtn; + private ImageView searchIV; + private CallKitSearchBarListener listener; + private Handler handler; + private boolean searchContentCleared; + + public CallKitSearchBarView(final Context context, AttributeSet attrs) { + super(context, attrs); + inflate(context, R.layout.callkit_view_search_bar_layout, this); + searchIV = findViewById(R.id.iv_icon); + editSearch = findViewById(R.id.et_search); + if (CallKitUtils.findConfigurationLanguage(context, "ar")) { + editSearch.setTextDirection(View.TEXT_DIRECTION_RTL); + } + handler = new Handler(); + editSearch.addTextChangedListener( + new TextWatcher() { + Runnable searchRunnable = null; + + @Override + public void beforeTextChanged( + CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (!TextUtils.isEmpty(s.toString())) { + searchIV.setImageDrawable( + getResources() + .getDrawable(R.drawable.callkit_ic_search_focused_x)); + clearBtn.setVisibility(VISIBLE); + } else { + searchIV.setImageDrawable( + getResources().getDrawable(R.drawable.callkit_ic_search_x)); + clearBtn.setVisibility(GONE); + } + } + + @Override + public void afterTextChanged(Editable s) { + if (listener == null) { + return; + } + if (searchRunnable != null) { + handler.removeCallbacks(searchRunnable); + } + final String keywords = editSearch.getText().toString().trim(); + if (!TextUtils.isEmpty(keywords)) { + searchContentCleared = false; + searchRunnable = + new Runnable() { + @Override + public void run() { + listener.onSearchStart(keywords); + } + }; + handler.postDelayed(searchRunnable, 500); + } else { + if (!searchContentCleared) { + listener.onClearButtonClick(); + } + } + } + }); + editSearch.setOnEditorActionListener( + new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + listener.onSoftSearchKeyClick(); + } + return false; + } + }); + clearBtn = findViewById(R.id.iv_clear); + clearBtn.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + editSearch.setText(""); + searchIV.setImageDrawable( + getResources().getDrawable(R.drawable.callkit_ic_search_x)); + clearBtn.setVisibility(GONE); + listener.onClearButtonClick(); + } + }); + + setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + InputMethodManager imm = + (InputMethodManager) + context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(editSearch, InputMethodManager.HIDE_NOT_ALWAYS); + listener.onSoftSearchKeyClick(); + } + }); + } + + boolean isSearchTextEmpty() { + return editSearch.getText().toString().equals(""); + } + + public void setSearchBarListener(CallKitSearchBarListener listener) { + this.listener = listener; + } + + public void clearSearchContent() { + searchContentCleared = true; + editSearch.setText(""); + } + + public void setSearchHint(String text) { + editSearch.setHint(text); + } + + void setSearchText(String content) { + if (TextUtils.isEmpty(content)) { + clearBtn.setVisibility(GONE); + return; + } + editSearch.setText(content); + editSearch.setSelection(content.length()); + searchIV.setImageDrawable( + getResources().getDrawable(R.drawable.callkit_ic_search_focused_x)); + clearBtn.setVisibility(VISIBLE); + } + + public EditText getEditText() { + return editSearch; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/CallKitUtils.java b/callkit/src/main/java/io/rong/callkit/util/CallKitUtils.java new file mode 100644 index 000000000..1a38ed40e --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/CallKitUtils.java @@ -0,0 +1,270 @@ +package io.rong.callkit.util; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.IBinder; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.View; +import android.view.Window; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.app.AppOpsManagerCompat; +import androidx.core.content.ContextCompat; +import io.rong.callkit.R; +import io.rong.calllib.RongCallCommon; +import io.rong.common.RLog; +import io.rong.imlib.model.Message; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** Created by dengxudong on 2018/5/17. */ +public class CallKitUtils { + + /** 拨打true or 接听false */ + public static boolean isDial = true; + + public static boolean shouldShowFloat; + /** 是否已经建立通话连接 默认没有,为了修改接听之后将情景模式切换成震动 在通话界面一直震动的问题 */ + public static boolean callConnected = false; + /** true:响铃中,false:响铃已结束 */ + // public static boolean RINGSTATE=false; + /** 当前 免提 是否打开的状态 true:打开中 */ + public static boolean speakerphoneState = false; + + public static StringBuffer stringBuffer = null; + + private static Map mapLastClickTime = new HashMap<>(); + + public static Drawable BackgroundDrawable(int drawable, Context context) { + return ContextCompat.getDrawable(context, drawable); + } + + public static int dp2px(float dpVal, Context context) { + return (int) + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dpVal, + context.getResources().getDisplayMetrics()); + } + + /** 关闭软键盘 */ + public static void closeKeyBoard(Activity activity, View view) { + IBinder token; + if (view == null || view.getWindowToken() == null) { + if (null == activity) { + return; + } + Window window = activity.getWindow(); + if (window == null) { + return; + } + View v = window.peekDecorView(); + if (v == null) { + return; + } + token = v.getWindowToken(); + } else { + token = view.getWindowToken(); + } + InputMethodManager imm = + (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(token, 0); + } + + /** + * 提供(相对)精确的除法运算。 + * + * @param vl1 被除数 + * @param vl2 除数 + * @return 商 + */ + public static double div(double vl1, double vl2) { + + BigDecimal b1 = BigDecimal.valueOf(vl1); + BigDecimal b2 = BigDecimal.valueOf(vl2); + // 4 表示表示需要精确到小数点以后几位。当发生除不尽的情况时,参数指定精度,以后的数字四舍五入。 + return b1.divide(b2, 4, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + /** 四舍五入把double转化int整型 */ + public static int getInt(double number) { + BigDecimal bd = BigDecimal.valueOf(number).setScale(0, BigDecimal.ROUND_HALF_UP); + return Integer.parseInt(bd.toString()); + } + + public static void textViewShadowLayer(TextView text, Context context) { + if (null == text) { + return; + } + text.setShadowLayer( + 16F, + 0F, + 2F, + context.getApplicationContext() + .getResources() + .getColor(R.color.callkit_shadowcolor)); + } + + public static boolean checkPermissions(Context context, @NonNull String[] permissions) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + if (permissions == null || permissions.length == 0) { + return true; + } + for (String permission : permissions) { + if (!hasPermission(context, permission)) { + return false; + } + } + return true; + } + + private static boolean hasPermission(Context context, String permission) { + String opStr = AppOpsManagerCompat.permissionToOp(permission); + if (opStr == null) { + return true; + } + boolean bool = + context.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + return bool; + } + + public static String[] getCallpermissions() { + String[] permissions = + new String[] { + Manifest.permission.CAMERA, + Manifest.permission.RECORD_AUDIO, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.INTERNET, + Manifest.permission.MODIFY_AUDIO_SETTINGS, + Manifest.permission.BLUETOOTH, + Manifest.permission.BLUETOOTH_ADMIN, + Manifest.permission.READ_PHONE_STATE, + }; + return permissions; + } + + /** 获取字符串指定拼接内容 */ + public static String getStitchedContent(String val1, String val2) { + if (TextUtils.isEmpty(val1)) { + val1 = ""; + } + if (TextUtils.isEmpty(val2)) { + val2 = ""; + } + if (stringBuffer == null) { + stringBuffer = new StringBuffer(); + } else { + stringBuffer.setLength(0); + } + stringBuffer.append(val1); + stringBuffer.append(val2); + return stringBuffer.toString(); + } + + /** 是否是debug状态 */ + public static boolean isDebug(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + return false; + } + } + + public static boolean isNetworkAvailable(Context context) { + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + if (networkInfo == null || !networkInfo.isConnected() || !networkInfo.isAvailable()) { + return false; + } + return true; + } + + /** double click */ + public static boolean isFastDoubleClick() { + return isFastDoubleClick("Default"); + } + + public static boolean isFastDoubleClick(String eventType) { + Long lastClickTime = mapLastClickTime.get(eventType); + if (lastClickTime == null) { + lastClickTime = 0l; + } + long curTime = System.currentTimeMillis(); + long timeD = curTime - lastClickTime; + if (timeD > 0 && timeD < 800) { + return true; + } + mapLastClickTime.put(eventType, curTime); + return false; + } + + /** 昵称长度超过5,后面使用...显示 */ + public static String nickNameRestrict(String userName) { + if (!TextUtils.isEmpty(userName) && userName.length() > 5) { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(userName.substring(0, 5).trim()); + stringBuffer.append("..."); + userName = stringBuffer.toString(); + } + return userName; + } + + public static Message.ReceivedStatus getReceivedStatus( + RongCallCommon.CallDisconnectedReason reason) { + // 己方超时未接听或是对方取消通话时,应插入未读消息 + if (reason == RongCallCommon.CallDisconnectedReason.REMOTE_CANCEL + || reason == RongCallCommon.CallDisconnectedReason.NO_RESPONSE) { + return new Message.ReceivedStatus(0); + } + // 默认是已读状态 + return new Message.ReceivedStatus(1); + } + + /** @param language zh en ar */ + public static boolean findConfigurationLanguage(Context context, String language) { + if (context == null || TextUtils.isEmpty(language)) { + RLog.w("bugtags", "findConfigurationLanguage->Resources is empty"); + return false; + } + Resources resources = context.getResources(); + if (resources == null) { + RLog.w("bugtags", "findConfigurationLanguage->Resources is empty"); + return false; + } + Configuration configuration = resources.getConfiguration(); + if (configuration == null || configuration.locale == null) { + RLog.w("bugtags", "findConfigurationLanguage->configuration is empty"); + return false; + } + + Locale locale = configuration.locale; + String languageApp = locale.getLanguage(); + RLog.d( + "bugtags", + "findConfigurationLanguage->languageApp : " + + languageApp + + " ,language : " + + language); + return TextUtils.equals(languageApp, language); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/CallRingingUtil.java b/callkit/src/main/java/io/rong/callkit/util/CallRingingUtil.java new file mode 100644 index 000000000..9456ff613 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/CallRingingUtil.java @@ -0,0 +1,427 @@ +package io.rong.callkit.util; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.AssetFileDescriptor; +import android.media.AudioAttributes; +import android.media.AudioAttributes.Builder; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Vibrator; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import androidx.annotation.DrawableRes; +import androidx.annotation.RequiresApi; +import io.rong.callkit.R; +import io.rong.callkit.RongIncomingCallService; +import io.rong.common.RLog; +import io.rong.imkit.notification.NotificationUtil; +import io.rong.push.notification.RongNotificationHelper; +import java.io.IOException; + +/** @author gusd @Date 2021/09/14 */ +public class CallRingingUtil { + private static final String TAG = "CallRingingUtil"; + private volatile boolean isRinging = false; + private volatile RingingMode mCurrentRingingMode = null; + + private MediaPlayer mMediaPlayer; + private Vibrator mVibrator; + private Context applicationContext; + private volatile boolean stopServiceAndRingingTag = false; + private volatile boolean isFirstReceivedBroadcast = true; + + private static final String DEFAULT_CHANNEL_NAME = "VOIP"; + private static final String DEFAULT_CHANNEL_ID = + RongNotificationHelper.getDefaultVoipChannelId(); + public static final int DEFAULT_ANSWER_ICON = R.drawable.rc_voip_notification_answer; + public static final int DEFAULT_HANGUP_ICON = R.drawable.rc_voip_notification_hangup; + + @DrawableRes private int answerIcon = DEFAULT_ANSWER_ICON; + @DrawableRes private int hangupIcon = DEFAULT_HANGUP_ICON; + + private CallRingingUtil() {} + + private static class InstanceHolder { + static final CallRingingUtil instance = new CallRingingUtil(); + } + + public static CallRingingUtil getInstance() { + return InstanceHolder.instance; + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public void startRingingService(Context context, Bundle bundle) { + if (!isRingingServiceRunning(context)) { + Intent intent = new Intent(); + intent.putExtras(bundle); + intent.setPackage(context.getPackageName()); + // KNOTE: 2021/9/29 前台服务启动限制 + try { + RongIncomingCallService.getInstance().startRing(context, intent); + } catch (Exception e) { + e.printStackTrace(); + RLog.e(TAG, e.getMessage()); + } + } + } + + public void stopService(Context context) { + RLog.d(TAG, "stopService: "); + if (isRingingServiceRunning(context)) { + RongIncomingCallService.getInstance().stopRinging(context); + } + } + + public void stopServiceButContinueRinging(Context context) { + RLog.d(TAG, "stopServiceButContinueRinging: "); + if (!isRingingServiceRunning(context)) { + stopServiceAndRingingTag = false; + return; + } + stopServiceAndRingingTag = true; + stopService(context); + } + + public boolean isRingingServiceRunning(Context context) { + return RongIncomingCallService.getInstance().isRinging(); + } + + public void startRinging(Context context, RingingMode mode) { + RLog.d(TAG, "startRinging: "); + if (isRinging) { + return; + } + + if (context == null) { + return; + } + applicationContext = context.getApplicationContext(); + + // 注册 BroadcastReceiver 监听情景模式的切换 + IntentFilter filter = new IntentFilter(); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + isFirstReceivedBroadcast = true; + applicationContext.registerReceiver( + mRingModeReceiver, + filter, + context.getApplicationInfo().packageName + ".permission.RONG_ACCESS_RECEIVER", + null); + + if (mode == RingingMode.Incoming || mode == RingingMode.Incoming_Custom) { + int ringerMode = NotificationUtil.getInstance().getRingerMode(context); + if (ringerMode != AudioManager.RINGER_MODE_SILENT) { + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + startVibrator(context); + } else { + if (isVibrateWhenRinging(context)) { + startVibrator(context); + } + callRinging(context, mode); + } + } + } else { + callRinging(context, mode); + } + mCurrentRingingMode = mode; + isRinging = true; + } + + protected void startVibrator(Context context) { + Log.d(TAG, "startVibrator: "); + if (mVibrator == null) { + mVibrator = + (Vibrator) + context.getApplicationContext() + .getSystemService(Context.VIBRATOR_SERVICE); + } else { + mVibrator.cancel(); + } + + long[] pattern = {500, 1000}; + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + AudioAttributes build = + new Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build(); + mVibrator.vibrate(pattern, 0, build); + } else { + mVibrator.vibrate(pattern, 0); + } + } + + /** 判断系统是否设置了 响铃时振动 */ + private boolean isVibrateWhenRinging(Context context) { + ContentResolver resolver = context.getApplicationContext().getContentResolver(); + if (Build.MANUFACTURER.equals("Xiaomi")) { + return Settings.System.getInt(resolver, "vibrate_in_normal", 0) == 1; + } else if (Build.MANUFACTURER.equals("smartisan")) { + return Settings.Global.getInt(resolver, "telephony_vibration_enabled", 0) == 1; + } else { + return Settings.System.getInt(resolver, "vibrate_when_ringing", 0) == 1; + } + } + + private void callRinging(Context context, RingingMode mode) { + initMp(); + try { + if (mode == RingingMode.Incoming) { + Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); + mMediaPlayer.setDataSource(context.getApplicationContext(), uri); + } else if (mode == RingingMode.Incoming_Custom || mode == RingingMode.Outgoing) { + int rawResId = + mode == RingingMode.Outgoing + ? R.raw.voip_outgoing_ring + : R.raw.voip_incoming_ring; + AssetFileDescriptor assetFileDescriptor = + context.getResources().openRawResourceFd(rawResId); + mMediaPlayer.setDataSource( + assetFileDescriptor.getFileDescriptor(), + assetFileDescriptor.getStartOffset(), + assetFileDescriptor.getLength()); + assetFileDescriptor.close(); + } + + // 设置 MediaPlayer 播放的声音用途 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + int usage = AudioAttributes.USAGE_VOICE_COMMUNICATION; + if (TextUtils.equals(Build.BRAND, "Xiaomi")) { + usage = AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING; + } + AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usage).build(); + mMediaPlayer.setAudioAttributes(attributes); + } else { + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL); + } + mMediaPlayer.prepareAsync(); + final AudioManager am = + (AudioManager) + context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE); + if (am != null) { + am.setSpeakerphoneOn( + mode == RingingMode.Incoming || mode == RingingMode.Incoming_Custom); + // 设置此值可在拨打时控制响铃音量 + am.setMode(AudioManager.MODE_IN_COMMUNICATION); + // 设置拨打时响铃音量默认值 + am.setStreamVolume( + AudioManager.STREAM_VOICE_CALL, 5, AudioManager.STREAM_VOICE_CALL); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e1) { + RLog.i(TAG, "---onOutgoingCallRinging Error---" + e1.getMessage()); + } + } + + private void initMp() { + if (mMediaPlayer == null) { + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnPreparedListener( + new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + try { + if (mp != null) { + mp.setLooping(true); + mp.start(); + } + } catch (IllegalStateException e) { + e.printStackTrace(); + RLog.i(TAG, "setOnPreparedListener Error!"); + } + } + }); + } + } + + public void stopRinging() { + try { + RLog.d(TAG, "stopRinging: "); + if (stopServiceAndRingingTag) { + stopServiceAndRingingTag = false; + return; + } + if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { + mMediaPlayer.stop(); + } + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + } + if (mVibrator != null) { + mVibrator.cancel(); + } + if (applicationContext != null) { + try { + applicationContext.unregisterReceiver(mRingModeReceiver); + } catch (Exception e) { + } + final AudioManager am = + (AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE); + if (am != null) { + am.setMode(AudioManager.MODE_IN_COMMUNICATION); + } + } + } catch (Exception e) { + e.printStackTrace(); + RLog.i(TAG, "mMediaPlayer stopRing error=" + e.getMessage()); + } finally { + isRinging = false; + mCurrentRingingMode = null; + } + } + + protected final BroadcastReceiver mRingModeReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + RLog.d( + TAG, + "onReceive : " + "context = " + context + "," + "intent = " + intent); + // 此类广播为 sticky 类型的,首次注册广播便会收到,因此第一次收到的广播不作处理 + if (isFirstReceivedBroadcast) { + isFirstReceivedBroadcast = false; + return; + } + if (!isRinging) { + return; + } + // 根据 isIncoming 判断只有在接听界面时做铃声和振动的切换,拨打界面不作处理 + if ((mCurrentRingingMode == RingingMode.Incoming + || mCurrentRingingMode == RingingMode.Incoming_Custom) + && AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction()) + && !CallKitUtils.callConnected) { + AudioManager am = + (AudioManager) + context.getApplicationContext() + .getSystemService(Context.AUDIO_SERVICE); + final int ringMode = am.getRingerMode(); + RLog.i(TAG, "Ring mode Receiver mode=" + ringMode); + switch (ringMode) { + case AudioManager.RINGER_MODE_NORMAL: + stopRinging(); + callRinging(context.getApplicationContext(), RingingMode.Incoming); + break; + case AudioManager.RINGER_MODE_SILENT: + stopRinging(); + break; + case AudioManager.RINGER_MODE_VIBRATE: + stopRinging(); + startVibrator(context); + break; + default: + } + } + } + }; + + /** + * 创建通知通道 + * + * @param context + */ + public void createNotificationChannel(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = + new NotificationChannel( + getNotificationChannelId(), + getNotificationChannelName(context), + NotificationManager.IMPORTANCE_HIGH); + channel.setSound(null, null); + context.getSystemService(NotificationManager.class).createNotificationChannel(channel); + } + } + + public String getNotificationChannelId() { + return DEFAULT_CHANNEL_ID; + } + + public String getNotificationChannelName(Context context) { + int id = + context.getResources() + .getIdentifier( + RongNotificationHelper.getDefaultVoipChannelName(), + "string", + context.getPackageName()); + String channelName = id == 0 ? null : context.getResources().getString(id); + return TextUtils.isEmpty(channelName) ? DEFAULT_CHANNEL_NAME : channelName; + } + + public void setNotificationHangupIcon(@DrawableRes int hangupIcon) { + this.hangupIcon = hangupIcon; + } + + @DrawableRes + public int getNotificationHangupIcon() { + return this.hangupIcon; + } + + public void setNotificationAnswerIcon(@DrawableRes int answerIcon) { + this.answerIcon = answerIcon; + } + + @DrawableRes + public int getNotificationAnswerIcon() { + return this.answerIcon; + } + + /** + * 创建通道并检查是否有悬浮通知权限 + * + * @param context + */ + public boolean createChannelAndCheckFullScreenPermission(Context context) { + createNotificationChannel(context); + return checkFullScreenPermission(context, getNotificationChannelId()); + } + + /** + * 检查是否有悬浮通知权限 + * + * @param context + * @param channelId + * @return + */ + public boolean checkFullScreenPermission(Context context, String channelId) { + NotificationManager mNotificationManager = + (NotificationManager) + context.getApplicationContext() + .getSystemService(Context.NOTIFICATION_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0及以上 + NotificationChannel channel = + mNotificationManager.getNotificationChannel(channelId); // CHANNEL_ID是自己定义的渠道ID + if (channel.getImportance() == NotificationManager.IMPORTANCE_DEFAULT) { // 未开启 + return false; + } + return true; + } + return false; + } + + /** + * 跳转到通知设置界面 + * + * @param context + */ + public void gotoChannelSettingPage(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); + intent.setPackage(context.getPackageName()); + intent.putExtra(Settings.EXTRA_CHANNEL_ID, getNotificationChannelId()); + context.startActivity(intent); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/CallSelectMemberSerializable.java b/callkit/src/main/java/io/rong/callkit/util/CallSelectMemberSerializable.java new file mode 100644 index 000000000..fd0b85df1 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/CallSelectMemberSerializable.java @@ -0,0 +1,20 @@ +package io.rong.callkit.util; + +import java.io.Serializable; +import java.util.HashMap; + +public class CallSelectMemberSerializable implements Serializable { + private HashMap hashMap = new HashMap<>(); + + public CallSelectMemberSerializable(HashMap hashMap) { + this.hashMap = hashMap; + } + + public HashMap getHashMap() { + return hashMap; + } + + public void setHashMap(HashMap hashMap) { + this.hashMap = hashMap; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/CallVerticalScrollView.java b/callkit/src/main/java/io/rong/callkit/util/CallVerticalScrollView.java new file mode 100644 index 000000000..2605e5308 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/CallVerticalScrollView.java @@ -0,0 +1,259 @@ +package io.rong.callkit.util; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CircleCrop; +import com.bumptech.glide.request.RequestOptions; +import io.rong.callkit.R; +import io.rong.callkit.RongCallKit; +import io.rong.imlib.model.UserInfo; +import java.util.ArrayList; +import java.util.List; + +/** 竖向滑动 多人语音——主叫方和通话中 */ +public class CallVerticalScrollView extends ScrollView implements ICallScrollView { + private Context context; + private boolean enableTitle; + private LinearLayout linearLayout; + private static int CHILDREN_PER_LINE = 4; + private static final int CHILDREN_SPACE = 18; + + private int portraitSize; + + public CallVerticalScrollView(Context context) { + super(context); + init(context); + } + + public CallVerticalScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + private void init(Context context) { + this.context = context; + linearLayout = new LinearLayout(context); + linearLayout.setLayoutParams( + new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + linearLayout.setOrientation(LinearLayout.VERTICAL); + addView(linearLayout); + } + + public int dip2pix(int dipValue) { + float scale = getResources().getDisplayMetrics().density; + return (int) (dipValue * scale + 0.5f); + } + + @Override + public View getChildAtIndex(int index) { + return linearLayout.getChildAt(index); + } + + @Override + public int getChildrenSpace() { + return CHILDREN_PER_LINE; + } + + public int getScreenWidth() { + return getResources().getDisplayMetrics().widthPixels; + } + + public void setChildPortraitSize(int size) { + portraitSize = size; + } + + public void enableShowState(boolean enable) { + enableTitle = enable; + } + + public void addChild(String childId, UserInfo userInfo) { + addChild(childId, userInfo, null); + } + + public void addChild(String childId, UserInfo userInfo, String state) { + int containerCount = linearLayout.getChildCount(); + LinearLayout lastContainer = null; + int i; + for (i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + if (container.getChildCount() < CHILDREN_PER_LINE) { + lastContainer = container; + break; + } + } + if (lastContainer == null) { + lastContainer = new LinearLayout(context); + lastContainer.setLayoutParams( + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + lastContainer.setGravity(Gravity.CENTER_HORIZONTAL); + lastContainer.setPadding(0, dip2pix(CHILDREN_SPACE), 0, 0); + linearLayout.addView(lastContainer); + } + + LinearLayout child = + (LinearLayout) + LayoutInflater.from(context) + .inflate(R.layout.rc_voip_user_info_mutlaudio, null); + child.setLayoutParams( + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + child.setPadding(0, 0, dip2pix(CHILDREN_SPACE), 0); + child.setTag(childId); + if (portraitSize > 0) { + child.findViewById(R.id.rc_user_portrait_layout) + .setLayoutParams(new LinearLayout.LayoutParams(portraitSize, portraitSize)); + } + ImageView imageView = (ImageView) child.findViewById(R.id.rc_user_portrait); + TextView name = (TextView) child.findViewById(R.id.rc_user_name); + name.setVisibility(enableTitle ? VISIBLE : GONE); + TextView stateV = (TextView) child.findViewById(R.id.rc_voip_member_state); + stateV.setVisibility(enableTitle ? VISIBLE : GONE); + if (state != null) { + stateV.setText(state); + } else { + stateV.setVisibility(GONE); + } + + if (userInfo != null) { + RongCallKit.getKitImageEngine() + .loadPortrait( + this.getContext(), + userInfo.getPortraitUri(), + R.drawable.rc_default_portrait, + imageView); + name.setText(userInfo.getName() == null ? userInfo.getUserId() : userInfo.getName()); + } else { + name.setText(childId); + } + lastContainer.addView(child); + } + + @Override + public void setScrollViewOverScrollMode(int mode) { + this.setOverScrollMode(mode); + } + + @Override + public void removeAllChild() { + linearLayout.removeAllViews(); + } + + public void removeChild(String childId) { + int containerCount = linearLayout.getChildCount(); + + LinearLayout lastContainer = null; + List containerList = new ArrayList<>(); + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + containerList.add(container); + } + for (LinearLayout resultContainer : containerList) { + if (lastContainer == null) { + LinearLayout child = (LinearLayout) resultContainer.findViewWithTag(childId); + if (child != null) { + resultContainer.removeView(child); + if (resultContainer.getChildCount() == 0) { + linearLayout.removeView(resultContainer); + break; + } else { + lastContainer = resultContainer; + } + } + } else { + View view = resultContainer.getChildAt(0); + resultContainer.removeView(view); + lastContainer.addView(view); + if (resultContainer.getChildCount() == 0) { + linearLayout.removeView(resultContainer); + break; + } else { + lastContainer = resultContainer; + } + } + } + } + + public View findChildById(String childId) { + int containerCount = linearLayout.getChildCount(); + + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + LinearLayout child = (LinearLayout) container.findViewWithTag(childId); + if (child != null) { + return child; + } + } + return null; + } + + public void updateChildInfo(String childId, UserInfo userInfo) { + int containerCount = linearLayout.getChildCount(); + + LinearLayout lastContainer = null; + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + LinearLayout child = (LinearLayout) container.findViewWithTag(childId); + if (child != null) { + ImageView imageView = (ImageView) child.findViewById(R.id.rc_user_portrait); + Glide.with(this) + .load(userInfo.getPortraitUri()) + .placeholder(R.drawable.rc_default_portrait) + .apply(RequestOptions.bitmapTransform(new CircleCrop())) + .into(imageView); + if (enableTitle) { + TextView textView = (TextView) child.findViewById(R.id.rc_user_name); + textView.setLines(1); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setText(userInfo.getName()); + } + } + } + } + + public void updateChildState(String childId, String state) { + int containerCount = linearLayout.getChildCount(); + + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + LinearLayout child = (LinearLayout) container.findViewWithTag(childId); + if (child != null) { + TextView textView = (TextView) child.findViewById(R.id.rc_voip_member_state); + textView.setText(state); + } + } + } + + /** + * @param childId + * @param visible + */ + public void updateChildState(String childId, boolean visible) { + int containerCount = linearLayout.getChildCount(); + + for (int i = 0; i < containerCount; i++) { + LinearLayout container = (LinearLayout) linearLayout.getChildAt(i); + LinearLayout child = (LinearLayout) container.findViewWithTag(childId); + if (child != null) { + TextView textView = (TextView) child.findViewById(R.id.rc_voip_member_state); + textView.setVisibility(visible ? VISIBLE : GONE); + // ImageView imageView = + // (ImageView) + // child.findViewById(R.id.callkit_mutilAudio_Floatinglayer); + // imageView.setVisibility(visible ? VISIBLE : GONE); + } + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/DefaultPushConfig.java b/callkit/src/main/java/io/rong/callkit/util/DefaultPushConfig.java new file mode 100644 index 000000000..3eb9947d8 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/DefaultPushConfig.java @@ -0,0 +1,157 @@ +package io.rong.callkit.util; + +import static android.content.Context.MODE_PRIVATE; + +import android.content.Context; +import android.content.SharedPreferences; +import android.text.TextUtils; +import io.rong.callkit.R; +import io.rong.imkit.RongIM; +import io.rong.imkit.userinfo.RongUserInfoManager; +import io.rong.imlib.model.AndroidConfig; +import io.rong.imlib.model.IOSConfig; +import io.rong.imlib.model.MessagePushConfig; +import io.rong.imlib.model.UserInfo; + +public class DefaultPushConfig { + + /** + * 获取邀请的 push config + * + * @param isPrivate 是否单人呼叫 + * @param groupName 群组呼叫的时候才需要填写: + */ + public static MessagePushConfig getInviteConfig( + Context context, boolean isAudio, boolean isPrivate, String groupName) { + UserInfo userInfo = + RongUserInfoManager.getInstance() + .getUserInfo(RongIM.getInstance().getCurrentUserId()); + String userName = userInfo == null ? "" : userInfo.getName(); + // 自定义音视频通话推送内容测试代码,融云SealTalk测试时配置写入SharedPreferences, + // 开发者根据实际需求定义发起通话和挂断时的push配置,在 startCall 前设置即可 + SharedPreferences sharedPreferences = + context.getSharedPreferences("push_config", MODE_PRIVATE); + String id = sharedPreferences.getString("id", ""); + String title = sharedPreferences.getString("title", ""); + String pushTile = TextUtils.isEmpty(title) ? (isPrivate ? userName : groupName) : title; + String content = sharedPreferences.getString("content", ""); + if (TextUtils.isEmpty(content)) { + content = + TextUtils.isEmpty(userName) + ? context.getResources() + .getString( + isAudio + ? R.string + .rc_voip_notificatio_audio_call_inviting_general + : R.string + .rc_voip_notificatio_video_call_inviting_general) + : userName + + " " + + context.getResources() + .getString( + isAudio + ? R.string + .rc_voip_notificatio_audio_call_inviting + : R.string + .rc_voip_notificatio_video_call_inviting); + } + String invitePushContent = content; + String data = sharedPreferences.getString("data", ""); + String hw = sharedPreferences.getString("hw", ""); + String mi = sharedPreferences.getString("mi", ""); + String oppo = sharedPreferences.getString("oppo", ""); + String threadId = sharedPreferences.getString("threadId", ""); + String apnsId = sharedPreferences.getString("apnsId", ""); + boolean vivo = sharedPreferences.getBoolean("vivo", false); + boolean forceDetail = sharedPreferences.getBoolean("forceDetail", false); + MessagePushConfig invitePushConfig = + getMessagePushConfig( + id, + pushTile, + invitePushContent, + data, + hw, + mi, + oppo, + threadId, + apnsId, + forceDetail); + + return invitePushConfig; + } + + /** + * 获取挂断的 push config + * + * @param isPrivate 是否单人呼叫 + * @param groupName 群组呼叫的时候才需要填写: + */ + public static MessagePushConfig getHangupConfig( + Context context, boolean isPrivate, String groupName) { + UserInfo userInfo = RongUserInfoManager.getInstance().getCurrentUserInfo(); + String userName = userInfo == null ? "" : userInfo.getName(); + + // 自定义音视频通话推送内容测试代码,融云SealTalk测试时配置写入SharedPreferences, + // 开发者根据实际需求定义发起通话和挂断时的push配置,在 startCall 前设置即可 + SharedPreferences sharedPreferences = + context.getSharedPreferences("push_config", MODE_PRIVATE); + String id = sharedPreferences.getString("id", ""); + String title = sharedPreferences.getString("title", ""); + String pushTile = TextUtils.isEmpty(title) ? (isPrivate ? userName : groupName) : title; + String content = sharedPreferences.getString("content", ""); + String hangupPushContent = + TextUtils.isEmpty(content) + ? context.getResources().getString(R.string.rc_voip_call_terminalted_notify) + : content; + String data = sharedPreferences.getString("data", ""); + String hw = sharedPreferences.getString("hw", ""); + String mi = sharedPreferences.getString("mi", ""); + String oppo = sharedPreferences.getString("oppo", ""); + String threadId = sharedPreferences.getString("threadId", ""); + String apnsId = sharedPreferences.getString("apnsId", ""); + boolean vivo = sharedPreferences.getBoolean("vivo", false); + boolean forceDetail = sharedPreferences.getBoolean("forceDetail", false); + MessagePushConfig hangupPushConfig = + getMessagePushConfig( + id, + pushTile, + hangupPushContent, + data, + hw, + mi, + oppo, + threadId, + apnsId, + forceDetail); + return hangupPushConfig; + } + + private static MessagePushConfig getMessagePushConfig( + String id, + String pushTile, + String invitePushContent, + String data, + String hw, + String mi, + String oppo, + String threadId, + String apnsId, + boolean forceDetail) { + return new MessagePushConfig.Builder() // + .setPushTitle(pushTile) // + .setPushContent(invitePushContent) // + .setPushData(data) // + .setForceShowDetailContent(forceDetail) // + .setAndroidConfig( + new AndroidConfig.Builder() // + .setNotificationId(id) // + .setChannelIdHW(hw) // + .setChannelIdMi(mi) // + .setChannelIdOPPO(oppo) // + .setCategoryHW("VOIP") + .setCategoryVivo("IM") + .build()) // + .setIOSConfig(new IOSConfig(threadId, apnsId)) + .build(); // + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/GlideBlurformation.java b/callkit/src/main/java/io/rong/callkit/util/GlideBlurformation.java new file mode 100644 index 000000000..6cbaae338 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/GlideBlurformation.java @@ -0,0 +1,26 @@ +package io.rong.callkit.util; + +import android.content.Context; +import android.graphics.Bitmap; +import androidx.annotation.NonNull; +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; +import java.security.MessageDigest; + +/** Created by dengxudong on 2018/5/18. */ +public class GlideBlurformation extends BitmapTransformation { + private Context context; + + public GlideBlurformation(Context context) { + this.context = context; + } + + @Override + protected Bitmap transform( + @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { + return BlurBitmapUtil.instance().blurBitmap(context, toTransform, 20, outWidth, outHeight); + } + + @Override + public void updateDiskCacheKey(MessageDigest messageDigest) {} +} diff --git a/callkit/src/main/java/io/rong/callkit/util/GlideRoundTransform.java b/callkit/src/main/java/io/rong/callkit/util/GlideRoundTransform.java new file mode 100644 index 000000000..1ae647fd4 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/GlideRoundTransform.java @@ -0,0 +1,54 @@ +package io.rong.callkit.util; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; +import java.security.MessageDigest; + +/** Created by Ethan on 2018/6/1. */ +public class GlideRoundTransform extends BitmapTransformation { + + private static float radius = 8f; + + public GlideRoundTransform() { + this(8); + } + + public GlideRoundTransform(int dp) { + super(); + this.radius = Resources.getSystem().getDisplayMetrics().density * dp; + } + + @Override + protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { + return roundCrop(pool, toTransform); + } + + private static Bitmap roundCrop(BitmapPool pool, Bitmap source) { + if (source == null) return null; + + Bitmap result = pool.get(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888); + if (result == null) { + result = + Bitmap.createBitmap( + source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(result); + Paint paint = new Paint(); + paint.setShader( + new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP)); + paint.setAntiAlias(true); + RectF rectF = new RectF(0f, 0f, source.getWidth(), source.getHeight()); + canvas.drawRoundRect(rectF, radius, radius, paint); + return result; + } + + @Override + public void updateDiskCacheKey(MessageDigest messageDigest) {} +} diff --git a/callkit/src/main/java/io/rong/callkit/util/GlideUtils.java b/callkit/src/main/java/io/rong/callkit/util/GlideUtils.java new file mode 100644 index 000000000..2d0a424bc --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/GlideUtils.java @@ -0,0 +1,54 @@ +package io.rong.callkit.util; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; +import android.widget.ImageView; +import cn.rongcloud.rtc.utils.FinLog; +import com.bumptech.glide.Glide; +import com.bumptech.glide.Priority; +import com.bumptech.glide.request.RequestOptions; +import io.rong.callkit.R; + +/** Created by dengxudong on 2018/5/18. */ +public class GlideUtils { + + private static final String TAG = GlideUtils.class.getSimpleName(); + + public static void showBlurTransformation(Context context, ImageView imageView, Uri val) { + if (val == null) { + return; + } + try { + Glide.with(context) + .load(val) + .apply(RequestOptions.bitmapTransform(new GlideBlurformation(context))) + .apply(new RequestOptions().centerCrop()) + .into(imageView); + } catch (Exception e) { + e.printStackTrace(); + FinLog.e(TAG, "Glide Utils Error=" + e.getMessage()); + } catch (NoSuchMethodError noSuchMethodError) { + noSuchMethodError.printStackTrace(); + FinLog.e(TAG, "Glide NoSuchMethodError = " + noSuchMethodError.getMessage()); + } + } + + public static void showPortrait(Context context, ImageView imageView, Uri val) { + RequestOptions requestOptions = new RequestOptions(); + requestOptions + .transform(new GlideBlurformation(context)) + .priority(Priority.HIGH) + .placeholder(R.drawable.rc_default_portrait) + .apply(new RequestOptions().centerCrop()); + if (val == null) { + Log.d(TAG, "showPortrait: val is Null"); + Glide.with(context) + .load(R.drawable.rc_default_portrait) + .apply(requestOptions) + .into(imageView); + } else { + Glide.with(context).load(val).apply(requestOptions).into(imageView); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/HeadsetInfo.java b/callkit/src/main/java/io/rong/callkit/util/HeadsetInfo.java new file mode 100644 index 000000000..506da3ce0 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/HeadsetInfo.java @@ -0,0 +1,45 @@ +package io.rong.callkit.util; + +/** Created by Dengxudong on 2018/8/23. */ +public class HeadsetInfo { + private boolean isInsert; + private HeadsetType type; + + public HeadsetInfo(boolean isInsert, HeadsetType type) { + this.isInsert = isInsert; + this.type = type; + } + + public boolean isInsert() { + return isInsert; + } + + public void setInsert(boolean insert) { + isInsert = insert; + } + + public HeadsetType getType() { + return type; + } + + public void setType(HeadsetType type) { + this.type = type; + } + + public enum HeadsetType { + /** 有线耳机 */ + WiredHeadset(0), + /** 蓝牙耳机 */ + BluetoothA2dp(1); + + int value; + + HeadsetType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/ICallScrollView.java b/callkit/src/main/java/io/rong/callkit/util/ICallScrollView.java new file mode 100644 index 000000000..1709100e2 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/ICallScrollView.java @@ -0,0 +1,35 @@ +package io.rong.callkit.util; + +import android.view.View; +import io.rong.imlib.model.UserInfo; + +/** Created by dengxudong on 2018/5/18. */ +public interface ICallScrollView { + void setScrollViewOverScrollMode(int mode); + + void removeAllChild(); + + void removeChild(String childId); + + View findChildById(String childId); + + void updateChildState(String childId, boolean visible); + + void updateChildState(String childId, String state); + + void setChildPortraitSize(int size); + + void enableShowState(boolean enable); + + void addChild(String childId, UserInfo userInfo); + + void addChild(String childId, UserInfo userInfo, String state); + + void updateChildInfo(String childId, UserInfo userInfo); + + int dip2pix(int dipValue); + + View getChildAtIndex(int index); + + int getChildrenSpace(); +} diff --git a/callkit/src/main/java/io/rong/callkit/util/IncomingCallExtraHandleUtil.java b/callkit/src/main/java/io/rong/callkit/util/IncomingCallExtraHandleUtil.java new file mode 100644 index 000000000..320703b9f --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/IncomingCallExtraHandleUtil.java @@ -0,0 +1,54 @@ +package io.rong.callkit.util; + +import android.app.NotificationManager; +import android.content.Context; +import io.rong.callkit.CallFloatBoxView; +import io.rong.callkit.VoIPBroadcastReceiver; +import io.rong.calllib.RongCallSession; +import io.rong.push.notification.RongNotificationInterface; + +/** 适配 Android 10 以上不允许后台启动 Activity 的工具类 */ +public class IncomingCallExtraHandleUtil { + public static int VOIP_NOTIFICATION_ID = 3000; // VoIP类型的通知消息。 + public static final int VOIP_REQUEST_CODE = 30001; + + private static RongCallSession cachedCallSession = null; + private static boolean checkPermissions = false; + + public static void removeNotification(Context context) { + RongNotificationInterface.removeNotification(context, VOIP_NOTIFICATION_ID); + removeAllPushServiceNotification(context); + VoIPBroadcastReceiver.clearNotificationCache(); + } + + public static void removeAllPushServiceNotification(Context context) { + NotificationManager nm = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + for (int i = VOIP_NOTIFICATION_ID; i >= 3000; i--) { + nm.cancel(i); + } + VOIP_NOTIFICATION_ID = 3000; + } + + public static RongCallSession getCallSession() { + return cachedCallSession; + } + + public static void cacheCallSession(RongCallSession callSession, boolean permissions) { + cachedCallSession = callSession; + checkPermissions = permissions; + } + + public static boolean isCheckPermissions() { + return checkPermissions; + } + + public static void clear() { + cachedCallSession = null; + checkPermissions = false; + } + + public static boolean needNotify() { + return cachedCallSession != null && !CallFloatBoxView.isCallFloatBoxShown(); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/RTCPhoneStateReceiver.java b/callkit/src/main/java/io/rong/callkit/util/RTCPhoneStateReceiver.java new file mode 100644 index 000000000..5e994a3df --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/RTCPhoneStateReceiver.java @@ -0,0 +1,76 @@ +package io.rong.callkit.util; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallSession; + +public class RTCPhoneStateReceiver extends BroadcastReceiver { + + private static final String TAG = "RTCPhoneStateReceiver"; + // 21以上会回调两次(状态值一样) + private static String twice = ""; + private TelephonyManager mTelephonyManager; + + public int getCallState(Context context) { + if (context == null) { + return -1; + } + + if (mTelephonyManager == null) { + mTelephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + } + return mTelephonyManager.getCallState(); + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (!("android.intent.action.PHONE_STATE").equals(action)) { + Log.i(TAG, "action :" + action); + return; + } + + String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); + + if (TextUtils.isEmpty(state) && TextUtils.isEmpty(twice)) { + int callState = getCallState(context); + if (TelephonyManager.CALL_STATE_OFFHOOK == callState) { + state = TelephonyManager.EXTRA_STATE_OFFHOOK; + } else if (TelephonyManager.CALL_STATE_RINGING == callState) { + state = TelephonyManager.EXTRA_STATE_RINGING; + } else if (TelephonyManager.CALL_STATE_IDLE == callState) { + state = TelephonyManager.EXTRA_STATE_IDLE; + } + } + + Log.i(TAG, "state : " + state + " , twice : " + twice); + + if (!TextUtils.isEmpty(state) && !twice.equals(state)) { + twice = state; + + if (RongCallClient.getInstance() == null) { + Log.e(TAG, "RongCallClient is empty"); + return; + } + + RongCallSession callSession = RongCallClient.getInstance().getCallSession(); + if (callSession == null) { + Log.e(TAG, "callSession is empty"); + return; + } + + if (twice.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) { + // ON_PHONE; + RongCallClient.getInstance().hangUpCall(); + } else if (twice.equals(TelephonyManager.EXTRA_STATE_IDLE)) { + // ON_PHONE_ENDED; + } + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/RingingMode.java b/callkit/src/main/java/io/rong/callkit/util/RingingMode.java new file mode 100644 index 000000000..933f92b63 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/RingingMode.java @@ -0,0 +1,13 @@ +package io.rong.callkit.util; + +public enum RingingMode { + Incoming(0), + Outgoing(1), + Incoming_Custom(2); + + private int val; + + RingingMode(int val) { + this.val = val; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/RongCallPermissionUtil.java b/callkit/src/main/java/io/rong/callkit/util/RongCallPermissionUtil.java new file mode 100644 index 000000000..52195c2a0 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/RongCallPermissionUtil.java @@ -0,0 +1,358 @@ +package io.rong.callkit.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import android.text.TextUtils; +import androidx.annotation.NonNull; +import androidx.core.app.AppOpsManagerCompat; +import io.rong.callkit.util.permission.PermissionShowDetail; +import io.rong.callkit.util.permission.PermissionType; +import io.rong.calllib.RongCallCommon; +import io.rong.common.RLog; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** @author gusd @Date 2021/09/17 */ +public class RongCallPermissionUtil { + private static final String TAG = "RongCallPermissionUtil"; + + public static void requestPermissions( + Activity activity, PermissionType[] permissionTypes, int requestCode) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return; + } + final List permissionsNotGranted = new ArrayList<>(); + for (PermissionType audioCallPermission : permissionTypes) { + if (!audioCallPermission.checkPermission(activity)) { + permissionsNotGranted.add(audioCallPermission.getPermissionName()); + } + } + if (!permissionsNotGranted.isEmpty()) { + activity.requestPermissions(permissionsNotGranted.toArray(new String[0]), requestCode); + } + } + + /////////////////////////////////////////////////////////////////////////// + // 音频通话权限相关 + /////////////////////////////////////////////////////////////////////////// + + /** + * 请求音频通话所需权限 + * + * @param activity + */ + public static void requestAudioCallNeedPermission(Activity activity, final int requestCode) { + requestPermissions(activity, getAudioCallPermissions(activity), requestCode); + } + + /** + * 获取音频通话所需权限 + * + * @return + */ + public static PermissionType[] getAudioCallPermissions(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + && context.getApplicationInfo() != null + && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { + return new PermissionType[] { + PermissionType.AudioRecord, + PermissionType.BluetoothConnect, + PermissionType.BluetoothScan, + PermissionType.BluetoothAdvertise + }; + } else { + return new PermissionType[] {PermissionType.AudioRecord}; + } + } + + public static boolean checkAudioCallNeedPermission(Context context) { + for (PermissionType audioCallPermission : getAudioCallPermissions(context)) { + if (!audioCallPermission.checkPermission(context)) { + return false; + } + } + return true; + } + + public static boolean checkAndRequestAudioCallPermission( + Activity activity, final int requestCode) { + boolean granted = checkAudioCallNeedPermission(activity); + if (!granted) { + requestAudioCallNeedPermission(activity, requestCode); + } + return granted; + } + + /////////////////////////////////////////////////////////////////////////// + // 视频通话权限相关 + /////////////////////////////////////////////////////////////////////////// + public static boolean checkVideoCallNeedPermission(Context context) { + for (PermissionType audioCallPermission : getVideoCallPermissions(context)) { + if (!audioCallPermission.checkPermission(context)) { + return false; + } + } + return true; + } + + public static void requestVideoCallNeedPermission(Activity activity, final int requestCode) { + requestPermissions(activity, getVideoCallPermissions(activity), requestCode); + } + + public static boolean checkAndRequestVideoCallPermission( + Activity activity, final int requestCode) { + boolean granted = checkVideoCallNeedPermission(activity); + if (!granted) { + requestVideoCallNeedPermission(activity, requestCode); + } + return granted; + } + + /** + * 获取视频通话所需必要权限 + * + * @return + */ + public static PermissionType[] getVideoCallPermissions(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + && context.getApplicationInfo() != null + && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { + return new PermissionType[] { + PermissionType.CameraPermission, + PermissionType.AudioRecord, + PermissionType.BluetoothConnect, + PermissionType.BluetoothScan, + PermissionType.BluetoothAdvertise, + }; + } else { + return new PermissionType[] { + PermissionType.CameraPermission, PermissionType.AudioRecord + }; + } + } + + public static boolean checkAndRequestPermissionByCallType( + Activity activity, RongCallCommon.CallMediaType type, final int requestCode) { + if (RongCallCommon.CallMediaType.VIDEO.equals(type)) { + return checkAndRequestVideoCallPermission(activity, requestCode); + } else if (RongCallCommon.CallMediaType.AUDIO.equals(type)) { + return checkAndRequestAudioCallPermission(activity, requestCode); + } + return false; + } + + public static boolean checkPermissionByType( + Context context, RongCallCommon.CallMediaType type) { + if (RongCallCommon.CallMediaType.VIDEO.equals(type)) { + return checkVideoCallNeedPermission(context); + } else if (RongCallCommon.CallMediaType.AUDIO.equals(type)) { + return checkAudioCallNeedPermission(context); + } + return false; + } + + /////////////////////////////////////////////////////////////////////////// + // 悬浮窗相关 + /////////////////////////////////////////////////////////////////////////// + + public static void requestFloatWindowNeedPermission( + final Context context, final DialogInterface.OnClickListener listener) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ArrayList permissionList = new ArrayList<>(); + permissionList.add(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + showPermissionAlert( + context, + context.getString(io.rong.imkit.R.string.rc_permission_grant_needed) + + getNotGrantedPermissionMsg(context, permissionList), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (listener != null) { + listener.onClick(dialog, which); + } + if (DialogInterface.BUTTON_POSITIVE == which) { + Intent intent = + new Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + context.getPackageName())); + if (intent.resolveActivity(context.getPackageManager()) != null) { + context.startActivity(intent); + } + } + } + }); + } + } + + private static String getNotGrantedPermissionMsg(Context context, List permissions) { + if (permissions == null || permissions.size() == 0) { + return ""; + } + Set permissionsValue = new HashSet<>(); + String permissionValue; + try { + for (String permission : permissions) { + permissionValue = + context.getString( + context.getResources() + .getIdentifier( + "rc_" + permission, + "string", + context.getPackageName()), + 0); + permissionsValue.add(permissionValue); + } + } catch (Resources.NotFoundException e) { + RLog.e( + TAG, + "one of the permissions is not recognized by SDK." + permissions.toString()); + return ""; + } + + StringBuilder result = new StringBuilder("("); + for (String value : permissionsValue) { + result.append(value).append(" "); + } + result = new StringBuilder(result.toString().trim() + ")"); + return result.toString(); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private static void showPermissionAlert( + Context context, String content, DialogInterface.OnClickListener listener) { + new AlertDialog.Builder(context) + .setMessage(content) + .setPositiveButton(io.rong.imkit.R.string.rc_confirm, listener) + .setNegativeButton(io.rong.imkit.R.string.rc_cancel, listener) + .setNeutralButton(io.rong.imkit.R.string.rc_not_prompt, listener) + .setCancelable(false) + .create() + .show(); + } + + public static boolean checkFloatWindowPermission(Context context) { + return PermissionType.FloatWindow.checkPermission(context); + } + + public static void showRequestPermissionFailedAlter( + final Context context, @NonNull String[] permissions, @NonNull int[] grantResults) { + final String content = getNotGrantedPermissionMsg(context, permissions, grantResults); + if (TextUtils.isEmpty(content)) { + return; + } + DialogInterface.OnClickListener listener = + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + PermissionShowDetail.showPermissionDetail(context); + break; + case DialogInterface.BUTTON_NEGATIVE: + default: + break; + } + } + }; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + new AlertDialog.Builder(context, android.R.style.Theme_Material_Light_Dialog_Alert) + .setMessage(content) + .setPositiveButton(io.rong.imkit.R.string.rc_confirm, listener) + .setNegativeButton(io.rong.imkit.R.string.rc_cancel, listener) + .setCancelable(false) + .create() + .show(); + } else { + new AlertDialog.Builder(context) + .setMessage(content) + .setPositiveButton(io.rong.imkit.R.string.rc_confirm, listener) + .setNegativeButton(io.rong.imkit.R.string.rc_cancel, listener) + .setCancelable(false) + .create() + .show(); + } + } + + private static String getNotGrantedPermissionMsg( + Context context, String[] permissions, int[] grantResults) { + if (permissions == null || permissions.length == 0) { + return ""; + } + try { + List permissionNameList = new ArrayList<>(permissions.length); + for (int i = 0; i < permissions.length; i++) { + if (grantResults[i] == PackageManager.PERMISSION_DENIED) { + String permissionName = + context.getString( + context.getResources() + .getIdentifier( + "rc_" + permissions[i], + "string", + context.getPackageName()), + 0); + if (!permissionNameList.contains(permissionName)) { + permissionNameList.add(permissionName); + } + } + } + + StringBuilder builder = + new StringBuilder( + context.getResources() + .getString(io.rong.imkit.R.string.rc_permission_grant_needed)); + return builder.append("(") + .append(TextUtils.join(" ", permissionNameList)) + .append(")") + .toString(); + } catch (Resources.NotFoundException e) { + RLog.e( + TAG, + "One of the permissions is not recognized by SDK." + + Arrays.toString(permissions)); + } + + return ""; + } + + public static boolean checkPermissions(Context context, @NonNull String[] permissions) { + if (permissions.length == 0) { + return true; + } + for (String permission : permissions) { + PermissionType permissionType = PermissionType.gerPermissionByName(permission); + if (permissionType != null) { + if (!permissionType.checkPermission(context)) { + return false; + } + } else { + boolean result = hasPermission(context, permission); + if (!result) { + return false; + } + } + } + return true; + } + + private static boolean hasPermission(Context context, String permission) { + String opStr = AppOpsManagerCompat.permissionToOp(permission); + if (opStr == null && Build.VERSION.SDK_INT < 23) { + return true; + } + return context != null + && context.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/SPUtils.java b/callkit/src/main/java/io/rong/callkit/util/SPUtils.java new file mode 100644 index 000000000..9b188f2f8 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/SPUtils.java @@ -0,0 +1,175 @@ +package io.rong.callkit.util; + +import android.content.Context; +import android.content.SharedPreferences; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Date; +import java.util.Map; + +/** + * @author dengxudong + * @version $Rev$ + */ +public class SPUtils { + public SPUtils() { + /* cannot be instantiated */ + throw new UnsupportedOperationException("cannot be instantiated"); + } + + /** 保存在手机里面的文件名 */ + public static final String FILE_NAME = "doudou"; + + /** 保存当前时间 */ + public static void putDataAndTime(Context context, String key) { + SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + Date nowdate = new Date(); + long timeData = nowdate.getTime(); + editor.putLong(key, timeData); + SharedPreferencesCompat.apply(editor); + } + + /** + * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法 + * + * @param context + * @param key + * @param object + */ + public static void put(Context context, String key, Object object) { + SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + + if (object instanceof String) { + editor.putString(key, (String) object); + } else if (object instanceof Integer) { + editor.putInt(key, (Integer) object); + } else if (object instanceof Boolean) { + editor.putBoolean(key, (Boolean) object); + } else if (object instanceof Float) { + editor.putFloat(key, (Float) object); + } else if (object instanceof Long) { + editor.putLong(key, (Long) object); + } else { + editor.putString(key, object.toString()); + } + SharedPreferencesCompat.apply(editor); + } + + /** + * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值 + * + * @param context + * @param key + * @param defaultObject + * @return + */ + public static Object get(Context context, String key, Object defaultObject) { + SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); + + if (defaultObject instanceof String) { + return sp.getString(key, (String) defaultObject); + } else if (defaultObject instanceof Integer) { + return sp.getInt(key, (Integer) defaultObject); + } else if (defaultObject instanceof Boolean) { + return sp.getBoolean(key, (Boolean) defaultObject); + } else if (defaultObject instanceof Float) { + return sp.getFloat(key, (Float) defaultObject); + } else if (defaultObject instanceof Long) { + return sp.getLong(key, (Long) defaultObject); + } + + return null; + } + + /** + * 移除某个key值已经对应的值 + * + * @param context + * @param key + */ + public static void remove(Context context, String key) { + SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.remove(key); + SharedPreferencesCompat.apply(editor); + } + + /** + * 清除所有数据 + * + * @param context + */ + public static void clear(Context context) { + SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.clear(); + SharedPreferencesCompat.apply(editor); + } + + /** + * 查询某个key是否已经存在 + * + * @param context + * @param key + * @return + */ + public static boolean contains(Context context, String key) { + SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); + return sp.contains(key); + } + + /** + * 返回所有的键值对 + * + * @param context + * @return + */ + public static Map getAll(Context context) { + SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); + return sp.getAll(); + } + + /** + * 创建一个解决SharedPreferencesCompat.apply方法的一个兼容类 + * + * @author zhy + */ + private static class SharedPreferencesCompat { + private static final Method sApplyMethod = findApplyMethod(); + + /** + * 反射查找apply的方法 + * + * @return + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private static Method findApplyMethod() { + try { + Class clz = SharedPreferences.Editor.class; + return clz.getMethod("apply"); + } catch (NoSuchMethodException e) { + } + return null; + } + + /** + * 如果找到则使用apply执行,否则使用commit + * + * @param editor + */ + public static void apply(SharedPreferences.Editor editor) { + try { + if (sApplyMethod != null) { + sApplyMethod.invoke(editor); + return; + } + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + editor.commit(); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/UserProfileOrderManager.java b/callkit/src/main/java/io/rong/callkit/util/UserProfileOrderManager.java new file mode 100644 index 000000000..d9eb265fe --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/UserProfileOrderManager.java @@ -0,0 +1,106 @@ +package io.rong.callkit.util; + +import android.text.TextUtils; +import android.util.Log; +import io.rong.calllib.CallUserProfile; +import io.rong.calllib.RongCallClient; +import java.util.ArrayList; +import java.util.List; + +public final class UserProfileOrderManager { + + private static final String TAG = "UserProfileOrderManager"; + private final ArrayList userIds; + + public UserProfileOrderManager() { + this.userIds = new ArrayList<>(); + } + + public UserProfileOrderManager(ArrayList value) { + this.userIds = new ArrayList<>(); + if (value != null && !value.isEmpty()) { + this.userIds.addAll(value); + } + ArrayList userIdsTmp = new ArrayList<>(); + if (RongCallClient.getInstance() != null + && RongCallClient.getInstance().getCallSession() != null) { + if (RongCallClient.getInstance().getCallSession().getParticipantProfileList() != null) { + for (CallUserProfile userProfile : + RongCallClient.getInstance().getCallSession().getParticipantProfileList()) { + if (!this.userIds.contains(userProfile.getUserId())) { + Log.e(TAG, "userIdsTmp.add : " + userProfile.getUserId()); + userIdsTmp.add(userProfile.getUserId()); + } + } + } + } + if (!userIdsTmp.isEmpty()) { + this.userIds.addAll(userIdsTmp); + } + } + + public List getSortedProfileList( + List participantProfileList) { + if (this.userIds.isEmpty()) { + // Log.e(TAG, "-------getSortedProfileList--->isEmpty"); + for (CallUserProfile userProfile : participantProfileList) { + this.userIds.add(userProfile.getUserId()); + } + return participantProfileList; + } else { + List callUserProfileList = new ArrayList<>(this.userIds.size()); + for (String userId : this.userIds) { + // Log.e(TAG, + // "-------getSortedProfileList--->userId : "+userId); + for (CallUserProfile callUserProfile : participantProfileList) { + if (TextUtils.equals(userId, callUserProfile.getUserId())) { + callUserProfileList.add(callUserProfile); + } + } + } + return callUserProfileList; + } + } + + public ArrayList getUserIds() { + // Log.d(TAG, "------getUserIds.start"); + // if (userIds != null && userIds.size() >= 0) { + // for (String userId : userIds) { + // Log.d(TAG, "getUserIds.id: " + userId); + // } + // } + return userIds; + } + + public void exchange(String fromUserId, String toUserId) { + int fromUserIdIndex = -1, toUserIdIndex = -1; + for (int i = 0; i < this.userIds.size(); i++) { + if (TextUtils.equals(userIds.get(i), fromUserId)) { + fromUserIdIndex = i; + } + + if (TextUtils.equals(userIds.get(i), toUserId)) { + toUserIdIndex = i; + } + } + + if (fromUserIdIndex != -1) { + try { + userIds.set(fromUserIdIndex, toUserId); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (toUserIdIndex != -1) { + try { + userIds.set(toUserIdIndex, fromUserId); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Log.e(TAG, "-----exchange----fromUserId : "+fromUserId + ", fromUserIdIndex : + // "+fromUserIdIndex + " , toUserId : "+toUserId +" ,toUserIdIndex : " +toUserIdIndex); + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/permission/DeviceAdapter.java b/callkit/src/main/java/io/rong/callkit/util/permission/DeviceAdapter.java new file mode 100644 index 000000000..35cf3a465 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/permission/DeviceAdapter.java @@ -0,0 +1,263 @@ +package io.rong.callkit.util.permission; + +import android.Manifest; +import android.app.AppOpsManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import androidx.core.app.AppOpsManagerCompat; +import io.rong.common.RLog; +import java.lang.reflect.Method; + +/** + * @author gusd @Date 2021/09/17 + * @escription 处理系统之间的差异化问题 + */ +public enum DeviceAdapter { + defaultAdapter(), // 默认 adapter + /////////////////////////////////////////////////////////////////////////// + // 小米手机 + /////////////////////////////////////////////////////////////////////////// + MiuiAdapter() { + @Override + public void gotoAppPermissionSettingPage(Context context) { + try { // MIUI 8 + Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR"); + localIntent.setClassName( + "com.miui.securitycenter", + "com.miui.permcenter.permissions.PermissionsEditorActivity"); + localIntent.putExtra("extra_pkgname", context.getPackageName()); + localIntent.setPackage(context.getPackageName()); + context.startActivity(localIntent); + } catch (Exception e) { + try { // MIUI 5/6/7 + Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR"); + localIntent.setClassName( + "com.miui.securitycenter", + "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); + localIntent.putExtra("extra_pkgname", context.getPackageName()); + localIntent.setPackage(context.getPackageName()); + context.startActivity(localIntent); + } catch (Exception e1) { // 否则跳转到应用详情 + super.gotoAppPermissionSettingPage(context); + } + } + } + + @Override + public boolean checkLockScreenDisplayPermission(Context context) { + AppOpsManager ops = null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + ops = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + } + try { + int op = 10020; // >= 23 + // ops.checkOpNoThrow(op, uid, packageName) + Method method = + ops.getClass() + .getMethod( + "checkOpNoThrow", + new Class[] {int.class, int.class, String.class}); + Integer result = + (Integer) + method.invoke( + ops, + op, + android.os.Process.myUid(), + context.getPackageName()); + + return result == AppOpsManager.MODE_ALLOWED; + + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + }, + /////////////////////////////////////////////////////////////////////////// + // 魅族手机 + /////////////////////////////////////////////////////////////////////////// + MeiZuAdapter() { + private static final String TAG = "MeiZuAdapter"; + + @Override + public void gotoAppPermissionSettingPage(Context context) { + try { + Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.putExtra("packageName", context.getApplicationInfo().processName); + intent.setPackage(context.getPackageName()); + context.startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + super.gotoAppPermissionSettingPage(context); + } + } + + @Override + public boolean checkRecordPermission(Context context) { + return super.checkRecordPermission(context) || hasRecordPermision(context); + } + + private boolean hasRecordPermision(Context context) { + boolean hasPermission = false; + int bufferSizeInBytes = + AudioRecord.getMinBufferSize( + 44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT); + if (bufferSizeInBytes < 0) { + RLog.e(TAG, "bufferSizeInBytes = " + bufferSizeInBytes); + return false; + } + AudioRecord audioRecord; + try { + audioRecord = + new AudioRecord( + MediaRecorder.AudioSource.MIC, + 44100, + AudioFormat.CHANNEL_IN_STEREO, + AudioFormat.ENCODING_PCM_16BIT, + bufferSizeInBytes); + audioRecord.startRecording(); + if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { + hasPermission = true; + audioRecord.stop(); + } + audioRecord.release(); + } catch (Exception e) { + RLog.e(TAG, "Audio record exception."); + } + return hasPermission; + } + }, + /////////////////////////////////////////////////////////////////////////// + // 华为手机 + /////////////////////////////////////////////////////////////////////////// + HuiWeiAdapter() { + private static final String TAG = "HuiWeiAdapter"; + + @Override + public void gotoAppPermissionSettingPage(Context context) { + try { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + ComponentName comp = + new ComponentName( + "com.huawei.systemmanager", + "com.huawei.permissionmanager.ui.MainActivity"); // 华为权限管理 + intent.setComponent(comp); + intent.setPackage(context.getPackageName()); + context.startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + super.gotoAppPermissionSettingPage(context); + } + } + }, + /////////////////////////////////////////////////////////////////////////// + // oppo 手机 + /////////////////////////////////////////////////////////////////////////// + OppoAdapter() { + @Override + public void gotoAppPermissionSettingPage(Context context) { + try { + Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("packageName", context.getApplicationInfo().processName); + ComponentName comp = + new ComponentName( + "com.coloros.securitypermission", + "com.coloros.securitypermission.permission.PermissionAppAllPermissionActivity"); // R11t 7.1.1 os-v3.2 + intent.setComponent(comp); + intent.setPackage(context.getPackageName()); + context.startActivity(intent); + } catch (Exception e) { + super.gotoAppPermissionSettingPage(context); + } + } + }, + /////////////////////////////////////////////////////////////////////////// + // vivo 手机 + /////////////////////////////////////////////////////////////////////////// + VivoAdapter() { + @Override + public boolean checkLockScreenDisplayPermission(Context context) { + String packageName = context.getPackageName(); + Uri uri2 = + Uri.parse( + "content://com.vivo.permissionmanager.provider.permission/control_locked_screen_action"); + String selection = "pkgname = ?"; + String[] selectionArgs = new String[] {packageName}; + try { + Cursor cursor = + context.getContentResolver() + .query(uri2, null, selection, selectionArgs, null); + if (cursor != null) { + if (cursor.moveToFirst()) { + int currentMode = cursor.getInt(cursor.getColumnIndex("currentstate")); + cursor.close(); + return currentMode == 0; + } else { + cursor.close(); + return false; + } + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + return false; + } + }; + + private static final String TAG = "DeviceAdapter"; + + public static DeviceAdapter getDeviceAdapter() { + DeviceAdapter adapter = null; + if (OSUtils.isEmui()) { + adapter = DeviceAdapter.HuiWeiAdapter; + } else if (OSUtils.isFlyme()) { + adapter = DeviceAdapter.MeiZuAdapter; + } else if (OSUtils.isMiui()) { + adapter = DeviceAdapter.MiuiAdapter; + } else if (OSUtils.isOppo()) { + adapter = DeviceAdapter.OppoAdapter; + } else if (OSUtils.isVivo()) { + adapter = DeviceAdapter.VivoAdapter; + } else { + adapter = DeviceAdapter.defaultAdapter; + } + RLog.d(TAG, "current device adapter = " + adapter.getClass().getName()); + return adapter; + } + + public void gotoAppPermissionSettingPage(Context context) { + Intent intent = new Intent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setPackage(context.getPackageName()); + intent.setData(Uri.fromParts("package", context.getPackageName(), null)); + context.startActivity(intent); + } + + public boolean checkRecordPermission(Context context) { + String opStr = AppOpsManagerCompat.permissionToOp(Manifest.permission.RECORD_AUDIO); + if (opStr == null && Build.VERSION.SDK_INT < 23) { + return true; + } + return context != null + && context.checkCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO) + == PackageManager.PERMISSION_GRANTED; + } + + // 只有 小米 和 vivo 设备可检测,其他设备无法检测,默认返回 true + public boolean checkLockScreenDisplayPermission(Context context) { + return true; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/permission/OSUtils.java b/callkit/src/main/java/io/rong/callkit/util/permission/OSUtils.java new file mode 100644 index 000000000..952b79a01 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/permission/OSUtils.java @@ -0,0 +1,119 @@ +package io.rong.callkit.util.permission; + +import android.os.Build; +import android.text.TextUtils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** Created by Android Studio. User: lvhongzhen Date: 2019-08-15 Time: 01:49 */ +public class OSUtils { + + public static final String ROM_MIUI = "MIUI"; + public static final String ROM_EMUI = "EMUI"; + public static final String ROM_FLYME = "FLYME"; + public static final String ROM_OPPO = "OPPO"; + public static final String ROM_SMARTISAN = "SMARTISAN"; + public static final String ROM_VIVO = "VIVO"; + public static final String ROM_QIKU = "QIKU"; + + private static final String KEY_VERSION_MIUI = "ro.miui.ui.version.name"; + private static final String KEY_VERSION_EMUI = "ro.build.version.emui"; + private static final String KEY_VERSION_OPPO = "ro.build.version.opporom"; + private static final String KEY_VERSION_SMARTISAN = "ro.smartisan.version"; + private static final String KEY_VERSION_VIVO = "ro.vivo.os.version"; + + private static String sName; + private static String sVersion; + + public static boolean isEmui() { + return check(ROM_EMUI); + } + + public static boolean isMiui() { + return check(ROM_MIUI); + } + + public static boolean isVivo() { + return check(ROM_VIVO); + } + + public static boolean isOppo() { + return check(ROM_OPPO); + } + + public static boolean isFlyme() { + return check(ROM_FLYME); + } + + public static boolean is360() { + return check(ROM_QIKU) || check("360"); + } + + public static boolean isSmartisan() { + return check(ROM_SMARTISAN); + } + + public static String getName() { + if (sName == null) { + check(""); + } + return sName; + } + + public static String getVersion() { + if (sVersion == null) { + check(""); + } + return sVersion; + } + + public static boolean check(String rom) { + if (sName != null) { + return sName.equals(rom); + } + + if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_MIUI))) { + sName = ROM_MIUI; + } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_EMUI))) { + sName = ROM_EMUI; + } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_OPPO))) { + sName = ROM_OPPO; + } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_VIVO))) { + sName = ROM_VIVO; + } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_SMARTISAN))) { + sName = ROM_SMARTISAN; + } else { + sVersion = Build.DISPLAY; + if (sVersion.toUpperCase().contains(ROM_FLYME)) { + sName = ROM_FLYME; + } else { + sVersion = Build.UNKNOWN; + sName = Build.MANUFACTURER.toUpperCase(); + } + } + return sName.equals(rom); + } + + public static String getProp(String name) { + String line = null; + BufferedReader input = null; + try { + Process p = Runtime.getRuntime().exec("getprop " + name); + input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); + line = input.readLine(); + input.close(); + } catch (IOException ex) { + return null; + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return line; + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/permission/PermissionShowDetail.java b/callkit/src/main/java/io/rong/callkit/util/permission/PermissionShowDetail.java new file mode 100644 index 000000000..e71cb041d --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/permission/PermissionShowDetail.java @@ -0,0 +1,58 @@ +package io.rong.callkit.util.permission; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; + +public class PermissionShowDetail { + private static final String VIVO = "vivo"; + + public static void showPermissionDetail(Context context) { + String manufacturer = Build.MANUFACTURER.toLowerCase(); + switch (manufacturer) { + case VIVO: + openVIVODetail(context); + break; + default: + defaultToDetail(context); + break; + } + } + + private static void defaultToDetail(Context context) { + try { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setPackage(context.getPackageName()); + Uri uri = Uri.fromParts("package", context.getPackageName(), null); + intent.setData(uri); + context.startActivity(intent); + } catch (Exception e) { + Intent intent = new Intent(Settings.ACTION_SETTINGS); + context.startActivity(intent); + } + } + + private static void openVIVODetail(Context context) { + Intent localIntent; + if (((Build.MODEL.contains("Y85")) && (!Build.MODEL.contains("Y85A"))) + || (Build.MODEL.contains("vivo Y53L"))) { + localIntent = new Intent(); + localIntent.setClassName( + "com.vivo.permissionmanager", + "com.vivo.permissionmanager.activity.PurviewTabActivity"); + localIntent.putExtra("packagename", context.getPackageName()); + localIntent.putExtra("tabId", "1"); + context.startActivity(localIntent); + } else { + localIntent = new Intent(); + localIntent.setClassName( + "com.vivo.permissionmanager", + "com.vivo.permissionmanager.activity.SoftPermissionDetailActivity"); + localIntent.setAction("secure.intent.action.softPermissionDetail"); + localIntent.putExtra("packagename", context.getPackageName()); + context.startActivity(localIntent); + } + } +} diff --git a/callkit/src/main/java/io/rong/callkit/util/permission/PermissionType.java b/callkit/src/main/java/io/rong/callkit/util/permission/PermissionType.java new file mode 100644 index 000000000..9c7b83582 --- /dev/null +++ b/callkit/src/main/java/io/rong/callkit/util/permission/PermissionType.java @@ -0,0 +1,267 @@ +package io.rong.callkit.util.permission; + +import android.Manifest; +import android.Manifest.permission; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.AppOpsManagerCompat; +import io.rong.common.RLog; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** @author gusd @Date 2021/09/17 别在多线程里面用 */ +public enum PermissionType { + + /** {@link io.rong.callkit.util.RTCPhoneStateReceiver } 类逻辑需要该权限 用于监听SIM卡来电 */ + ReadPhoneStatePermission(permission.READ_PHONE_STATE) { + @Override + public boolean checkPermission(Context context) { + return super.checkPermission(context); + } + }, + + // 摄像头权限 + CameraPermission(Manifest.permission.CAMERA) { + @Override + public boolean checkPermission(Context context) { + return super.checkPermission(context); + } + }, + // 录音权限 + AudioRecord(Manifest.permission.RECORD_AUDIO) { + public boolean checkPermission(Context context) { + return DeviceAdapter.getDeviceAdapter().checkRecordPermission(context); + } + }, + // android 12 的蓝牙权限 + BluetoothConnect("android.permission.BLUETOOTH_CONNECT") { + @Override + public int getVersion() { + return Build.VERSION_CODES.S; + } + }, + + BluetoothScan("android.permission.BLUETOOTH_SCAN") { + @Override + public int getVersion() { + return Build.VERSION_CODES.S; + } + }, + + BluetoothAdvertise("android.permission.BLUETOOTH_ADVERTISE") { + @Override + public int getVersion() { + return Build.VERSION_CODES.S; + } + }, + + // 悬浮窗 + FloatWindow("android.settings.action.MANAGE_OVERLAY_PERMISSION") { + private static final String TAG = "FloatWindow"; + + @Override + public boolean checkPermission(final Context context) { + boolean result = true; + boolean booleanValue; + if (Build.VERSION.SDK_INT >= 23) { + try { + booleanValue = + (Boolean) + Settings.class + .getDeclaredMethod("canDrawOverlays", Context.class) + .invoke(null, new Object[] {context}); + RLog.i(TAG, "isFloatWindowOpAllowed allowed: " + booleanValue); + return booleanValue; + } catch (Exception e) { + RLog.e( + TAG, + String.format( + "getDeclaredMethod:canDrawOverlays! Error:%s, etype:%s", + e.getMessage(), e.getClass().getCanonicalName())); + return true; + } + } else if (Build.VERSION.SDK_INT < 19) { + return true; + } else if (Build.BRAND.toLowerCase().contains("xiaomi")) { + Method method; + Object systemService = context.getSystemService(Context.APP_OPS_SERVICE); + try { + method = + Class.forName("android.app.AppOpsManager") + .getMethod("checkOp", Integer.TYPE, Integer.TYPE, String.class); + } catch (NoSuchMethodException e) { + RLog.e( + TAG, + String.format( + "NoSuchMethodException method:checkOp! Error:%s", + e.getMessage())); + method = null; + } catch (ClassNotFoundException e) { + RLog.e(TAG, "canDrawOverlays", e); + method = null; + } + if (method != null) { + try { + Integer tmp = + (Integer) + method.invoke( + systemService, + new Object[] { + 24, + context.getApplicationInfo().uid, + context.getPackageName() + }); + result = tmp != null && tmp == 0; + } catch (Exception e) { + RLog.e( + TAG, + String.format( + "call checkOp failed: %s etype:%s", + e.getMessage(), e.getClass().getCanonicalName())); + } + } + RLog.i(TAG, "isFloatWindowOpAllowed allowed: " + result); + return result; + } + return true; + } + + @Override + public void gotoSettingPage(Context context) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + Intent intent = + new Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + context.getPackageName())); + intent.setPackage(context.getPackageName()); + context.startActivity(intent); + } + } + }, + // 悬浮通知 + FloatNotification("FloatNotificationPermission") { + @Override + @Deprecated + public boolean checkPermission(Context context) { + return false; + } + + public boolean checkPermission(Context context, String channelId) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return false; + } + NotificationManager mNotificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationChannel channel = + mNotificationManager.getNotificationChannel(channelId); // CHANNEL_ID是自己定义的渠道ID + return channel.getImportance() == NotificationManager.IMPORTANCE_HIGH; + } + + @Deprecated + @Override + public void gotoSettingPage(Context context) { + super.gotoSettingPage(context); + } + + public void gotoSettingPage(Context context, String channelId) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return; + } + Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); + intent.setPackage(context.getPackageName()); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); + intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId); + context.startActivity(intent); + } + }, + // 通知锁屏显示 + DisplayLockScreen("DisplayLockScreen") { + @Override + public boolean checkPermission(Context context) { + return DeviceAdapter.getDeviceAdapter().checkLockScreenDisplayPermission(context); + } + + @Override + public void gotoSettingPage(Context context) { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Intent intent = new Intent(); + intent.setPackage(context.getPackageName()); + intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); + intent.putExtra("app_package", context.getPackageName()); + intent.putExtra("app_uid", context.getApplicationInfo().uid); + context.startActivity(intent); + } else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setPackage(context.getPackageName()); + intent.setData(Uri.parse("package:" + context.getPackageName())); + context.startActivity(intent); + } + } + }; + + private static final Map permissionMap = new HashMap<>(); + + static { + for (PermissionType value : PermissionType.values()) { + permissionMap.put(value.mPermissionName, value); + } + } + + @Nullable + public static PermissionType gerPermissionByName(String permissionName) { + return permissionMap.get(permissionName); + } + + private final String mPermissionName; + // 是否为必要权限 + private boolean isNecessary; + + PermissionType(@NonNull String permissionName) { + this.mPermissionName = permissionName; + } + + @NonNull + public String getPermissionName() { + return mPermissionName; + } + + /** 跳转到对应的设置页面 */ + public void gotoSettingPage(Context context) { + // 默认跳转到权限设置界面 + DeviceAdapter.getDeviceAdapter().gotoAppPermissionSettingPage(context); + } + + public boolean checkPermission(Context context) { + String opStr = AppOpsManagerCompat.permissionToOp(mPermissionName); + if (opStr == null && Build.VERSION.SDK_INT < 23) { + return true; + } + return context != null + && context.checkCallingOrSelfPermission(mPermissionName) + == PackageManager.PERMISSION_GRANTED; + } + + // 权限对应的版本 + public int getVersion() { + return 0; + } + + public boolean isNecessary() { + return isNecessary; + } + + public void setNecessary(boolean isNecessary) { + this.isNecessary = isNecessary; + } +} diff --git a/callkit/src/main/res/drawable-ldrtl-xhdpi/callkit_ic_nav_back_x.png b/callkit/src/main/res/drawable-ldrtl-xhdpi/callkit_ic_nav_back_x.png new file mode 100644 index 000000000..9a91423e9 Binary files /dev/null and b/callkit/src/main/res/drawable-ldrtl-xhdpi/callkit_ic_nav_back_x.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_ic_nav_back_x.png b/callkit/src/main/res/drawable-xhdpi/callkit_ic_nav_back_x.png new file mode 100644 index 000000000..31787cd25 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_ic_nav_back_x.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_ic_search.png b/callkit/src/main/res/drawable-xhdpi/callkit_ic_search.png new file mode 100644 index 000000000..468d555de Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_ic_search.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_delete_x.png b/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_delete_x.png new file mode 100644 index 000000000..c55cad3ee Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_delete_x.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_focused_x.png b/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_focused_x.png new file mode 100644 index 000000000..f7ab1cfb5 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_focused_x.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_x.png b/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_x.png new file mode 100644 index 000000000..468d555de Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_ic_search_x.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_clo_camera.png b/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_clo_camera.png new file mode 100644 index 000000000..accf127c3 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_clo_camera.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_mute.png b/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_mute.png new file mode 100644 index 000000000..87059956e Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_mute.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_status.png b/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_status.png new file mode 100644 index 000000000..a8f1b5b6a Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_mult_video_user_status.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_mute_unavailable.png b/callkit/src/main/res/drawable-xhdpi/callkit_mute_unavailable.png new file mode 100644 index 000000000..d63043d39 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_mute_unavailable.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/callkit_select_ic_nav_back_x.png b/callkit/src/main/res/drawable-xhdpi/callkit_select_ic_nav_back_x.png new file mode 100644 index 000000000..31787cd25 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/callkit_select_ic_nav_back_x.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_ic_phone_normal.png b/callkit/src/main/res/drawable-xhdpi/rc_ic_phone_normal.png new file mode 100644 index 000000000..305f9d267 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_ic_phone_normal.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_ic_phone_pressed.png b/callkit/src/main/res/drawable-xhdpi/rc_ic_phone_pressed.png new file mode 100644 index 000000000..bb4bc26f0 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_ic_phone_pressed.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_add.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_add.png new file mode 100644 index 000000000..df273ce7b Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_add.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_answer.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_answer.png new file mode 100644 index 000000000..e1e087f0c Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_answer.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_answer_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_answer_hover.png new file mode 100644 index 000000000..a26c91e91 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_answer_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_left_cancel.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_left_cancel.png new file mode 100644 index 000000000..2fdbe7a62 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_left_cancel.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_left_connected.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_left_connected.png new file mode 100644 index 000000000..63444fc13 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_left_connected.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_right_cancel.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_right_cancel.png new file mode 100644 index 000000000..fdbef3268 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_right_cancel.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_right_connected.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_right_connected.png new file mode 100644 index 000000000..d17eb411a Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_audio_right_connected.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_camera.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_camera.png new file mode 100644 index 000000000..a210f42c4 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_camera.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_camera_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_camera_hover.png new file mode 100644 index 000000000..95dfe5e97 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_camera_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_disable_camera.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_disable_camera.png new file mode 100644 index 000000000..7a0e42afc Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_disable_camera.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_disable_camera_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_disable_camera_hover.png new file mode 100644 index 000000000..c7beeb6a8 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_disable_camera_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_float_audio.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_float_audio.png new file mode 100644 index 000000000..0f3bd3af5 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_float_audio.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_float_video.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_float_video.png new file mode 100644 index 000000000..775af3347 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_float_video.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_handfree.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_handfree.png new file mode 100644 index 000000000..a5ad6aba9 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_handfree.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_handfree_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_handfree_hover.png new file mode 100644 index 000000000..ee2c9dab6 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_handfree_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_handup.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_handup.png new file mode 100644 index 000000000..1d9523abf Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_handup.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_hang_up.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_hang_up.png new file mode 100644 index 000000000..5edc980da Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_hang_up.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_hang_up_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_hang_up_hover.png new file mode 100644 index 000000000..be532ef45 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_hang_up_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_add.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_add.png new file mode 100644 index 000000000..448146ef1 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_add.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_camera.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_camera.png new file mode 100644 index 000000000..1b8fad968 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_camera.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_checked.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_checked.png new file mode 100644 index 000000000..07d246cbc Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_checked.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_hover.png new file mode 100644 index 000000000..223f03cbc Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_normal.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_normal.png new file mode 100644 index 000000000..cd297ff87 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_normal.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video.png new file mode 100644 index 000000000..3ebd5ab09 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video_pressed.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video_pressed.png new file mode 100644 index 000000000..a844f1ad4 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video_pressed.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_iphone.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_iphone.png new file mode 100644 index 000000000..787d44a72 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_iphone.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_iphone_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_iphone_hover.png new file mode 100644 index 000000000..aff88241d Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_iphone_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_menu_bg.9.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_menu_bg.9.png new file mode 100644 index 000000000..9e9a63b98 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_menu_bg.9.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_minimize.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_minimize.png new file mode 100644 index 000000000..477bda139 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_minimize.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_more.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_more.png new file mode 100644 index 000000000..c97c1c8da Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_more.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_mute.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_mute.png new file mode 100644 index 000000000..668211211 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_mute.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_mute_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_mute_hover.png new file mode 100644 index 000000000..d76ca4bb2 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_mute_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_notification_answer.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_notification_answer.png new file mode 100644 index 000000000..fa473189b Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_notification_answer.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_notification_hangup.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_notification_hangup.png new file mode 100644 index 000000000..05fede5c0 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_notification_hangup.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_phone.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_phone.png new file mode 100644 index 000000000..73c14be78 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_phone.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_1.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_1.png new file mode 100644 index 000000000..f63b5a418 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_1.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_2.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_2.png new file mode 100644 index 000000000..eb3950105 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_2.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_3.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_3.png new file mode 100644 index 000000000..548664e16 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_3.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_4.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_4.png new file mode 100644 index 000000000..67bc71cb7 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_4.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_5.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_5.png new file mode 100644 index 000000000..6353b8a7e Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_5.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_6.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_6.png new file mode 100644 index 000000000..4a6cafca5 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_signal_6.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer.png new file mode 100644 index 000000000..963939eb0 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer_hover.png new file mode 100644 index 000000000..b062c3bc2 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_switch_camera.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_switch_camera.png new file mode 100644 index 000000000..264e5169d Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_switch_camera.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer.png new file mode 100644 index 000000000..7bf266460 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover.png new file mode 100644 index 000000000..bdd388f48 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover_new.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover_new.png new file mode 100644 index 000000000..3c1a74c4f Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover_new.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_new.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_new.png new file mode 100644 index 000000000..4fdf757d6 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_answer_new.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_video_left.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_left.png new file mode 100644 index 000000000..f2fc8ad06 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_left.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_video_right.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_right.png new file mode 100644 index 000000000..17ab18148 Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_video_right.png differ diff --git a/callkit/src/main/res/drawable-xhdpi/rc_voip_whiteboard.png b/callkit/src/main/res/drawable-xhdpi/rc_voip_whiteboard.png new file mode 100644 index 000000000..1a1a9215f Binary files /dev/null and b/callkit/src/main/res/drawable-xhdpi/rc_voip_whiteboard.png differ diff --git a/callkit/src/main/res/drawable/bg_search.xml b/callkit/src/main/res/drawable/bg_search.xml new file mode 100644 index 000000000..c92572da8 --- /dev/null +++ b/callkit/src/main/res/drawable/bg_search.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/callkit_multiaudiouesrinfocontners.xml b/callkit/src/main/res/drawable/callkit_multiaudiouesrinfocontners.xml new file mode 100644 index 000000000..be7350762 --- /dev/null +++ b/callkit/src/main/res/drawable/callkit_multiaudiouesrinfocontners.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/callkit/src/main/res/drawable/callkit_selector_icon_search.xml b/callkit/src/main/res/drawable/callkit_selector_icon_search.xml new file mode 100644 index 000000000..ae75fa932 --- /dev/null +++ b/callkit/src/main/res/drawable/callkit_selector_icon_search.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/callkit_voip_iphone.xml b/callkit/src/main/res/drawable/callkit_voip_iphone.xml new file mode 100644 index 000000000..fc4874007 --- /dev/null +++ b/callkit/src/main/res/drawable/callkit_voip_iphone.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_ic_phone_selector.xml b/callkit/src/main/res/drawable/rc_ic_phone_selector.xml new file mode 100644 index 000000000..d2965bff9 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_ic_phone_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_ic_video_selector.xml b/callkit/src/main/res/drawable/rc_ic_video_selector.xml new file mode 100644 index 000000000..e9c324e42 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_ic_video_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_audio_answer_selector.xml b/callkit/src/main/res/drawable/rc_voip_audio_answer_selector.xml new file mode 100644 index 000000000..f0b5e72fb --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_audio_answer_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_audio_answer_selector_new.xml b/callkit/src/main/res/drawable/rc_voip_audio_answer_selector_new.xml new file mode 100644 index 000000000..26b4245bd --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_audio_answer_selector_new.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_camera_selector.xml b/callkit/src/main/res/drawable/rc_voip_camera_selector.xml new file mode 100644 index 000000000..c86a84de9 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_camera_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_checkbox.xml b/callkit/src/main/res/drawable/rc_voip_checkbox.xml new file mode 100644 index 000000000..e907894d8 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_checkbox.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_dialog_bg.xml b/callkit/src/main/res/drawable/rc_voip_dialog_bg.xml new file mode 100644 index 000000000..d8d2088a6 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_dialog_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_disable_camera_selector.xml b/callkit/src/main/res/drawable/rc_voip_disable_camera_selector.xml new file mode 100644 index 000000000..c86a84de9 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_disable_camera_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_float_bg.xml b/callkit/src/main/res/drawable/rc_voip_float_bg.xml new file mode 100644 index 000000000..35f8824a2 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_float_bg.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_hangup_selector.xml b/callkit/src/main/res/drawable/rc_voip_hangup_selector.xml new file mode 100644 index 000000000..4a258ec0e --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_hangup_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_item_selector.xml b/callkit/src/main/res/drawable/rc_voip_item_selector.xml new file mode 100644 index 000000000..1cfefb642 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_item_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_mute_selector.xml b/callkit/src/main/res/drawable/rc_voip_mute_selector.xml new file mode 100644 index 000000000..57d77398c --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_mute_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_speaker_selector.xml b/callkit/src/main/res/drawable/rc_voip_speaker_selector.xml new file mode 100644 index 000000000..677e66734 --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_speaker_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_vedio_answer_selector.xml b/callkit/src/main/res/drawable/rc_voip_vedio_answer_selector.xml new file mode 100644 index 000000000..3c43d864b --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_vedio_answer_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/drawable/rc_voip_vedio_answer_selector_new.xml b/callkit/src/main/res/drawable/rc_voip_vedio_answer_selector_new.xml new file mode 100644 index 000000000..3c43d864b --- /dev/null +++ b/callkit/src/main/res/drawable/rc_voip_vedio_answer_selector_new.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/activity_call_select_member2.xml b/callkit/src/main/res/layout/activity_call_select_member2.xml new file mode 100644 index 000000000..0a4299426 --- /dev/null +++ b/callkit/src/main/res/layout/activity_call_select_member2.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/callkit_actionbar_option_text_layout.xml b/callkit/src/main/res/layout/callkit_actionbar_option_text_layout.xml new file mode 100644 index 000000000..664778b81 --- /dev/null +++ b/callkit/src/main/res/layout/callkit_actionbar_option_text_layout.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/callkit_conference_search_top_layout.xml b/callkit/src/main/res/layout/callkit_conference_search_top_layout.xml new file mode 100644 index 000000000..003f444c5 --- /dev/null +++ b/callkit/src/main/res/layout/callkit_conference_search_top_layout.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/callkit_multivideo_gaussianblur.xml b/callkit/src/main/res/layout/callkit_multivideo_gaussianblur.xml new file mode 100644 index 000000000..b4d2e0883 --- /dev/null +++ b/callkit/src/main/res/layout/callkit_multivideo_gaussianblur.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/callkit/src/main/res/layout/callkit_rc_voip_activity_select_member.xml b/callkit/src/main/res/layout/callkit_rc_voip_activity_select_member.xml new file mode 100644 index 000000000..768ee55bd --- /dev/null +++ b/callkit/src/main/res/layout/callkit_rc_voip_activity_select_member.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/callkit_rc_voip_activity_select_member_layout.xml b/callkit/src/main/res/layout/callkit_rc_voip_activity_select_member_layout.xml new file mode 100644 index 000000000..99e304d62 --- /dev/null +++ b/callkit/src/main/res/layout/callkit_rc_voip_activity_select_member_layout.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/callkit_view_search_bar_layout.xml b/callkit/src/main/res/layout/callkit_view_search_bar_layout.xml new file mode 100644 index 000000000..07aecf8d7 --- /dev/null +++ b/callkit/src/main/res/layout/callkit_view_search_bar_layout.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_ac_muti_audio.xml b/callkit/src/main/res/layout/rc_voip_ac_muti_audio.xml new file mode 100644 index 000000000..d1e2924e6 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_ac_muti_audio.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_activity_select_member.xml b/callkit/src/main/res/layout/rc_voip_activity_select_member.xml new file mode 100644 index 000000000..f8ffaad4a --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_activity_select_member.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_activity_single_call.xml b/callkit/src/main/res/layout/rc_voip_activity_single_call.xml new file mode 100644 index 000000000..6fca718a9 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_activity_single_call.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_audio_call_user_info.xml b/callkit/src/main/res/layout/rc_voip_audio_call_user_info.xml new file mode 100644 index 000000000..175f75e26 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_audio_call_user_info.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/rc_voip_audio_call_user_info_incoming.xml b/callkit/src/main/res/layout/rc_voip_audio_call_user_info_incoming.xml new file mode 100644 index 000000000..cc5471472 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_audio_call_user_info_incoming.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/rc_voip_call_bottom_connected_button_layout.xml b/callkit/src/main/res/layout/rc_voip_call_bottom_connected_button_layout.xml new file mode 100644 index 000000000..728db6947 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_call_bottom_connected_button_layout.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/rc_voip_call_bottom_incoming_button_layout.xml b/callkit/src/main/res/layout/rc_voip_call_bottom_incoming_button_layout.xml new file mode 100644 index 000000000..d06097e3d --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_call_bottom_incoming_button_layout.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/rc_voip_call_user_info_incoming.xml b/callkit/src/main/res/layout/rc_voip_call_user_info_incoming.xml new file mode 100644 index 000000000..61d49b348 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_call_user_info_incoming.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/rc_voip_contact_container.xml b/callkit/src/main/res/layout/rc_voip_contact_container.xml new file mode 100644 index 000000000..79b523f33 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_contact_container.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_dialog_popup_prompt.xml b/callkit/src/main/res/layout/rc_voip_dialog_popup_prompt.xml new file mode 100644 index 000000000..b4b188690 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_dialog_popup_prompt.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_float_box.xml b/callkit/src/main/res/layout/rc_voip_float_box.xml new file mode 100644 index 000000000..83c510b3a --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_float_box.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_item_incoming_maudio.xml b/callkit/src/main/res/layout/rc_voip_item_incoming_maudio.xml new file mode 100644 index 000000000..6465cb9a8 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_item_incoming_maudio.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_item_outgoing_maudio.xml b/callkit/src/main/res/layout/rc_voip_item_outgoing_maudio.xml new file mode 100644 index 000000000..448305eac --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_item_outgoing_maudio.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_listitem_select_member.xml b/callkit/src/main/res/layout/rc_voip_listitem_select_member.xml new file mode 100644 index 000000000..f7ed8879e --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_listitem_select_member.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_msg_multi_call_end.xml b/callkit/src/main/res/layout/rc_voip_msg_multi_call_end.xml new file mode 100644 index 000000000..45183c1e3 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_msg_multi_call_end.xml @@ -0,0 +1,10 @@ + + + + diff --git a/callkit/src/main/res/layout/rc_voip_multi_video_call.xml b/callkit/src/main/res/layout/rc_voip_multi_video_call.xml new file mode 100644 index 000000000..9a07db6bd --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_multi_video_call.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_multi_video_calling_bottom_view.xml b/callkit/src/main/res/layout/rc_voip_multi_video_calling_bottom_view.xml new file mode 100644 index 000000000..f10dce656 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_multi_video_calling_bottom_view.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/rc_voip_multi_video_calling_bottom_view_rtl.xml b/callkit/src/main/res/layout/rc_voip_multi_video_calling_bottom_view_rtl.xml new file mode 100644 index 000000000..18939b8ec --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_multi_video_calling_bottom_view_rtl.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/rc_voip_multi_video_top_view.xml b/callkit/src/main/res/layout/rc_voip_multi_video_top_view.xml new file mode 100644 index 000000000..f24edb7ad --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_multi_video_top_view.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_observer_hint.xml b/callkit/src/main/res/layout/rc_voip_observer_hint.xml new file mode 100644 index 000000000..f218fa527 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_observer_hint.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_pop_menu.xml b/callkit/src/main/res/layout/rc_voip_pop_menu.xml new file mode 100644 index 000000000..361db191c --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_pop_menu.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/callkit/src/main/res/layout/rc_voip_user_info.xml b/callkit/src/main/res/layout/rc_voip_user_info.xml new file mode 100644 index 000000000..2675936b2 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_user_info.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_user_info_mutlaudio.xml b/callkit/src/main/res/layout/rc_voip_user_info_mutlaudio.xml new file mode 100644 index 000000000..100c71d35 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_user_info_mutlaudio.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_user_portrait.xml b/callkit/src/main/res/layout/rc_voip_user_portrait.xml new file mode 100644 index 000000000..8996b1506 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_user_portrait.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_video_call_user_info.xml b/callkit/src/main/res/layout/rc_voip_video_call_user_info.xml new file mode 100644 index 000000000..0cde00db2 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_video_call_user_info.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/layout/rc_voip_viewlet_remote_user.xml b/callkit/src/main/res/layout/rc_voip_viewlet_remote_user.xml new file mode 100644 index 000000000..dda915d21 --- /dev/null +++ b/callkit/src/main/res/layout/rc_voip_viewlet_remote_user.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/raw/voip_incoming_ring.mp3 b/callkit/src/main/res/raw/voip_incoming_ring.mp3 new file mode 100644 index 000000000..21472a705 Binary files /dev/null and b/callkit/src/main/res/raw/voip_incoming_ring.mp3 differ diff --git a/callkit/src/main/res/raw/voip_network_error_sound.wav b/callkit/src/main/res/raw/voip_network_error_sound.wav new file mode 100644 index 000000000..5cb8fdfa6 Binary files /dev/null and b/callkit/src/main/res/raw/voip_network_error_sound.wav differ diff --git a/callkit/src/main/res/raw/voip_outgoing_ring.mp3 b/callkit/src/main/res/raw/voip_outgoing_ring.mp3 new file mode 100644 index 000000000..cfd8cfe71 Binary files /dev/null and b/callkit/src/main/res/raw/voip_outgoing_ring.mp3 differ diff --git a/callkit/src/main/res/values-ar-rIL/strings.xml b/callkit/src/main/res/values-ar-rIL/strings.xml new file mode 100644 index 000000000..2450248dc --- /dev/null +++ b/callkit/src/main/res/values-ar-rIL/strings.xml @@ -0,0 +1,96 @@ + + + تم اختيار %d أشخاص + تأكيد + مكالمة فيديو + مكالمة صوتية + جاري الاتصال + جاري الاتصال... + الخط مشغول + مكالمة ملغية + تم الإلغاء + الشبكة غير متوفرة، يرجى التحقق من شبكتك + تم الإلغاء + " انتهت المكالمة" + بدء الاتصال + اختر الأعضاء + مكالمة صوتية جارية , حاول مرة اخرى + مكالمة فيديو جارية , حاول مرة اخرى + مكالمة صوتية في التقدم ، دعوة جديدة غير مسموح + " مكالمات الفيديو في التقدم ، مكالمات جديدة لا يسمح" + تم الرد على الأجهزة الأخرى . + + يدعوك إلى مكالمة صوتية + يدعوك إلى مكالمة فيديو + + يدعوك الى مكالمة فيديو + يدعوك إلى مكالمة صوتية + لديك دعوة مكالمة صوتية + لديك دعوة مكالمة فيديو + + + " انتهت المكالمة" + مكالمة ملغية + تم إلغاء المكالمة + " انتهت المكالمة" + مكالمة ملغية + تم إلغاء المكالمة + صامت + إنهاء المحادثة + قبول + كاميرا + مكبر صوت + اغلق الكاميرا + تشغيل الكاميرا + اعضاء + تم الإلغاء + رفض + لا يوجد إجابة + تم الإلغاء + رفض + لايوجد اجابة + المدة  + الخط مشغول + قد يكون المستخدم الآخر مشغول الآن , حاول مرة اخرى . + [مكالمة صوتية] + [مكالمة فيديو] + حول إلى مكالمة صوتية + المستخدم الآخر حول المكالمة الى مكالمة صوتية . + حول إلى مكالمة صوتية . + يرجى تفعيل خاصية النوافذ العائمة . + جاري تنفيذ المكالمة + مكالمة صوتية جارية , اضغط للاستمرار + مكالمة فيديو جارية , اضغط للاستمرار + Up to %d اعضاء + جاري الاتصال... + الشبكة غير مستقرة + اختر الأعضاء + خدمات الصوت والفيديو غير مفعله + تم إضافتها إلى القائمة السوداء + + اختر الأعضاء + بحث + تم إغلاق التطبيق، يرجى تفعيل تصريح عرض نوافذ منبثقة في الخلفية من صفحة الإعدادات" + + المكالمة الصوتية مرفوضة + مكالمة الفيديو مرفوضة + ارفع يدك للتحدث + حسناً + حسناً + الإلغاء + ميكروفون + كاميرا وميكروفون + مفتوح + إغلاق + يجب أن تنضم إلى الإجتماع كجمهور، استمر أو لا + يرجى رفع يدك للتحدث + Up to %d اعضاء + Up to %d اعضاء + الرجاء تعيين الأذونات ذات الصلة + تحميل اللوحة البيضاء … + شهادة صفحة الويب التي قمت بزيارتها غير صالحة, هل ترغب في متابعة زيارتك؟ + " الإتصال بالرسائل الفورية غير طبيعي الرجاء التحقق من الشبكة والمحاولة مرة أخرى" + لا توجد نتائج بحث + [تم إنهاء المكالمة] + الإلغاء + \ No newline at end of file diff --git a/callkit/src/main/res/values-en/colors.xml b/callkit/src/main/res/values-en/colors.xml new file mode 100644 index 000000000..298004b58 --- /dev/null +++ b/callkit/src/main/res/values-en/colors.xml @@ -0,0 +1,4 @@ + + + #72767B + \ No newline at end of file diff --git a/callkit/src/main/res/values-en/dimens.xml b/callkit/src/main/res/values-en/dimens.xml new file mode 100644 index 000000000..e6926c283 --- /dev/null +++ b/callkit/src/main/res/values-en/dimens.xml @@ -0,0 +1,8 @@ + + + 18sp + 90dp + 17dp + 17dp + 8dp + \ No newline at end of file diff --git a/callkit/src/main/res/values-en/rc_voipkit_string.xml b/callkit/src/main/res/values-en/rc_voipkit_string.xml new file mode 100644 index 000000000..f97a4e3b2 --- /dev/null +++ b/callkit/src/main/res/values-en/rc_voipkit_string.xml @@ -0,0 +1,97 @@ + + + %d members selected + Confirm + Video Call + Voice Call + Awaiting response... + Connecting + Busy line + No answer + Cancelled + Network is unavailable, please check your network + Interrupt + Call ended + Call started + Select Members + Voice call is on. Try again later. + Video call is on. Try again later. + A voice call is in progress. New calls are not allowed + A video call is in progress. New calls are not allowed + Other devices have been answered + + Invites you to voice call + Invites you to video call + + Invites you to video call + Invites you to voice call + You\'ve got a voice chat invitation + You\'ve got a video chat invitation + + + Call ended + Call No answer + Call Cancelled + Call ended + Call No answer + Call Cancelled + Mute + Hang up + Accept + Camera + Speaker + Turn off camera + Turn on camera + Members + Cancelled + Declined + No answer + Remote Cancelled + Remote Declined + Remote No answer + Duration  + Busy line + The other user may be busy now, Try again later. + [Voice Call] + [Video Call] + Switch to voice call + Other user switched to voice call. + Switched to voice call + Please enable floating windows + Call in progress + Tap to continue as voice call is on + Tap to continue as video call is on + Up to %d members + Connecting... + Network is unstable + Select Members + Audio and video services are turned off + Has been blacklisted by receiver + + Select Members + Search + Your system turned off \""Display pop-up windows while running in the background\"" permission, +Please turn it on in the application permission management page of \""Settings\"" + + Voice call rejected + Video call rejected + Please set the relevant permissions + Whiteboard loading... + The web page certificate you visited is invalid, Do you want to continue your visit? + IM connection is abnormal. Please check the network and try again + no search results + + [Call ended] + Raise hand to speak + whiteboard + ok + cancel + microphone + camera and mic + open + close + Need to join the meeting as an audience, continue or not + You are an observer, please raise your hand if you wish to speak + Up to %d members + Up to %d members + diff --git a/callkit/src/main/res/values/callkit_callUserGridViewStyle.xml b/callkit/src/main/res/values/callkit_callUserGridViewStyle.xml new file mode 100644 index 000000000..7d650a398 --- /dev/null +++ b/callkit/src/main/res/values/callkit_callUserGridViewStyle.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/values/colors.xml b/callkit/src/main/res/values/colors.xml new file mode 100644 index 000000000..7930705ad --- /dev/null +++ b/callkit/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #72767B + #999999 + + \ No newline at end of file diff --git a/callkit/src/main/res/values/dimens.xml b/callkit/src/main/res/values/dimens.xml new file mode 100644 index 000000000..00d7fa941 --- /dev/null +++ b/callkit/src/main/res/values/dimens.xml @@ -0,0 +1,16 @@ + + + 18sp + 90dp + 17dp + + 12dp + 13dp + 14dp + 40dp + 48dp + 60dp + 80dp + 17dp + 8dp + \ No newline at end of file diff --git a/callkit/src/main/res/values/rc_voipkit_color.xml b/callkit/src/main/res/values/rc_voipkit_color.xml new file mode 100644 index 000000000..0ba33baa4 --- /dev/null +++ b/callkit/src/main/res/values/rc_voipkit_color.xml @@ -0,0 +1,38 @@ + + + #000000 + + #141C24 + #1f4a76 + #FFFFFF + #0099ff + #33000000 + #00000000 + #262626 + #B3000000 + #E5E5E5 + #3A91F3 + #E6141C24 + + #FAFAFA + #e5e5e5 + #DFDFDF + #B2B2B2 + #FAFAFA + #262626 + #3A91F3 + + #FAFAFA + #262626 + + #3A91F3 + #7CA1C9 + #939393 + #262626 + #939393 + #3a91f3 + #000000 + #803A91F3 + #939393 + #3F81BC + \ No newline at end of file diff --git a/callkit/src/main/res/values/rc_voipkit_ids.xml b/callkit/src/main/res/values/rc_voipkit_ids.xml new file mode 100644 index 000000000..41bbeb495 --- /dev/null +++ b/callkit/src/main/res/values/rc_voipkit_ids.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/values/rc_voipkit_string.xml b/callkit/src/main/res/values/rc_voipkit_string.xml new file mode 100644 index 000000000..74d9a5aca --- /dev/null +++ b/callkit/src/main/res/values/rc_voipkit_string.xml @@ -0,0 +1,97 @@ + + + 已选择 %d 人 + 确定 + 视频通话 + 语音通话 + 等待对方接受邀请... + 连接中 + 对方忙 + 通话未接听 + 挂断 + 当前网络不可用,请检查你的网络设置 + 异常挂断 + 通话已结束 + 开始通话 + 添加成员 + 正在进行语音通话,请稍后再试 + 正在进行视频通话,请稍后再试 + 正在进行语音通话,不允许发起新的通话 + 正在进行视频通话,不允许发起新的通话 + 其他设备已处理 + 用户被开发者后台封禁 + + 邀请您进行语音通话... + 邀请您进行视频通话... + 邀请您进行视频通话 + 邀请您进行语音通话 + 您有一个语音通话邀请 + 您有一个视频通话邀请 + 通话已结束 + 通话未接听 + 已取消通话 + 通话已结束 + 通话未接听 + 已取消通话 + 静音 + 挂断 + 接听 + 摄像头 + 免提 + 关闭摄像头 + 开启摄像头 + 群聊成员 + 已取消 + 已拒绝 + 未接听 + 对方已取消 + 对方已拒绝 + 对方未接听 + 通话时长  + 对方忙 + 对方忙,请稍后再试 + [语音通话] + [视频通话] + 语音通话 + 对方已切换到语音通话 + 已切换到语音通话 + 请打开悬浮窗权限 + 通话中... + 语音通话中,轻击以继续 + 视频通话中,轻击以继续 + 您最多只能选择%d人 + 举手发言 + 添加成员 + 白板 + 确认 + 取消 + 主持人邀请您参与讨论 是否接受? + 主持人请求您%1$s%2$s 是否接受? + 麦克风 + 摄像头和麦克风 + 打开 + 关闭 + 为了保障会议流畅进行,需要您以观众模式加入,是否继续? + 您的身份为观察者,如需发言请举手 + 您最多只能选择%d人 + 您最多只能选择%d人 + 网络质量不佳 + 连接中... + 音视频服务已关闭 + 已被对方加入黑名单 + + 添加成员 + 搜索 + 您的系统当前关闭了【后台启动界面】权限,请到系统设置的应用权限管理页面开启。 + + 语音通话已拒绝 + 视频通话已拒绝 + + 请设置相关权限 + 白板加载中... + 您访问的网页证书无效,是否继续访问? + IM连接异常,请检查网络后重试 + 无搜索结果 + + [结束通话] + \ No newline at end of file diff --git a/callkit/src/main/res/values/rc_voipkit_style.xml b/callkit/src/main/res/values/rc_voipkit_style.xml new file mode 100644 index 000000000..34e966e0d --- /dev/null +++ b/callkit/src/main/res/values/rc_voipkit_style.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/callkit/src/main/res/values/strings.xml b/callkit/src/main/res/values/strings.xml new file mode 100644 index 000000000..f33ea0a51 --- /dev/null +++ b/callkit/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Cancel + diff --git a/common/src/main/java/com/yunbao/common/activity/MyWalletActivity.java b/common/src/main/java/com/yunbao/common/activity/MyWalletActivity.java index 522d04cc6..a58c5bd2b 100644 --- a/common/src/main/java/com/yunbao/common/activity/MyWalletActivity.java +++ b/common/src/main/java/com/yunbao/common/activity/MyWalletActivity.java @@ -66,7 +66,11 @@ public class MyWalletActivity extends AbsActivity { GoogleBillingUtilNew.getInstance().initGooglePay(mContext); vp_content = (ViewPager) findViewById(R.id.vp_content); TextView rView = (TextView) findViewById(R.id.rView); - findViewById(R.id.redPacketMain).setVisibility(View.VISIBLE); + if(getPackageName().equals("com.pdlive.shayu")) { + findViewById(R.id.redPacketMain).setVisibility(View.VISIBLE); + }else{ + findViewById(R.id.redPacketMain).setVisibility(View.GONE); + } findViewById(R.id.redPacketMain).setOnClickListener(v -> { RouteUtil.forwardRedPacketList(); }); diff --git a/settings.gradle b/settings.gradle index 64dd6d751..bbd306ed2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,3 +7,4 @@ include ':pluginsForAnchor' include ':OneToOne' include ':ViewPager2Delegate' include ':TabLayout' +include ':callkit'