liaocheng %!s(int64=4) %!d(string=hai) anos
pai
achega
639e610099
Modificáronse 100 ficheiros con 8422 adicións e 0 borrados
  1. 1 0
      im/CallKit/.gitignore
  2. 15 0
      im/CallKit/build.gradle
  3. 17 0
      im/CallKit/proguard-rules.pro
  4. 67 0
      im/CallKit/src/main/AndroidManifest.xml
  5. 160 0
      im/CallKit/src/main/java/io/rong/callkit/AudioPlugin.java
  6. 745 0
      im/CallKit/src/main/java/io/rong/callkit/BaseCallActivity.java
  7. 14 0
      im/CallKit/src/main/java/io/rong/callkit/BaseNoActionBarActivity.java
  8. 201 0
      im/CallKit/src/main/java/io/rong/callkit/CallEndMessageItemProvider.java
  9. 632 0
      im/CallKit/src/main/java/io/rong/callkit/CallFloatBoxView.java
  10. 68 0
      im/CallKit/src/main/java/io/rong/callkit/CallOptionMenu.java
  11. 155 0
      im/CallKit/src/main/java/io/rong/callkit/CallPromptDialog.java
  12. 454 0
      im/CallKit/src/main/java/io/rong/callkit/CallSelectMemberActivity.java
  13. 234 0
      im/CallKit/src/main/java/io/rong/callkit/CallUserGridView.java
  14. 84 0
      im/CallKit/src/main/java/io/rong/callkit/ContainerLayout.java
  15. 757 0
      im/CallKit/src/main/java/io/rong/callkit/MultiAudioCallActivity.java
  16. 97 0
      im/CallKit/src/main/java/io/rong/callkit/MultiCallEndMessageProvider.java
  17. 1470 0
      im/CallKit/src/main/java/io/rong/callkit/MultiVideoCallActivity.java
  18. 65 0
      im/CallKit/src/main/java/io/rong/callkit/PickupDetector.java
  19. 26 0
      im/CallKit/src/main/java/io/rong/callkit/RongCallAction.java
  20. 26 0
      im/CallKit/src/main/java/io/rong/callkit/RongCallCustomerHandlerListener.java
  21. 289 0
      im/CallKit/src/main/java/io/rong/callkit/RongCallKit.java
  22. 165 0
      im/CallKit/src/main/java/io/rong/callkit/RongCallModule.java
  23. 177 0
      im/CallKit/src/main/java/io/rong/callkit/RongCallProxy.java
  24. 16 0
      im/CallKit/src/main/java/io/rong/callkit/RongVoIPIntent.java
  25. 925 0
      im/CallKit/src/main/java/io/rong/callkit/SingleCallActivity.java
  26. 158 0
      im/CallKit/src/main/java/io/rong/callkit/VideoPlugin.java
  27. 292 0
      im/CallKit/src/main/java/io/rong/callkit/util/BluetoothUtil.java
  28. 65 0
      im/CallKit/src/main/java/io/rong/callkit/util/BlurBitmapUtil.java
  29. 21 0
      im/CallKit/src/main/java/io/rong/callkit/util/CallKitSearchBarListener.java
  30. 140 0
      im/CallKit/src/main/java/io/rong/callkit/util/CallKitSearchBarView.java
  31. 176 0
      im/CallKit/src/main/java/io/rong/callkit/util/CallKitUtils.java
  32. 226 0
      im/CallKit/src/main/java/io/rong/callkit/util/CallVerticalScrollView.java
  33. 31 0
      im/CallKit/src/main/java/io/rong/callkit/util/GlideBlurformation.java
  34. 57 0
      im/CallKit/src/main/java/io/rong/callkit/util/GlideRoundTransform.java
  35. 57 0
      im/CallKit/src/main/java/io/rong/callkit/util/GlideUtils.java
  36. 53 0
      im/CallKit/src/main/java/io/rong/callkit/util/HeadsetInfo.java
  37. 61 0
      im/CallKit/src/main/java/io/rong/callkit/util/HeadsetPlugReceiver.java
  38. 23 0
      im/CallKit/src/main/java/io/rong/callkit/util/ICallScrollView.java
  39. 191 0
      im/CallKit/src/main/java/io/rong/callkit/util/SPUtils.java
  40. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_nav_back_x.png
  41. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_search.png
  42. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_search_delete_x.png
  43. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_search_focused_x.png
  44. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_search_x.png
  45. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_mult_video_user_clo_camera.png
  46. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_mult_video_user_mute.png
  47. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_mult_video_user_status.png
  48. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_mute_unavailable.png
  49. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/callkit_select_ic_nav_back_x.png
  50. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_add.png
  51. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_answer.png
  52. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_answer_hover.png
  53. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_left_cancel.png
  54. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_left_connected.png
  55. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_right_cancel.png
  56. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_right_connected.png
  57. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_camera.png
  58. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_camera_hover.png
  59. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_disable_camera.png
  60. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_disable_camera_hover.png
  61. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_float_audio.png
  62. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_float_video.png
  63. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_handfree.png
  64. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_handfree_hover.png
  65. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_handup.png
  66. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_hang_up.png
  67. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_hang_up_hover.png
  68. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_add.png
  69. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_camera.png
  70. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_checked.png
  71. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_hover.png
  72. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_normal.png
  73. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video.png
  74. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video_pressed.png
  75. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_iphone.png
  76. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_iphone_hover.png
  77. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_menu_bg.9.png
  78. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_minimize.png
  79. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_more.png
  80. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_mute.png
  81. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_mute_hover.png
  82. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_phone.png
  83. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_1.png
  84. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_2.png
  85. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_3.png
  86. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_4.png
  87. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_5.png
  88. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_6.png
  89. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer.png
  90. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer_hover.png
  91. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_switch_camera.png
  92. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_answer.png
  93. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover.png
  94. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover_new.png
  95. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_answer_new.png
  96. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_left.png
  97. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_right.png
  98. BIN=BIN
      im/CallKit/src/main/res/drawable-xhdpi/rc_voip_whiteboard.png
  99. 11 0
      im/CallKit/src/main/res/drawable/bg_search.xml
  100. 0 0
      im/CallKit/src/main/res/drawable/callkit_multiaudiouesrinfocontners.xml

+ 1 - 0
im/CallKit/.gitignore

@@ -0,0 +1 @@
+/build

+ 15 - 0
im/CallKit/build.gradle

@@ -0,0 +1,15 @@
+apply plugin: 'com.android.library'
+apply from: "../gradle_component/baseconfig.gradle"
+android {
+
+    defaultConfig {
+        versionName "2.9.11 Stable"
+    }
+}
+
+dependencies {
+    api fileTree(dir: 'libs', include: ['*.jar'])
+    api project(':IMKit')
+    api project(':CallLib')
+    api project(':bc_base')
+}

+ 17 - 0
im/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 *;
+#}

+ 67 - 0
im/CallKit/src/main/AndroidManifest.xml

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.rong.callkit">
+
+
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+
+    <application>
+
+        <!-- new voip config start -->
+        <activity
+            android:name=".MultiVideoCallActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateHidden|adjustResize">
+            <intent-filter>
+                <action android:name="io.rong.intent.action.voip.MULTIVIDEO" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".SingleCallActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateHidden|adjustResize">
+            <intent-filter>
+                <action android:name="io.rong.intent.action.voip.SINGLEVIDEO" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="io.rong.intent.action.voip.SINGLEAUDIO" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".MultiAudioCallActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateHidden|adjustResize">
+            <intent-filter>
+                <action android:name="io.rong.intent.action.voip.MULTIAUDIO" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <!-- new voip config end -->
+
+        <activity android:name=".CallSelectMemberActivity"></activity>
+    </application>
+
+</manifest>

+ 160 - 0
im/CallKit/src/main/java/io/rong/callkit/AudioPlugin.java

@@ -0,0 +1,160 @@
+package io.rong.callkit;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+import io.rong.callkit.util.CallKitUtils;
+import io.rong.calllib.RongCallClient;
+import io.rong.calllib.RongCallCommon;
+import io.rong.calllib.RongCallSession;
+import io.rong.common.RLog;
+import io.rong.imkit.RongExtension;
+import io.rong.imkit.RongIM;
+import io.rong.imkit.plugin.IPluginModule;
+import io.rong.imkit.plugin.IPluginRequestPermissionResultCallback;
+import io.rong.imkit.utilities.PermissionCheckUtil;
+import io.rong.imlib.RongIMClient;
+import io.rong.imlib.model.Conversation;
+import io.rong.imlib.model.Discussion;
+
+/**
+ * Created by weiqinxiao on 16/8/16.
+ */
+public class AudioPlugin implements IPluginModule, IPluginRequestPermissionResultCallback {
+    private static final String TAG = "AudioPlugin";
+    private ArrayList<String> 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(final Fragment currentFragment, final RongExtension extension) {
+        context = currentFragment.getActivity().getApplicationContext();
+        conversationType = extension.getConversationType();
+        targetId = extension.getTargetId();
+        Log.i(TAG,"---- targetId=="+targetId);
+        String[] permissions = CallKitUtils.getCallpermissions();
+        if (PermissionCheckUtil.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) {
+        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;
+        }
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+        if (networkInfo == null || !networkInfo.isConnected() || !networkInfo.isAvailable()) {
+            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.getApplicationContext().startActivity(intent);
+        } else if (conversationType.equals(Conversation.ConversationType.DISCUSSION)) {
+            RongIM.getInstance().getDiscussion(targetId, new RongIMClient.ResultCallback<Discussion>() {
+                @Override
+                public void onSuccess(Discussion discussion) {
+                    Intent intent = new Intent(context, CallSelectMemberActivity.class);
+                    allMembers = (ArrayList<String>) discussion.getMemberIdList();
+                    intent.putStringArrayListExtra("allMembers", allMembers);
+                    intent.putExtra("conversationType", conversationType.getValue());
+                    String myId = RongIMClient.getInstance().getCurrentUserId();
+                    ArrayList<String> 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(RongIMClient.ErrorCode 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<String> 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;
+        }
+
+        Intent intent = new Intent(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIAUDIO);
+        ArrayList<String> userIds = data.getStringArrayListExtra("invited");
+        ArrayList<String> 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.getApplicationContext().startActivity(intent);
+    }
+
+    @Override
+    public boolean onRequestPermissionResult(Fragment fragment, RongExtension extension, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        if (PermissionCheckUtil.checkPermissions(fragment.getActivity(), permissions)) {
+            startAudioActivity(fragment, extension);
+        } else {
+            extension.showRequestPermissionFailedAlter(PermissionCheckUtil.getNotGrantedPermissionMsg(context, permissions, grantResults));
+        }
+        return true;
+    }
+}

+ 745 - 0
im/CallKit/src/main/java/io/rong/callkit/BaseCallActivity.java

@@ -0,0 +1,745 @@
+package io.rong.callkit;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.bailingcloud.bailingvideo.engine.binstack.util.FinLog;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.rong.callkit.util.BluetoothUtil;
+import io.rong.callkit.util.CallKitUtils;
+import io.rong.callkit.util.HeadsetInfo;
+import io.rong.callkit.util.HeadsetPlugReceiver;
+import io.rong.calllib.IRongCallListener;
+import io.rong.calllib.RongCallClient;
+import io.rong.calllib.RongCallCommon;
+import io.rong.calllib.RongCallSession;
+import io.rong.common.RLog;
+import io.rong.imkit.RongContext;
+import io.rong.imkit.manager.AudioPlayManager;
+import io.rong.imkit.manager.AudioRecordManager;
+import io.rong.imkit.utilities.PermissionCheckUtil;
+import io.rong.imkit.utils.NotificationUtil;
+import io.rong.imlib.model.UserInfo;
+
+import static io.rong.callkit.CallFloatBoxView.showFB;
+import static io.rong.callkit.util.CallKitUtils.isDial;
+
+/**
+ * Created by weiqinxiao on 16/3/9.
+ */
+public class BaseCallActivity extends BaseNoActionBarActivity implements IRongCallListener, PickupDetector.PickupDetectListener {
+
+    private static final String TAG = "BaseCallActivity";
+    private static final String MEDIAPLAYERTAG = "MEDIAPLAYERTAG";
+    private final static 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 MediaPlayer mMediaPlayer;
+    private Vibrator mVibrator;
+    private long time = 0;
+    private Runnable updateTimeRunnable;
+
+    private boolean shouldRestoreFloat;
+    //是否是请求开启悬浮窗权限的过程中
+    private boolean checkingOverlaysPermission;
+    protected Handler handler;
+    /**
+     * 表示是否正在挂断
+     */
+    protected boolean isFinishing;
+
+    protected PickupDetector pickupDetector;
+    protected PowerManager powerManager;
+    protected PowerManager.WakeLock wakeLock;
+
+    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;
+
+    /**
+     * 判断是拨打界面还是接听界面
+     */
+    private boolean isIncoming;
+
+    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);
+    }
+
+    /**
+     * 监听情景模式(Ringer Mode)发生改变后,切换为铃声或振动
+     */
+    protected final BroadcastReceiver mRingModeReceiver = new BroadcastReceiver() {
+        boolean isFirstReceivedBroadcast = true;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // 此类广播为 sticky 类型的,首次注册广播便会收到,因此第一次收到的广播不作处理
+            if (isFirstReceivedBroadcast) {
+                isFirstReceivedBroadcast = false;
+                return;
+            }
+            // 根据 isIncoming 判断只有在接听界面时做铃声和振动的切换,拨打界面不作处理
+            if (isIncoming && intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION) && !CallKitUtils.callConnected) {
+                AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+                final int ringMode = am.getRingerMode();
+                Log.i(TAG,"Ring mode Receiver mode="+ringMode);
+                switch (ringMode) {
+                    case AudioManager.RINGER_MODE_NORMAL:
+                        stopRing();
+                        startRing();
+                        break;
+                    case AudioManager.RINGER_MODE_SILENT:
+                        stopRing();
+                        break;
+                    case AudioManager.RINGER_MODE_VIBRATE:
+                        stopRing();
+                        startVibrator();
+                        break;
+                    default:
+                }
+            }
+        }
+    };
+
+    private HeadsetPlugReceiver headsetPlugReceiver=null;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        RLog.d(TAG, "BaseCallActivity onCreate");
+        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;
+
+        PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
+        boolean isScreenOn = pm.isScreenOn();
+        if (!isScreenOn) {
+            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK, "bright");
+            wl.acquire();
+            wl.release();
+        }
+        handler = new Handler();
+        RongCallProxy.getInstance().setCallListener(this);
+
+        AudioPlayManager.getInstance().stopPlay();
+        AudioRecordManager.getInstance().destroyRecord();
+        RongContext.getInstance().getEventBus().register(this);
+
+        initMp();
+
+        //注册 BroadcastReceiver 监听情景模式的切换
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+        registerReceiver(mRingModeReceiver, filter);
+    }
+
+    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();
+                        Log.i(MEDIAPLAYERTAG,"setOnPreparedListener Error!");
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Intent intent = getIntent();
+        Bundle bundle = intent.getBundleExtra("floatbox");
+        if (shouldRestoreFloat && bundle != null) {
+            onRestoreFloatBox(bundle);
+        }
+    }
+
+    public void onOutgoingCallRinging() {
+        isIncoming = false;
+        try {
+            initMp();
+            AssetFileDescriptor assetFileDescriptor = getResources().openRawResourceFd(R.raw.voip_outgoing_ring);
+            mMediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(),
+                    assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
+            assetFileDescriptor.close();
+            // 设置 MediaPlayer 播放的声音用途
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                AudioAttributes attributes = new AudioAttributes.Builder()
+                        .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                        .build();
+                mMediaPlayer.setAudioAttributes(attributes);
+            } else {
+                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
+            }
+            mMediaPlayer.prepareAsync();
+            final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+            if (am != null) {
+                am.setSpeakerphoneOn(false);
+                // 设置此值可在拨打时控制响铃音量
+                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){
+            Log.i(MEDIAPLAYERTAG,"---onOutgoingCallRinging Error---"+e1.getMessage());
+        }
+    }
+
+    public void onIncomingCallRinging() {
+        isIncoming = true;
+        int ringerMode = NotificationUtil.getRingerMode(this);
+        if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
+            if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
+                startVibrator();
+            } else {
+                if (isVibrateWhenRinging()) {
+                    startVibrator();
+                }
+                startRing();
+            }
+        }
+    }
+
+    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 long getTime() {
+        return time;
+    }
+
+    @SuppressLint("MissingPermission")
+    protected void stopRing() {
+        try {
+            if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+                mMediaPlayer.stop();
+            }
+            if (mMediaPlayer != null) {
+                mMediaPlayer.reset();
+            }
+            if (mVibrator != null) {
+                mVibrator.cancel();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.i(MEDIAPLAYERTAG,"mMediaPlayer stopRing error="+((e==null)?"null":e.getMessage()));
+        }
+    }
+
+    protected void startRing() {
+        Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+        try {
+            mMediaPlayer.setDataSource(this, uri);
+            mMediaPlayer.prepareAsync();
+        } catch (IOException e) {
+            e.printStackTrace();
+            RLog.e(TAG, "TYPE_RINGTONE not found : " + uri);
+            try {
+                uri = RingtoneManager.getValidRingtoneUri(this);
+                mMediaPlayer.setDataSource(this, uri);
+                mMediaPlayer.prepareAsync();
+            } catch (IOException e1) {
+                e1.printStackTrace();
+                RLog.e(TAG, "Ringtone not found: " + uri);
+            }catch (IllegalStateException el) {
+                el.printStackTrace();
+                Log.i(MEDIAPLAYERTAG,"startRing--IllegalStateException");
+            }
+        }
+    }
+
+    protected void startVibrator() {
+        if (mVibrator == null) {
+            mVibrator = (Vibrator) RongContext.getInstance().getSystemService(Context.VIBRATOR_SERVICE);
+        } else {
+            mVibrator.cancel();
+        }
+        mVibrator.vibrate(new long[]{500, 1000}, 0);
+    }
+
+    @Override
+    public void onCallOutgoing(RongCallSession callProfile, SurfaceView localVideo) {
+        CallKitUtils.shouldShowFloat = true;
+        CallKitUtils.isDial=true;
+    }
+
+    @Override
+    public void onRemoteUserRinging(String userId) {
+
+    }
+
+    @Override
+    public void onCallDisconnected(RongCallSession callProfile, RongCallCommon.CallDisconnectedReason reason) {
+        if (RongCallKit.getCustomerHandlerListener() != null) {
+            RongCallKit.getCustomerHandlerListener().onCallDisconnected(callProfile, reason);
+        }
+        CallKitUtils.callConnected=false;
+        CallKitUtils.shouldShowFloat = false;
+
+        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 REMOTE_HANGUP:
+            case HANGUP:
+            case NETWORK_ERROR:
+            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);
+        }
+        stopRing();
+        NotificationUtil.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) {
+
+    }
+
+    @Override
+    public void onMediaTypeChanged(String userId, RongCallCommon.CallMediaType mediaType, SurfaceView video) {
+
+    }
+
+    @Override
+    public void onError(RongCallCommon.CallErrorCode errorCode) {
+    }
+
+    @Override
+    public void onCallConnected(RongCallSession callProfile, SurfaceView localVideo) {
+        if (RongCallKit.getCustomerHandlerListener() != null) {
+            RongCallKit.getCustomerHandlerListener().onCallConnected(callProfile, localVideo);
+        }
+        CallKitUtils.callConnected=true;
+        CallKitUtils.shouldShowFloat = true;
+        CallKitUtils.isDial=false;
+        AudioPlayManager.getInstance().setInVoipMode(true);
+        AudioRecordManager.getInstance().destroyRecord();
+    }
+
+
+    @Override
+    protected void onPause() {
+        if (CallKitUtils.shouldShowFloat && !checkingOverlaysPermission) {
+            Bundle bundle = new Bundle();
+            String action = onSaveFloatBoxState(bundle);
+            if (checkDrawOverlaysPermission(true)) {
+                if (action != null) {
+                    bundle.putString("action", action);
+                    showFB(getApplicationContext(),bundle);
+                    int mediaType = bundle.getInt("mediaType");
+                    showOnGoingNotification(getString(R.string.rc_call_on_going),
+                            mediaType == RongCallCommon.CallMediaType.AUDIO.getValue()
+                                    ? getString(R.string.rc_audio_call_on_going) : getString(R.string.rc_video_call_on_going));
+                    if (!isFinishing()) {
+                        finish();
+                    }
+                }
+            } else {
+                Toast.makeText(this, getString(R.string.rc_voip_float_window_not_allowed), Toast.LENGTH_SHORT).show();
+            }
+        }
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        RLog.d(TAG, "BaseCallActivity onResume");
+        try {
+            RongCallSession session = RongCallClient.getInstance().getCallSession();
+            if (session != null) {
+                RongCallProxy.getInstance().setCallListener(this);
+                if (shouldRestoreFloat) {
+                    CallFloatBoxView.hideFloatBox();
+                    NotificationUtil.clearNotification(this, BaseCallActivity.CALL_NOTIFICATION_ID);
+                }
+                long activeTime = session != null ? session.getActiveTime() : 0;
+                time = activeTime == 0 ? 0 : (System.currentTimeMillis() - activeTime) / 1000;
+                shouldRestoreFloat = true;
+                if (time > 0) {
+                    CallKitUtils.shouldShowFloat = true;
+                }
+                if (checkingOverlaysPermission) {
+                    checkDrawOverlaysPermission(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<String> selectedUserIds = RongCallKit.getCustomerHandlerListener().handleActivityResult(requestCode, resultCode, data);
+            if (selectedUserIds != null && selectedUserIds.size() > 0)
+                onAddMember(selectedUserIds);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        try {
+            RongContext.getInstance().getEventBus().unregister(this);
+            handler.removeCallbacks(updateTimeRunnable);
+            unregisterReceiver(mRingModeReceiver);
+            if(mMediaPlayer!=null && mMediaPlayer.isPlaying()){
+                mMediaPlayer.stop();
+            }
+            mMediaPlayer.release();
+            // 退出此页面后应设置成正常模式,否则按下音量键无法更改其他音频类型的音量
+            AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+            if (am != null) {
+                am.setMode(AudioManager.MODE_NORMAL);
+            }
+            if(mMediaPlayer!=null){
+                mMediaPlayer=null;
+            }
+        } catch (IllegalStateException e) {
+            e.printStackTrace();
+            Log.i(MEDIAPLAYERTAG,"--- onDestroy IllegalStateException---");
+        }
+        super.onDestroy();
+        unRegisterHeadsetplugReceiver();
+    }
+
+    @Override
+    public void onRemoteCameraDisabled(String userId, boolean muted) {
+
+    }
+
+    @Override
+    public void onWhiteBoardURL(String url) {
+
+    }
+
+    @Override
+    public void onNetWorkLossRate(int lossRate) {
+
+    }
+
+    @Override
+    public void onNotifySharingScreen(String userId, boolean isSharing) {
+
+    }
+
+    @Override
+    public void onNotifyDegradeNormalUserToObserver(String userId) {
+
+    }
+
+    @Override
+    public void onNotifyAnswerObserverRequestBecomeNormalUser(String userId, long status) {
+
+    }
+
+    @Override
+    public void onNotifyUpgradeObserverToNormalUser() {
+
+    }
+
+    @Override
+    public void onNotifyHostControlUserDevice(String userId, int dType, int isOpen) {
+
+    }
+
+    /** onStart时恢复浮窗 **/
+    public void onRestoreFloatBox(Bundle bundle) {
+
+    }
+
+    protected void addMember(ArrayList<String> 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<String> newMemberIds) {
+    }
+
+    /** onPause时保存页面各状态数据 **/
+    public String onSaveFloatBoxState(Bundle bundle) {
+        return null;
+    }
+
+    public void showOnGoingNotification(String title, String content) {
+        Intent intent = new Intent(getIntent().getAction());
+        Bundle bundle = new Bundle();
+        onSaveFloatBoxState(bundle);
+        bundle.putBoolean("isDial",isDial);
+        intent.putExtra("floatbox", bundle);
+        intent.putExtra("callAction", RongCallAction.ACTION_RESUME_CALL.getName());
+        PendingIntent pendingIntent = PendingIntent.getActivity(this, 1000, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        NotificationUtil.showNotification(this, title, content, pendingIntent, CALL_NOTIFICATION_ID, Notification.DEFAULT_LIGHTS);
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+    }
+
+    @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 result;
+    }
+
+    private class UpdateTimeRunnable implements Runnable {
+        private TextView timeView;
+
+        public UpdateTimeRunnable(TextView timeView) {
+            this.timeView = timeView;
+        }
+
+        @Override
+        public void run() {
+            time++;
+            if (time >= 3600) {
+                timeView.setText(String.format("%d:%02d:%02d", time / 3600, (time % 3600) / 60, (time % 60)));
+            } else {
+                timeView.setText(String.format("%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 (Build.BRAND.toLowerCase().contains("xiaomi") || Build.VERSION.SDK_INT >= 23) {
+            if (PermissionCheckUtil.canDrawOverlays(this, needOpenPermissionSetting)) {
+                checkingOverlaysPermission = false;
+                return true;
+            } else {
+                if (needOpenPermissionSetting && !Build.BRAND.toLowerCase().contains("xiaomi")) {
+                    checkingOverlaysPermission = true;
+                }
+                return false;
+            }
+        } else {
+            checkingOverlaysPermission = false;
+            return true;
+        }
+    }
+
+    protected void createPowerManager() {
+        if (powerManager == null) {
+            powerManager = (PowerManager) getSystemService(POWER_SERVICE);
+            wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
+        }
+    }
+
+    protected void createPickupDetector() {
+        if (pickupDetector == null) {
+            pickupDetector = new PickupDetector(this);
+        }
+    }
+
+    @Override
+    public void onPickupDetected(boolean isPickingUp) {
+        if (wakeLock == null) {
+            RLog.d(TAG, "No PROXIMITY_SCREEN_OFF_WAKE_LOCK");
+            return;
+        }
+        if (isPickingUp && !wakeLock.isHeld()) {
+            wakeLock.acquire();
+        }
+        if (!isPickingUp && wakeLock.isHeld()) {
+            try {
+                wakeLock.setReferenceCounted(false);
+                wakeLock.release();
+            } catch (Exception e) {
+
+            }
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        if (!PermissionCheckUtil.checkPermissions(this, permissions)) {
+            PermissionCheckUtil.showRequestPermissionFailedAlter(this, PermissionCheckUtil.getNotGrantedPermissionMsg(this, permissions, grantResults));
+        }
+    }
+
+    /**
+     * 判断系统是否设置了 响铃时振动
+     */
+    private boolean isVibrateWhenRinging() {
+        ContentResolver resolver = 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;
+        }
+    }
+
+    public void openSpeakerphoneNoWiredHeadsetOn(){
+        AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
+        if (audioManager.isWiredHeadsetOn()) {
+            RongCallClient.getInstance().setEnableSpeakerphone(false);
+        } else {
+            RongCallClient.getInstance().setEnableSpeakerphone(true);
+        }
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    /**
+     * onHangupBtnClick onDestory 处解绑
+     */
+    public void unRegisterHeadsetplugReceiver(){
+        if(headsetPlugReceiver!=null){
+            unregisterReceiver(headsetPlugReceiver);
+            headsetPlugReceiver=null;
+        }
+    }
+}

+ 14 - 0
im/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.RongConfigurationManager;
+
+public class BaseNoActionBarActivity extends Activity {
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        Context newContext = RongConfigurationManager.getInstance().getConfigurationContext(newBase);
+        super.attachBaseContext(newContext);
+    }
+}

+ 201 - 0
im/CallKit/src/main/java/io/rong/callkit/CallEndMessageItemProvider.java

@@ -0,0 +1,201 @@
+package io.rong.callkit;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import java.util.Locale;
+
+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.widget.AutoLinkTextView;
+import io.rong.imkit.RongIM;
+import io.rong.imkit.model.ProviderTag;
+import io.rong.imkit.model.UIMessage;
+import io.rong.imkit.utilities.OptionsPopupDialog;
+import io.rong.imkit.widget.provider.IContainerItemProvider;
+import io.rong.imlib.model.Message;
+
+import static io.rong.calllib.RongCallCommon.CallDisconnectedReason.OTHER_DEVICE_HAD_ACCEPTED;
+
+@ProviderTag(messageContent = CallSTerminateMessage.class, showSummaryWithName = false, showProgress = false, showWarning = false, showReadState = false)
+public class CallEndMessageItemProvider extends IContainerItemProvider.MessageProvider<CallSTerminateMessage> {
+    private static class ViewHolder {
+        AutoLinkTextView message;
+    }
+
+
+    @Override
+    public View newView(Context context, ViewGroup group) {
+        View view = LayoutInflater.from(context).inflate(R.layout.rc_item_text_message, null);
+
+        ViewHolder holder = new ViewHolder();
+        holder.message = (AutoLinkTextView) view.findViewById(android.R.id.text1);
+        view.setTag(holder);
+        return view;
+    }
+
+    @Override
+    public void bindView(View v, int position, CallSTerminateMessage content, UIMessage data) {
+        ViewHolder holder = (ViewHolder) v.getTag();
+
+        if (data == null || content == null) {
+            return;
+        }
+        if (data.getMessageDirection() == Message.MessageDirection.SEND) {
+            holder.message.setBackgroundResource(R.drawable.rc_ic_bubble_right);
+        } else {
+            holder.message.setBackgroundResource(R.drawable.rc_ic_bubble_left);
+        }
+
+        RongCallCommon.CallMediaType mediaType = content.getMediaType();
+        String direction = content.getDirection();
+        Drawable drawable = null;
+
+        String msgContent = "";
+        switch (content.getReason()) {
+            case CANCEL:
+                msgContent = v.getResources().getString(R.string.rc_voip_mo_cancel);
+                break;
+            case REJECT:
+                msgContent = v.getResources().getString(R.string.rc_voip_mo_reject);
+                break;
+            case NO_RESPONSE:
+            case BUSY_LINE:
+                msgContent = v.getResources().getString(R.string.rc_voip_mo_no_response);
+                break;
+            case REMOTE_BUSY_LINE:
+                msgContent = v.getResources().getString(R.string.rc_voip_mt_busy);
+                break;
+            case REMOTE_CANCEL:
+                msgContent = v.getResources().getString(R.string.rc_voip_mt_cancel);
+                break;
+            case REMOTE_REJECT:
+                msgContent = v.getResources().getString(R.string.rc_voip_mt_reject);
+                break;
+            case REMOTE_NO_RESPONSE:
+                msgContent = v.getResources().getString(R.string.rc_voip_mt_no_response);
+                break;
+            case HANGUP:
+            case REMOTE_HANGUP:
+                msgContent = v.getResources().getString(R.string.rc_voip_call_time_length);
+                msgContent += content.getExtra();
+                break;
+            case NETWORK_ERROR:
+            case REMOTE_NETWORK_ERROR:
+            case INIT_VIDEO_ERROR:
+                msgContent = v.getResources().getString(R.string.rc_voip_call_interrupt);
+                break;
+            case OTHER_DEVICE_HAD_ACCEPTED:
+                msgContent = v.getResources().getString(R.string.rc_voip_call_other);
+                break;
+        }
+
+        holder.message.setText(msgContent);
+        holder.message.setCompoundDrawablePadding(15);
+
+        if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) {
+            if (direction != null && direction.equals("MO")) {
+                drawable = v.getResources().getDrawable(R.drawable.rc_voip_video_right);
+                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+                holder.message.setCompoundDrawables(null, null, drawable, null);
+                holder.message.setTextColor(v.getResources().getColor(R.color.rc_voip_color_right));
+            } else {
+                drawable = v.getResources().getDrawable(R.drawable.rc_voip_video_left);
+                drawable.setBounds(0, 0,  drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+                holder.message.setCompoundDrawables(drawable, null, null, null);
+                holder.message.setTextColor(v.getResources().getColor(R.color.rc_voip_color_left));
+            }
+        } else {
+            if (direction != null && direction.equals("MO")) {
+                if (content.getReason().equals(RongCallCommon.CallDisconnectedReason.HANGUP) ||
+                        content.getReason().equals(RongCallCommon.CallDisconnectedReason.REMOTE_HANGUP)) {
+                    drawable = v.getResources().getDrawable(R.drawable.rc_voip_audio_right_connected);
+                } else {
+                    drawable = v.getResources().getDrawable(R.drawable.rc_voip_audio_right_cancel);
+                }
+                drawable.setBounds(0, 0,  drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+                holder.message.setCompoundDrawables(null, null, drawable, null);
+                holder.message.setTextColor(v.getResources().getColor(R.color.rc_voip_color_right));
+            } else {
+                if (content.getReason().equals(RongCallCommon.CallDisconnectedReason.HANGUP) ||
+                        content.getReason().equals(RongCallCommon.CallDisconnectedReason.REMOTE_HANGUP)) {
+                    drawable = v.getResources().getDrawable(R.drawable.rc_voip_audio_left_connected);
+                } else {
+                    drawable = v.getResources().getDrawable(R.drawable.rc_voip_audio_left_cancel);
+                }
+                drawable.setBounds(0, 0,  drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+                holder.message.setCompoundDrawables(drawable, null, null, null);
+                holder.message.setTextColor(v.getResources().getColor(R.color.rc_voip_color_left));
+            }
+        }
+    }
+
+    @Override
+    public Spannable getContentSummary(CallSTerminateMessage data) {
+        return null;
+    }
+
+    @Override
+    public Spannable getContentSummary(Context context, CallSTerminateMessage data) {
+
+        RongCallCommon.CallMediaType mediaType = data.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));
+        }
+    }
+
+    @Override
+    public void onItemClick(View view, int position, CallSTerminateMessage content, UIMessage message) {
+        if (content.getReason() == OTHER_DEVICE_HAD_ACCEPTED){
+            return;
+        }
+        RongCallSession profile = RongCallClient.getInstance().getCallSession();
+        if (profile != null && profile.getActiveTime() > 0) {
+            Toast.makeText(view.getContext(),
+                    profile.getMediaType() == RongCallCommon.CallMediaType.AUDIO ?
+                            view.getContext().getString(R.string.rc_voip_call_audio_start_fail) :
+                            view.getContext().getString(R.string.rc_voip_call_video_start_fail),
+                    Toast.LENGTH_SHORT)
+                    .show();
+            return;
+        }
+        RongCallCommon.CallMediaType mediaType = content.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(view.getContext().getPackageName());
+        intent.putExtra("conversationType", message.getConversationType().getName().toLowerCase(Locale.US));
+        intent.putExtra("targetId", message.getTargetId());
+        intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName());
+        view.getContext().startActivity(intent);
+    }
+
+    @Override
+    public void onItemLongClick(final View view, int position, final CallSTerminateMessage content, final UIMessage message) {
+
+        String[] items = new String[] {view.getContext().getResources().getString(R.string.rc_dialog_item_message_delete)};
+
+        OptionsPopupDialog.newInstance(view.getContext(), items).setOptionsPopupDialogListener(new OptionsPopupDialog.OnOptionsItemClickedListener() {
+            @Override
+            public void onOptionsItemClicked(int which) {
+                if (which == 0)
+                    RongIM.getInstance().deleteMessages(new int[] {message.getMessageId()}, null);
+            }
+        }).show();
+    }
+}

+ 632 - 0
im/CallKit/src/main/java/io/rong/callkit/CallFloatBoxView.java

@@ -0,0 +1,632 @@
+package io.rong.callkit;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+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.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import io.rong.callkit.util.CallKitUtils;
+import io.rong.calllib.IRongCallListener;
+import io.rong.calllib.RongCallClient;
+import io.rong.calllib.RongCallCommon;
+import io.rong.calllib.RongCallSession;
+import io.rong.calllib.message.CallSTerminateMessage;
+import io.rong.common.RLog;
+import io.rong.imkit.RongIM;
+import io.rong.imkit.utils.NotificationUtil;
+import io.rong.imlib.RongIMClient;
+import io.rong.imlib.model.Conversation;
+import io.rong.message.InformationNotificationMessage;
+
+import static io.rong.callkit.util.CallKitUtils.closeKeyBoard;
+import static io.rong.callkit.util.CallKitUtils.isDial;
+
+/**
+ * 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;
+
+    public static void showFB(Context context, Bundle bundle){
+        Log.i("audioTag","CallKitUtils.isDial="+CallKitUtils.isDial);
+        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;
+
+        mBundle = bundle;
+        wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        final 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.format = PixelFormat.TRANSLUCENT;
+        params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+        params.gravity = Gravity.CENTER;
+        params.x = context.getResources().getDisplayMetrics().widthPixels;
+        params.y = 0;
+
+        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) {
+                        onClickToResume();
+                    } else {
+                        tag = 0;
+                    }
+                }
+                return true;
+            }
+        });
+        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);
+        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 onCallOutgoing(RongCallSession callInfo, SurfaceView localVideo) {
+
+            }
+
+            @Override
+            public void onRemoteUserRinging(String userId) {
+
+            }
+
+            @Override
+            public void onCallDisconnected(RongCallSession callProfile, RongCallCommon.CallDisconnectedReason reason) {
+                String senderId;
+                String extra = "";
+                senderId = callProfile.getInviterUserId();
+                switch (reason) {
+                    case HANGUP:
+                    case REMOTE_HANGUP:
+                        if (mTime >= 3600) {
+                            extra = String.format("%d:%02d:%02d", mTime / 3600, (mTime % 3600) / 60, (mTime % 60));
+                        } else {
+                            extra = String.format("%02d:%02d", (mTime % 3600) / 60, (mTime % 60));
+                        }
+                        break;
+                }
+
+                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");
+                                RongIM.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);
+                                RongIM.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())) {
+                                RongIM.getInstance().insertOutgoingMessage(Conversation.ConversationType.GROUP, callProfile.getTargetId(),
+                                        io.rong.imlib.model.Message.SentStatus.SENT, informationNotificationMessage, serverTime, null);
+                            } else {
+                                io.rong.imlib.model.Message.ReceivedStatus receivedStatus = new io.rong.imlib.model.Message.ReceivedStatus(0);
+                                RongIM.getInstance().insertIncomingMessage(Conversation.ConversationType.GROUP, callProfile.getTargetId(),
+                                        senderId, receivedStatus, 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) {
+                    wm.removeView(mView);
+                    timer.cancel();
+                    timer = null;
+                    isShown = false;
+                    mView = null;
+                    mTime = 0;
+                }
+                NotificationUtil.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;
+            }
+
+            @Override
+            public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) {
+
+            }
+
+            @Override
+            public void onRemoteUserLeft(String userId, RongCallCommon.CallDisconnectedReason reason) {
+
+            }
+
+            @Override
+            public void onMediaTypeChanged(String userId, RongCallCommon.CallMediaType mediaType, SurfaceView video) {
+
+            }
+
+            @Override
+            public void onError(RongCallCommon.CallErrorCode errorCode) {
+
+            }
+
+            @Override
+            public void onCallConnected(RongCallSession callInfo, SurfaceView localVideo) {
+                CallKitUtils.isDial=false;
+            }
+
+            @Override
+            public void onRemoteCameraDisabled(String userId, boolean muted) {
+
+            }
+
+            @Override
+            public void onWhiteBoardURL(String url) {
+
+            }
+
+            @Override
+            public void onNetWorkLossRate(int lossRate) {
+
+            }
+
+            @Override
+            public void onNotifySharingScreen(String userId, boolean isSharing) {
+
+            }
+
+            @Override
+            public void onNotifyDegradeNormalUserToObserver(String userId) {
+
+            }
+
+            @Override
+            public void onNotifyAnswerObserverRequestBecomeNormalUser(String userId, long status) {
+
+            }
+
+            @Override
+            public void onNotifyUpgradeObserverToNormalUser() {
+
+            }
+
+            @Override
+            public void onNotifyHostControlUserDevice(String userId, int dType, int isOpen) {
+
+            }
+        });
+    }
+
+    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 = 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.format = PixelFormat.TRANSLUCENT;
+        params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+        params.gravity = Gravity.CENTER;
+        params.x = context.getResources().getDisplayMetrics().widthPixels;
+        params.y = 0;
+
+        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) {
+                        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 onCallOutgoing(RongCallSession callInfo, SurfaceView localVideo) {
+
+            }
+
+            @Override
+            public void onRemoteUserRinging(String userId) {
+
+            }
+
+            @Override
+            public void onCallDisconnected(RongCallSession callProfile, RongCallCommon.CallDisconnectedReason reason) {
+                String senderId;
+                String extra = "";
+                senderId = callProfile.getInviterUserId();
+                switch (reason) {
+                    case HANGUP:
+                    case REMOTE_HANGUP:
+//                        if (mTime >= 3600) {
+//                            extra = String.format("%d:%02d:%02d", mTime / 3600, (mTime % 3600) / 60, (mTime % 60));
+//                        } else {
+//                            extra = String.format("%02d:%02d", (mTime % 3600) / 60, (mTime % 60));
+//                        }
+                        break;
+                }
+
+                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");
+                                RongIM.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);
+                                RongIM.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())) {
+                                RongIM.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);
+                                RongIM.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;
+                }
+                NotificationUtil.clearNotification(mContext, BaseCallActivity.CALL_NOTIFICATION_ID);
+                RongCallClient.getInstance().setVoIPCallListener(RongCallProxy.getInstance());
+            }
+
+            @Override
+            public void onRemoteUserLeft(String userId, RongCallCommon.CallDisconnectedReason reason) {
+
+            }
+
+            @Override
+            public void onMediaTypeChanged(String userId, RongCallCommon.CallMediaType mediaType, SurfaceView video) {
+
+            }
+
+            @Override
+            public void onError(RongCallCommon.CallErrorCode errorCode) {
+            }
+
+            @Override
+            public void onCallConnected(RongCallSession callInfo, SurfaceView localVideo) {
+                if(CallKitUtils.isDial && isShown){
+                    CallFloatBoxView.showFloatBoxToCallTime();
+                    CallKitUtils.isDial=false;
+                }
+            }
+
+
+            @Override
+            public void onRemoteUserJoined(String userId, RongCallCommon.CallMediaType mediaType, int userType, SurfaceView remoteVideo) {
+                if(CallKitUtils.isDial && isShown){
+                    CallFloatBoxView.showFloatBoxToCallTime();
+                    CallKitUtils.isDial=false;
+                }
+            }
+
+            @Override
+            public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) {
+
+            }
+
+            @Override
+            public void onRemoteCameraDisabled(String userId, boolean muted) {
+
+            }
+
+            @Override
+            public void onWhiteBoardURL(String url) {
+
+            }
+
+            @Override
+            public void onNetWorkLossRate(int lossRate) {
+
+            }
+
+            @Override
+            public void onNotifySharingScreen(String userId, boolean isSharing) {
+
+            }
+
+            @Override
+            public void onNotifyDegradeNormalUserToObserver(String userId) {
+
+            }
+
+            @Override
+            public void onNotifyAnswerObserverRequestBecomeNormalUser(String userId, long status) {
+
+            }
+
+            @Override
+            public void onNotifyUpgradeObserverToNormalUser() {
+
+            }
+
+            @Override
+            public void onNotifyHostControlUserDevice(String userId, int dType, int isOpen) {
+
+            }
+        });
+    }
+
+    /***
+     * 调用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() {
+        RongCallClient.getInstance().setVoIPCallListener(RongCallProxy.getInstance());
+        if (isShown && null != mView) {
+            wm.removeView(mView);
+            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.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(mBundle.getInt("mediaType")==RongCallCommon.CallMediaType.VIDEO.getValue() &&
+                !isDial){
+            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());
+        mContext.startActivity(intent);
+        mBundle = null;
+    }
+
+    private static void setupTime(final TextView timeView) {
+        final Handler handler = new Handler(Looper.getMainLooper());
+        TimerTask task = new TimerTask() {
+            @Override
+            public void run() {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTime++;
+                        if (mTime >= 3600) {
+                            timeView.setText(String.format("%d:%02d:%02d", mTime / 3600, (mTime % 3600) / 60, (mTime % 60)));
+                            timeView.setVisibility(View.VISIBLE);
+                        } else {
+                            timeView.setText(String.format("%02d:%02d", (mTime % 3600) / 60, (mTime % 60)));
+                            timeView.setVisibility(View.VISIBLE);
+                        }
+                    }
+                });
+            }
+        };
+
+        timer = new Timer();
+        timer.schedule(task, 0, 1000);
+    }
+}

+ 68 - 0
im/CallKit/src/main/java/io/rong/callkit/CallOptionMenu.java

@@ -0,0 +1,68 @@
+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;
+
+/**
+ * Created by mamingyang on 2018/3/19.
+ */
+
+public class CallOptionMenu extends PopupWindow {
+    private View.OnClickListener onItemClickListener;
+    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);
+            }
+        });
+        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);
+            }
+        }
+    }
+}

+ 155 - 0
im/CallKit/src/main/java/io/rong/callkit/CallPromptDialog.java

@@ -0,0 +1,155 @@
+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.rc_dimen_size_40);
+        return getScreenWidth() - 2 * (distanceToBorder);
+    }
+
+    private int getScreenWidth() {
+        return ((WindowManager) (mContext.getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay().getWidth();
+    }
+}

+ 454 - 0
im/CallKit/src/main/java/io/rong/callkit/CallSelectMemberActivity.java

@@ -0,0 +1,454 @@
+package io.rong.callkit;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import io.rong.callkit.util.CallKitSearchBarListener;
+import io.rong.callkit.util.CallKitSearchBarView;
+import io.rong.callkit.util.CallKitUtils;
+import io.rong.calllib.RongCallCommon;
+import io.rong.imkit.RongContext;
+import io.rong.imkit.model.GroupUserInfo;
+import io.rong.imkit.userInfoCache.RongUserInfoManager;
+import io.rong.imkit.widget.AsyncImageView;
+import io.rong.imlib.model.Conversation;
+import io.rong.imlib.model.UserInfo;
+
+/**
+ * @author dengxudong
+ * @version $Rev$
+ */
+public class CallSelectMemberActivity extends BaseNoActionBarActivity {
+
+    ArrayList<String> selectedMember;
+    private boolean isFirstDialog=true;
+    /** 已经选择的观察者列表 **/
+    private ArrayList<String> observerMember;
+    TextView txtvStart,callkit_conference_selected_number;
+    ListAdapter mAdapter;
+    ListView mList;
+    RongCallCommon.CallMediaType mMediaType;
+    private Conversation.ConversationType conversationType;
+    private EditText searchView;
+    private ArrayList<String> allMembers=null;
+
+    private HashMap<String,String> tempNickmembers=new HashMap<>();
+
+    private ArrayList<String> searchMembers=new ArrayList<>();
+    private ArrayList<String> invitedMembers;
+    private ArrayList<String> tempMembers=new ArrayList<>();
+
+    private ArrayList<String> allObserver=null;//保存当前通话中从多人音/视频传递过来的观察者列表
+
+    private UserInfo userInfo;
+    private GroupUserInfo groupUserInfo;
+
+    private String groupId;
+    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;
+
+    @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);
+        RongContext.getInstance().getEventBus().register(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");
+        allObserver=intent.getStringArrayListExtra("allObserver");
+        allMembers = intent.getStringArrayListExtra("allMembers");
+        groupId = intent.getStringExtra("groupId");
+        RongCallKit.GroupMembersProvider provider = RongCallKit.getGroupMemberProvider();
+        if (groupId != null && allMembers == null && provider != null) {
+            allMembers=provider.getMemberList(groupId, new RongCallKit.OnGroupMembersResult() {
+                @Override
+                public void onGotMemberList(ArrayList<String> members) {
+                    if (mAdapter != null) {
+                        if (members != null && members.size() > 0) {
+                            mAdapter.setAllMembers(members);
+                            allMembers=members;
+                            mAdapter.notifyDataSetChanged();
+
+                            /**转换昵称***/
+                            for (int i = 0; i < allMembers.size(); i++) {
+                                String userNickName=allMembers.get(i);
+                                userInfo = RongContext.getInstance().getUserInfoFromCache(userNickName);
+                                String displayName = "";
+                                if (conversationType != null && conversationType.equals(Conversation.ConversationType.GROUP)) {
+                                    groupUserInfo = RongUserInfoManager.getInstance().getGroupUserInfo(groupId, userNickName);
+                                    if (groupUserInfo != null && !TextUtils.isEmpty(groupUserInfo.getNickname())) {
+                                        displayName = groupUserInfo.getNickname();
+                                        userNickName=displayName;
+                                    }
+                                }
+                                if (TextUtils.isEmpty(displayName)) {
+                                    if (userInfo != null) {
+                                        userNickName=userInfo.getName();
+                                    }
+                                }
+                                tempNickmembers.put(allMembers.get(i),userNickName);
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        callkit_conference_selected_number= (TextView) findViewById(R.id.callkit_conference_selected_number);
+        callkit_conference_selected_number.setText(getString(R.string.callkit_selected_contacts_count,allMembers==null?0:allMembers.size()));
+        txtvStart = (TextView) findViewById(R.id.callkit_btn_ok);
+        txtvStart.setEnabled(false);
+        txtvStart.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent();
+                intent.putStringArrayListExtra("invited", selectedMember);
+                intent.putStringArrayListExtra("observers", observerMember);
+                setResult(RESULT_OK, intent);
+                CallSelectMemberActivity.this.finish();
+            }
+        });
+
+        if (allMembers == null) {
+            allMembers = invitedMembers;
+        }
+
+        mList = (ListView) findViewById(R.id.calkit_list_view_select_member);
+        if (invitedMembers != null && invitedMembers.size() > 0) {
+            mAdapter = new ListAdapter(allMembers, invitedMembers);
+            mList.setAdapter(mAdapter);
+            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);
+        ivBack.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                rlSearchTop.setVisibility(View.GONE);
+                rlActionBar.setVisibility(View.VISIBLE);
+                mAdapter.setAllMembers(allMembers);
+                mAdapter.notifyDataSetChanged();
+                CallKitUtils.closeKeyBoard(CallSelectMemberActivity.this,null);
+            }
+        });
+        searchBar.setSearchBarListener(new CallKitSearchBarListener() {
+            @Override
+            public void onSearchStart(String content) {
+                if(allMembers!=null && allMembers.size()>0){
+                    startSearchMember(content);
+                }else{
+                    Toast.makeText(CallSelectMemberActivity.this, "暂无数据提供搜索!", Toast.LENGTH_SHORT).show();
+                }
+            }
+
+            @Override
+            public void onSoftSearchKeyClick() {
+            }
+
+            @Override
+            public void onClearButtonClick() {
+                if (invitedMembers != null) {
+                    mAdapter = new ListAdapter(allMembers, invitedMembers);
+                    mList.setAdapter(mAdapter);
+                    mList.setOnItemClickListener(adapterOnItemClickListener);
+                }
+            }
+        });
+
+    }
+
+    private void startSearchMember(String searchEditContent) {
+        try {
+            searchMembers.clear();
+            tempMembers.clear();
+            if(!TextUtils.isEmpty(searchEditContent)){
+                for (String name:allMembers){
+                    if(((String)tempNickmembers.get(name)).indexOf(searchEditContent)!=-1){
+                        searchMembers.add(name);
+                    }
+                }
+                tempMembers.addAll(searchMembers);
+            }else{
+                tempMembers.addAll(allMembers);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            tempMembers.addAll(allMembers);
+        }
+        setData();
+    }
+
+    private void setData() {
+        if(null!=tempMembers && tempMembers.size()>0){
+            ListAdapter adapter = new ListAdapter(tempMembers, invitedMembers);
+            mList.setAdapter(adapter);
+            mList.setOnItemClickListener(adapterOnItemClickListener);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        RongContext.getInstance().getEventBus().unregister(this);
+        super.onDestroy();
+    }
+
+    class ListAdapter extends BaseAdapter {
+        List<String> mallMembers;
+        List<String> invitedMembers;
+
+        public ListAdapter(List<String> allMembers, List<String> invitedMembers) {
+            this.mallMembers = allMembers;
+            this.invitedMembers = invitedMembers;
+        }
+
+        public void setAllMembers(List<String> 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 = (AsyncImageView) convertView.findViewById(R.id.rc_user_portrait);
+                holder.name = (TextView) convertView.findViewById(R.id.rc_user_name);
+                convertView.setTag(holder);
+            }
+
+            holder = (ViewHolder) convertView.getTag();
+            holder.checkbox.setTag(mallMembers.get(position));
+            if (invitedMembers.contains(mallMembers.get(position))) {
+                holder.checkbox.setClickable(false);
+                holder.checkbox.setEnabled(false);
+                holder.checkbox.setImageResource(R.drawable.rc_voip_icon_checkbox_checked);
+            } else {
+                if (selectedMember.contains(mallMembers.get(position))) {
+                    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);
+            }
+            UserInfo userInfo = RongContext.getInstance().getUserInfoFromCache(mallMembers.get(position));
+            String displayName = "";
+            if (conversationType != null && conversationType.equals(Conversation.ConversationType.GROUP)) {
+                GroupUserInfo groupUserInfo = RongUserInfoManager.getInstance().getGroupUserInfo(groupId, mallMembers.get(position));
+                if (groupUserInfo != null && !TextUtils.isEmpty(groupUserInfo.getNickname())) {
+                    displayName = groupUserInfo.getNickname();
+                    holder.name.setText(displayName);
+                }
+            }
+            if (TextUtils.isEmpty(displayName)) {
+                if (userInfo != null) {
+                    holder.name.setText(userInfo.getName());
+                } else {
+                    holder.name.setText(mallMembers.get(position));
+                }
+            }
+            if (userInfo != null) {
+                holder.portrait.setAvatar(userInfo.getPortraitUri());
+            } else {
+                holder.portrait.setAvatar(null);
+            }
+            return convertView;
+        }
+    }
+
+    public void onEventMainThread(UserInfo userInfo) {
+        if (mList != null) {
+            int first = mList.getFirstVisiblePosition() - mList.getHeaderViewsCount();
+            int last = mList.getLastVisiblePosition() - mList.getHeaderViewsCount();
+
+            int index = first - 1;
+
+            while (++index <= last && index >= 0 && index < mAdapter.getCount()) {
+                if (mAdapter.getItem(index).equals(userInfo.getUserId())) {
+                    mAdapter.getView(index, mList.getChildAt(index - mList.getFirstVisiblePosition() + mList.getHeaderViewsCount()), mList);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    class ViewHolder {
+        ImageView checkbox;
+        AsyncImageView 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("选择联系人");
+        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 (!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));
+                    }
+                    return;
+                }
+                int totalNumber=selectedMember.size() + (invitedMembers.size()-(allObserver==null?0:allObserver.size()));
+                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));
+                    } else {
+                        txtvStart.setEnabled(false);
+                        txtvStart.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));
+                    } else {
+                        txtvStart.setEnabled(false);
+                        txtvStart.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, selectedMember.size()));
+            }
+        }
+    };
+}

+ 234 - 0
im/CallKit/src/main/java/io/rong/callkit/CallUserGridView.java

@@ -0,0 +1,234 @@
+package io.rong.callkit;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.rong.callkit.util.ICallScrollView;
+import io.rong.imkit.widget.AsyncImageView;
+import io.rong.imlib.model.Group;
+import io.rong.imlib.model.UserInfo;
+
+/**
+ * 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 final static int CHILDREN_SPACE = 13;
+
+    private int portraitSize;
+    private boolean isHorizontal=true;
+
+    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));
+        }
+        AsyncImageView imageView = (AsyncImageView)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) {
+            imageView.setAvatar(userInfo.getPortraitUri());
+            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);
+    }
+
+    public void removeChild(String childId) {
+        int containerCount = linearLayout.getChildCount();
+
+        LinearLayout lastContainer = null;
+        List<LinearLayout> 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) {
+                AsyncImageView imageView = (AsyncImageView)child.findViewById(R.id.rc_user_portrait);
+                imageView.setAvatar(userInfo.getPortraitUri());
+                if (enableTitle) {
+                    TextView textView = (TextView)child.findViewById(R.id.rc_user_name);
+                    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);
+            }
+        }
+    }
+}

+ 84 - 0
im/CallKit/src/main/java/io/rong/callkit/ContainerLayout.java

@@ -0,0 +1,84 @@
+package io.rong.callkit;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.RelativeLayout;
+
+import com.bailingcloud.bailingvideo.engine.binstack.util.FinLog;
+import com.bailingcloud.bailingvideo.engine.view.BlinkVideoView;
+
+/**
+ * Created by Administrator on 2017/3/30.
+ */
+
+public class ContainerLayout extends RelativeLayout {
+    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("---xx-- add view " + videoView.toString() + " Height: " + ((BlinkVideoView) videoView).rotatedFrameHeight + " Width: " + ((BlinkVideoView) videoView).rotatedFrameWidth);
+        super.addView(videoView, getBigContainerParams((BlinkVideoView) videoView));
+        currentView = videoView;
+        ((BlinkVideoView) videoView).setOnSizeChangedListener(new BlinkVideoView.OnSizeChangedListener() {
+            @Override
+            public void onChanged(BlinkVideoView.Size size) {
+                try {
+                    ContainerLayout.this.removeAllViews();
+                    FinLog.d("---xx-- change view " + videoView.toString() + " Height: " + ((BlinkVideoView) videoView).rotatedFrameHeight + " Width: " + ((BlinkVideoView) videoView).rotatedFrameWidth);
+                    ContainerLayout.this.addView(videoView, getBigContainerParams((BlinkVideoView) videoView));
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+    }
+
+
+    @NonNull
+    private LayoutParams getBigContainerParams(BlinkVideoView 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.rotatedFrameHeight == 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)
+            ((BlinkVideoView) currentView).setOnSizeChangedListener(null);
+        super.removeAllViews();
+    }
+
+    private int screenWidth;
+    private int screenHeight;
+
+}

+ 757 - 0
im/CallKit/src/main/java/io/rong/callkit/MultiAudioCallActivity.java

@@ -0,0 +1,757 @@
+package io.rong.callkit;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Bundle;
+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 com.bailingcloud.bailingvideo.engine.binstack.util.FinLog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import io.rong.callkit.util.BluetoothUtil;
+import io.rong.callkit.util.CallVerticalScrollView;
+import io.rong.callkit.util.CallKitUtils;
+import io.rong.callkit.util.HeadsetInfo;
+import io.rong.callkit.util.ICallScrollView;
+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.RongContext;
+import io.rong.imkit.RongIM;
+import io.rong.imkit.utilities.PermissionCheckUtil;
+import io.rong.imkit.widget.AsyncImageView;
+import io.rong.imlib.RongIMClient;
+import io.rong.imlib.model.Conversation;
+import io.rong.imlib.model.Discussion;
+import io.rong.imlib.model.UserInfo;
+
+/**
+ * <a href="http://support.rongcloud.cn/kb/Njcy">如何实现不基于于群组的voip</a>
+ */
+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;
+
+    @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(false);
+        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 (PermissionCheckUtil.checkPermissions(this, AUDIO_CALL_PERMISSIONS)) {
+                    if (startForCheckPermissions) {
+                        startForCheckPermissions = false;
+                        RongCallClient.getInstance().onPermissionGranted();
+                    } else {
+                        initView();
+                    }
+                } else {
+                    if (startForCheckPermissions) {
+                        startForCheckPermissions = false;
+                        Toast.makeText(this, "打设置相关权限", 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("handFree");
+            audioContainer.addView(outgoingLayout);
+            String str= (String) SPUtils.get(MultiAudioCallActivity.this,"ICallScrollView","");
+
+            FrameLayout controller = (FrameLayout) audioContainer.findViewById(R.id.rc_voip_control_layout);
+            controller.addView(outgoingController);
+            callSession = RongCallClient.getInstance().getCallSession();
+            if (callSession == null) {
+                setShouldShowFloat(false);
+                finish();
+                return;
+            }
+            List<CallUserProfile> 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())) {
+                    if (item.getCallStatus().equals(RongCallCommon.CallStatus.CONNECTED))
+                        memberContainer.addChild(item.getUserId(), RongContext.getInstance().getUserInfoFromCache(item.getUserId()));
+                    else {
+                        String state = getString(R.string.rc_voip_call_connecting);
+                        memberContainer.addChild(item.getUserId(), RongContext.getInstance().getUserInfoFromCache(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<String> invitedList = new ArrayList<>();
+
+        if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) {
+            callSession = intent.getParcelableExtra("callSession");
+            TextView name = (TextView) incomingLayout.findViewById(R.id.rc_user_name);
+            AsyncImageView userPortrait = (AsyncImageView) incomingLayout.findViewById(R.id.rc_voip_user_portrait);
+            UserInfo userInfo = RongContext.getInstance().getUserInfoFromCache(callSession.getCallerUserId());
+            if (userInfo != null && userInfo.getName() != null)
+                name.setText(userInfo.getName());
+            else
+                name.setText(callSession.getCallerUserId());
+            if (userInfo != null && userInfo.getPortraitUri() != null) {
+                userPortrait.setAvatar(userInfo.getPortraitUri());
+                userPortrait.setVisibility(View.VISIBLE);
+            }
+
+            name.setTag(callSession.getCallerUserId() + "callerName");
+            audioContainer.addView(incomingLayout);
+            memberContainer = (CallUserGridView) audioContainer.findViewById(R.id.rc_voip_members_container_gridView);
+            SPUtils.put(MultiAudioCallActivity.this,"ICallScrollView","CallUserGridView");
+
+            memberContainer.setChildPortraitSize(memberContainer.dip2pix(55));
+            List<CallUserProfile> list = callSession.getParticipantProfileList();
+            for (CallUserProfile profile : list) {
+                if (!profile.getUserId().equals(callSession.getCallerUserId())) {
+                    invitedList.add(profile.getUserId());
+                    userInfo = RongContext.getInstance().getUserInfoFromCache(profile.getUserId());
+                    memberContainer.addChild(profile.getUserId(), userInfo);
+                }
+            }
+            FrameLayout controller = (FrameLayout) audioContainer.findViewById(R.id.rc_voip_control_layout);
+            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();
+        } 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<String> userIds = intent.getStringArrayListExtra("invitedUsers");
+            ArrayList<String> 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.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(false);
+            for (int i = 0; i < userIds.size(); i++) {
+                if (!userIds.get(i).equals(RongIMClient.getInstance().getCurrentUserId())) {
+                    invitedList.add(userIds.get(i));
+                    UserInfo userInfo = RongContext.getInstance().getUserInfoFromCache(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);
+            }
+            RongCallClient.getInstance().startCall(conversationType, targetId, invitedList, observers, RongCallCommon.CallMediaType.AUDIO, "multi");
+        }
+        memberContainer.setScrollViewOverScrollMode(View.OVER_SCROLL_NEVER);
+        createPowerManager();
+        createPickupDetector();
+
+        if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) {
+            regisHeadsetPlugReceiver();
+            if(BluetoothUtil.hasBluetoothA2dpConnected() || BluetoothUtil.isWiredHeadsetOn(MultiAudioCallActivity.this)){
+                HeadsetInfo headsetInfo=new HeadsetInfo(true,HeadsetInfo.HeadsetType.BluetoothA2dp);
+                onEventMainThread(headsetInfo);
+            }
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        if (pickupDetector != null) {
+            pickupDetector.unRegister();
+        }
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        if (pickupDetector == null) createPickupDetector();
+        if (wakeLock == null) createPowerManager();
+        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<String> newMemberIds) {
+        if (newMemberIds == null || newMemberIds.isEmpty()) {
+            return;
+        }
+        ArrayList<String> added = new ArrayList<>();
+        List<String> participants = new ArrayList<>();
+        List<CallUserProfile> 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;
+        onOutgoingCallRinging();
+
+        regisHeadsetPlugReceiver();
+        if(BluetoothUtil.hasBluetoothA2dpConnected() || BluetoothUtil.isWiredHeadsetOn(this)){
+            HeadsetInfo headsetInfo=new HeadsetInfo(true,HeadsetInfo.HeadsetType.BluetoothA2dp);
+            onEventMainThread(headsetInfo);
+        }
+    }
+
+    @Override
+    public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) {
+        super.onRemoteUserInvited(userId, mediaType);
+        memberContainer.addChild(userId, RongContext.getInstance().getUserInfoFromCache(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, RongContext.getInstance().getUserInfoFromCache(userId));
+        }
+    }
+
+    @Override
+    public void onRemoteUserLeft(final String userId, RongCallCommon.CallDisconnectedReason reason) {
+        String text = null;
+        switch (reason) {
+            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 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);
+    }
+
+    /**
+     * 已建立通话。
+     * 通话接通时,通过回调 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 = RongContext.getInstance().getUserInfoFromCache(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) {
+                RongCallClient.getInstance().setEnableLocalAudio(v.isSelected());
+                v.setSelected(!v.isSelected());
+            }
+        });
+
+        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) {
+                RongCallClient.getInstance().setEnableSpeakerphone(!v.isSelected());
+                v.setSelected(!v.isSelected());
+            }
+        });
+
+        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)) {
+                    RongIMClient.getInstance().getDiscussion(callSession.getTargetId(), new RongIMClient.ResultCallback<Discussion>() {
+                        @Override
+                        public void onSuccess(Discussion discussion) {
+                            Intent intent = new Intent(MultiAudioCallActivity.this, CallSelectMemberActivity.class);
+                            ArrayList<String> added = new ArrayList<String>();
+                            List<CallUserProfile> list = RongCallClient.getInstance().getCallSession().getParticipantProfileList();
+                            for (CallUserProfile profile : list) {
+                                added.add(profile.getUserId());
+                            }
+                            ArrayList<String> allObserver= (ArrayList<String>) RongCallClient.getInstance().getCallSession().getObserverUserList();
+                            intent.putStringArrayListExtra("allObserver",allObserver);
+                            intent.putStringArrayListExtra("allMembers", (ArrayList<String>) 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(RongIMClient.ErrorCode e) {
+
+                        }
+                    });
+                } else if (callSession.getConversationType().equals(Conversation.ConversationType.GROUP)) {
+                    Intent intent = new Intent(MultiAudioCallActivity.this, CallSelectMemberActivity.class);
+                    ArrayList<String> added = new ArrayList<>();
+                    List<CallUserProfile> list = RongCallClient.getInstance().getCallSession().getParticipantProfileList();
+                    for (CallUserProfile profile : list) {
+                        added.add(profile.getUserId());
+                    }
+                    ArrayList<String> allObserver= (ArrayList<String>) RongCallClient.getInstance().getCallSession().getObserverUserList();
+                    intent.putStringArrayListExtra("allObserver",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<String> added = new ArrayList<>();
+                    List<CallUserProfile> list = RongCallClient.getInstance().getCallSession().getParticipantProfileList();
+                    for (CallUserProfile profile : list) {
+                        added.add(profile.getUserId());
+                    }
+                    addMember(added);
+                }
+            }
+        });
+
+        View minimizeV = outgoingLayout.findViewById(R.id.rc_voip_minimize);
+        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);
+            }
+        });
+
+
+
+        AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
+        if (audioManager.isWiredHeadsetOn() || BluetoothUtil.hasBluetoothA2dpConnected()) {
+            handFree=false;
+            RongCallClient.getInstance().setEnableSpeakerphone(false);
+            View 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);
+            }
+        } else {
+            RongCallClient.getInstance().setEnableSpeakerphone(handFree);
+            View handFreeV = outgoingLayout.findViewById(R.id.rc_voip_handfree_btn);
+            if (handFreeV != null) {
+                handFreeV.setSelected(handFree);
+            }
+        }
+        stopRing();
+    }
+
+    @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(RongIMClient.MediaType.AUDIO);
+        long serverTime = System.currentTimeMillis() - RongIMClient.getInstance().getDeltaTime();
+        RongIM.getInstance().insertMessage(callSession.getConversationType(), callSession.getTargetId(), callSession.getCallerUserId(), multiCallEndMessage, serverTime, null);
+        stopRing();
+        postRunnableDelay(new Runnable() {
+            @Override
+            public void run() {
+                finish();
+            }
+        });
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS) {
+            if (PermissionCheckUtil.checkPermissions(this, AUDIO_CALL_PERMISSIONS)) {
+                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 (callSession.getEndTime() != 0) {
+                finish();
+                return;
+            }
+            shouldShowFloat = true;
+            if (resultCode == RESULT_OK) {
+                ArrayList<String> invited = data.getStringArrayListExtra("invited");
+                ArrayList<String> observers = data.getStringArrayListExtra("observers");
+                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<String> invited = data.getStringArrayListExtra("pickedIds");
+                    RongCallClient.getInstance().addParticipants(callSession.getCallId(), invited,null);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (wakeLock != null && wakeLock.isHeld()) {
+            wakeLock.setReferenceCounted(false);
+            wakeLock.release();
+        }
+        super.onDestroy();
+    }
+
+    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());
+    }
+
+    @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("handFree", handFree);
+        }
+        return intentAction;
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (callSession == null) {
+            callSession = RongCallClient.getInstance().getCallSession();
+            if (callSession == null) {
+                super.onBackPressed();
+                return;
+            }
+        }
+        List<CallUserProfile> 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());
+        }
+    }
+
+    public void onMinimizeClick(View view) {
+        super.onMinimizeClick(view);
+    }
+
+    public void onEventMainThread(UserInfo userInfo) {
+        if (isFinishing()) {
+            return;
+        }
+        TextView callerName = (TextView) audioContainer.findViewWithTag(userInfo.getUserId() + "callerName");
+        if (callerName != null && userInfo.getName() != null)
+            callerName.setText(userInfo.getName());
+        if (memberContainer != null && memberContainer.findChildById(userInfo.getUserId()) != null) {
+            memberContainer.updateChildInfo(userInfo.getUserId(), userInfo);
+        }
+    }
+
+    public void onEventMainThread(HeadsetInfo headsetInfo) {
+        if(headsetInfo==null || !BluetoothUtil.isForground(MultiAudioCallActivity.this)){
+            FinLog.i("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(false);
+                ImageView handFreeV=null;
+                if(null!=outgoingLayout){
+                    handFreeV = outgoingLayout.findViewById(R.id.rc_voip_handfree_btn);
+                }
+                if (handFreeV != null) {
+                    handFreeV.setSelected(false);
+                    handFreeV.setEnabled(true);
+                    handFreeV.setClickable(true);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.i("bugtags","MultiAudioCallActivity->onEventMainThread Error="+e.getMessage());
+        }
+    }
+}

+ 97 - 0
im/CallKit/src/main/java/io/rong/callkit/MultiCallEndMessageProvider.java

@@ -0,0 +1,97 @@
+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.model.ProviderTag;
+import io.rong.imkit.model.UIMessage;
+import io.rong.imkit.widget.provider.IContainerItemProvider;
+import io.rong.imlib.RongIMClient;
+
+@ProviderTag(
+        messageContent = MultiCallEndMessage.class,
+        showPortrait = false,
+        showProgress = false,
+        showWarning = false,
+        centerInHorizontal = true,
+        showSummaryWithName = false
+)
+public class MultiCallEndMessageProvider extends IContainerItemProvider.MessageProvider<MultiCallEndMessage> {
+
+    protected static class ViewHolder {
+        public TextView textView;
+    }
+    @Override
+    public View newView(Context context, ViewGroup group) {
+        View v = LayoutInflater.from(context)
+                .inflate(R.layout.rc_voip_msg_multi_call_end, null);
+        ViewHolder holder = new ViewHolder();
+        holder.textView = v.findViewById(R.id.rc_msg);
+        v.setTag(holder);
+        return v;
+    }
+
+    @Override
+    public void bindView(View v, int position, MultiCallEndMessage content, UIMessage message) {
+        Context context = v.getContext();
+        String msg = "";
+
+        if (content.getReason() == RongCallCommon.CallDisconnectedReason.REMOTE_NO_RESPONSE) {
+            if (content.getMediaType() == RongIMClient.MediaType.AUDIO) {
+                msg = context.getResources().getString(R.string.rc_voip_audio_no_response);
+            } else if (content.getMediaType() == RongIMClient.MediaType.VIDEO) {
+                msg = context.getResources().getString(R.string.rc_voip_video_no_response);
+            }
+        } else {
+            if (content.getMediaType() == RongIMClient.MediaType.AUDIO) {
+                msg = context.getResources().getString(R.string.rc_voip_audio_ended);
+            } else if (content.getMediaType() == RongIMClient.MediaType.VIDEO) {
+                msg = context.getResources().getString(R.string.rc_voip_video_ended);
+            }
+        }
+        ViewHolder holder = (ViewHolder) v.getTag();
+        holder.textView.setText(msg);
+    }
+
+    @Override
+    public Spannable getContentSummary(Context context, MultiCallEndMessage message) {
+        String msg = "";
+
+        if (message.getReason() == RongCallCommon.CallDisconnectedReason.NO_RESPONSE) {
+            if (message.getMediaType() == RongIMClient.MediaType.AUDIO) {
+                msg = context.getResources().getString(R.string.rc_voip_audio_no_response);
+            } else if (message.getMediaType() == RongIMClient.MediaType.VIDEO) {
+                msg = context.getResources().getString(R.string.rc_voip_video_no_response);
+            }
+        } else {
+            if (message.getMediaType() == RongIMClient.MediaType.AUDIO) {
+                msg = context.getResources().getString(R.string.rc_voip_audio_ended);
+            } else if (message.getMediaType() == RongIMClient.MediaType.VIDEO) {
+                msg = context.getResources().getString(R.string.rc_voip_video_ended);
+            }
+        }
+        return new SpannableString(msg);
+    }
+
+    @Override
+    public Spannable getContentSummary(MultiCallEndMessage data) {
+        return null;
+    }
+
+    @Override
+    public void onItemClick(View view, int position, MultiCallEndMessage content, UIMessage message) {
+
+    }
+
+    @Override
+    public void onItemLongClick(View view, int position, MultiCallEndMessage content, UIMessage message) {
+
+    }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1470 - 0
im/CallKit/src/main/java/io/rong/callkit/MultiVideoCallActivity.java


+ 65 - 0
im/CallKit/src/main/java/io/rong/callkit/PickupDetector.java

@@ -0,0 +1,65 @@
+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);
+    }
+}

+ 26 - 0
im/CallKit/src/main/java/io/rong/callkit/RongCallAction.java

@@ -0,0 +1,26 @@
+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;
+    }
+}

+ 26 - 0
im/CallKit/src/main/java/io/rong/callkit/RongCallCustomerHandlerListener.java

@@ -0,0 +1,26 @@
+package io.rong.callkit;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.SurfaceView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.rong.calllib.RongCallCommon;
+import io.rong.calllib.RongCallSession;
+
+public interface RongCallCustomerHandlerListener {
+
+    List<String> handleActivityResult(int requestCode, int resultCode, Intent data);
+
+    void addMember(Context context, ArrayList<String> currentMemberIds);
+
+    void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType);
+
+    void onCallConnected(RongCallSession callSession, SurfaceView localVideo);
+
+    void onCallDisconnected(RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason);
+
+}

+ 289 - 0
im/CallKit/src/main/java/io/rong/callkit/RongCallKit.java

@@ -0,0 +1,289 @@
+package io.rong.callkit;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+import io.rong.calllib.RongCallClient;
+import io.rong.calllib.RongCallCommon;
+import io.rong.calllib.RongCallSession;
+import io.rong.imkit.utilities.PermissionCheckUtil;
+import io.rong.imlib.RongIMClient;
+import io.rong.imlib.model.Conversation;
+
+public class RongCallKit {
+
+    public enum CallMediaType {
+        CALL_MEDIA_TYPE_AUDIO, CALL_MEDIA_TYPE_VIDEO
+    }
+
+    public interface ICallUsersProvider {
+        void onGotUserList(ArrayList<String> userIds);
+    }
+
+    private static GroupMembersProvider mGroupMembersProvider;
+
+    private static RongCallCustomerHandlerListener customerHandlerListener;
+
+    /**
+     * 发起单人通话。
+     *
+     * @param context   上下文
+     * @param targetId  会话 id
+     * @param mediaType 会话媒体类型
+     */
+    public static void startSingleCall(Context context, String targetId, CallMediaType mediaType) {
+        if (checkEnvironment(context, mediaType)) {
+            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("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName());
+            intent.setPackage(context.getPackageName());
+            context.startActivity(intent);
+        }
+    }
+
+    /**
+     * 发起多人通话
+     *
+     * @param context          上下文
+     * @param conversationType 会话类型
+     * @param targetId         会话 id
+     * @param mediaType        会话媒体类型
+     * @param userIds          参与者 id 列表
+     */
+    public static void startMultiCall(Context context, Conversation.ConversationType conversationType, String targetId, CallMediaType mediaType, ArrayList<String> 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);
+            context.startActivity(intent);
+        }
+    }
+
+
+    /**
+     * 开始多人通话。
+     * 返回当前会话用户列表提供者对象,用户拿到该对象后,异步从服务器取出当前会话用户列表后,
+     * 调用提供者中的 onGotUserList 方法,填充 ArrayList<String> userIds 后,就会自动发起多人通话。
+     *
+     * @param context          上下文
+     * @param conversationType 会话类型
+     * @param targetId         会话 id
+     * @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<String> 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);
+                context.startActivity(intent);
+            }
+        };
+    }
+
+    /**
+     *  发起的多人通话,不依赖群、讨论组等
+     * @param context
+     * @param userIds 邀请的成员
+     * @param oberverIds 邀请的以观察者身份加入房间的成员
+     * @param mediaType
+     */
+    public static void startMultiCall(final Context context, ArrayList<String> userIds, ArrayList<String> 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());
+        context.startActivity(intent);
+    }
+
+    /**
+     * 发起的多人通话,不依赖群、讨论组等
+     * <p>
+     * <a href="http://support.rongcloud.cn/kb/Njcy">如何实现不基于于群组的voip</a>
+     *
+     * @param context
+     * @param mediaType
+     * @return
+     */
+    public static void startMultiCall(final Context context, ArrayList<String> 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());
+        context.startActivity(intent);
+    }
+
+    /**
+     * 检查应用音视频授权信息
+     * 检查网络连接状态
+     * 检查是否在通话中
+     *
+     * @param context   启动的 activity
+     * @param mediaType 启动音视频的媒体类型
+     * @return 是否允许启动通话界面
+     */
+    private static boolean checkEnvironment(Context context, CallMediaType mediaType) {
+        if (context instanceof Activity) {
+            String[] permissions;
+            if (mediaType.equals(CallMediaType.CALL_MEDIA_TYPE_AUDIO)) {
+                permissions = new String[]{Manifest.permission.RECORD_AUDIO};
+            } else {
+                permissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
+            }
+            if (!PermissionCheckUtil.requestPermissions((Activity) context, permissions)) {
+                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.getActiveTime() > 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<String> getMemberList(String groupId, OnGroupMembersResult result);
+    }
+
+    /**
+     * 群组成员提供者的异步回调接口。
+     */
+    public interface OnGroupMembersResult {
+        /**
+         * 群组成员提供者的异步回调接口。
+         *
+         * @param members 成员列表。
+         */
+        void onGotMemberList(ArrayList<String> members);
+    }
+
+    /**
+     * <p>设置群组成员的提供者。</p>
+     * <p>设置后,当 {@link CallSelectMemberActivity} 界面展示群组成员时,会回调 {@link GroupMembersProvider#getMemberList(String, OnGroupMembersResult)},
+     * 使用者只需要根据对应的 groupId 提供对应的群组成员。
+     * 如果需要异步从服务器获取群组成员,使用者可以在此方法中发起异步请求,然后返回 null 信息。
+     * 在异步请求结果返回后,根据返回的结果调用 {@link OnGroupMembersResult#onGotMemberList(ArrayList)}  刷新信息。</p>
+     *
+     * @param groupMembersProvider 群组成员提供者。
+     */
+    public static void setGroupMemberProvider(GroupMembersProvider groupMembersProvider) {
+        mGroupMembersProvider = groupMembersProvider;
+    }
+
+    /**
+     * 获取群组成员提供者。
+     *
+     * @return 群组成员提供者。
+     */
+    public static GroupMembersProvider getGroupMemberProvider() {
+        return mGroupMembersProvider;
+    }
+
+    /**
+     * <p>设置通话时用户自定义操作监听。</p>
+     * <p>CallKit中的Activity是通过action隐式启动,如果用户想继承现有的Activity自定义操作,子类Activity在
+     * AndroidManifest.xml声明后启动该Activity时会弹出提示框让用户选择,这个问题解决方式开发者可以直接把
+     * callKit/AndroidManifest.xml中对应的Activity声明去掉,此Listener提供了另一种实现方案,
+     * RongCallCustomerHandlerListener中并没有定义很多方法,开发者如果需要,可以新增自己的方法</p>
+     */
+    public static void setCustomerHandlerListener(RongCallCustomerHandlerListener callCustomerHandlerListener) {
+        customerHandlerListener = callCustomerHandlerListener;
+    }
+
+    /**
+     * 通话过程中用户自定义操作。
+     */
+    public static RongCallCustomerHandlerListener getCustomerHandlerListener() {
+        return customerHandlerListener;
+    }
+}

+ 165 - 0
im/CallKit/src/main/java/io/rong/callkit/RongCallModule.java

@@ -0,0 +1,165 @@
+package io.rong.callkit;
+
+import android.content.Context;
+import android.content.Intent;
+
+import com.bailingcloud.bailingvideo.engine.binstack.util.FinLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.rong.calllib.IRongReceivedCallListener;
+import io.rong.calllib.RongCallClient;
+import io.rong.calllib.RongCallCommon;
+import io.rong.calllib.RongCallSession;
+import io.rong.common.RLog;
+import io.rong.imkit.RongIM;
+import io.rong.imkit.manager.IExternalModule;
+import io.rong.imkit.plugin.IPluginModule;
+import io.rong.imlib.model.Conversation;
+
+/**
+ * Created by weiqinxiao on 16/8/15.
+ */
+public class RongCallModule implements IExternalModule {
+    private final static String TAG = "RongCallModule";
+
+    private RongCallSession mCallSession;
+    private boolean mViewLoaded;
+    private Context mContext;
+
+    public RongCallModule() {
+        RLog.i(TAG, "Constructor");
+    }
+
+    @Override
+    public void onInitialized(String appKey) {
+        RongIM.registerMessageTemplate(new CallEndMessageItemProvider());
+        RongIM.registerMessageTemplate(new MultiCallEndMessageProvider());
+    }
+
+    @Override
+    public void onConnected(String token) {
+        RongCallClient.getInstance().setVoIPCallListener(RongCallProxy.getInstance());
+        // 开启音视频日志,如果不需要开启,则去掉下面这句。
+        RongCallClient.getInstance().setEnablePrintLog(true);
+        RongCallClient.getInstance().setVideoProfile(RongCallCommon.CallVideoProfile.VIDEO_PROFILE_480P);
+    }
+
+    @Override
+    public void onCreate(final Context context) {
+        mContext = context;
+        IRongReceivedCallListener callListener = new IRongReceivedCallListener() {
+            @Override
+            public void onReceivedCall(final RongCallSession callSession) {
+                FinLog.d("VoIPReceiver", "onReceivedCall");
+                if (mViewLoaded) {
+                    FinLog.d("VoIPReceiver", "onReceivedCall->onCreate->mViewLoaded=true");
+                    startVoIPActivity(mContext, callSession, false);
+                } else {
+                    FinLog.d("VoIPReceiver", "onReceivedCall->onCreate->mViewLoaded=false");
+                    mCallSession = callSession;
+                }
+            }
+
+            @Override
+            public void onCheckPermission(RongCallSession callSession) {
+                FinLog.d("VoIPReceiver", "onCheckPermissions");
+                mCallSession = callSession;
+                if (mViewLoaded) {
+                    startVoIPActivity(mContext, callSession, true);
+                }
+            }
+        };
+
+        RongCallClient.setReceivedCallListener(callListener);
+    }
+
+    /**
+     * 此方法的目的是,防止 voip 通话界面被会话或者会话列表界面覆盖。
+     * 所有要等待会话或者会话列表加载出后,再显示voip 通话界面。
+     * <p>
+     * 当会话列表或者会话界面加载出来后,此方法会被回调。
+     * 如果开发者没有会话或者会话列表界面,只需要将下面的 mViewLoaded 在 onCreate 时设置为 true 即可。
+     */
+    @Override
+    public void onViewCreated() {
+        mViewLoaded = true;
+        if (mCallSession != null) {
+            startVoIPActivity(mContext, mCallSession, false);
+        }
+    }
+
+    @Override
+    public List<IPluginModule> getPlugins(Conversation.ConversationType conversationType) {
+        List<IPluginModule> 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 void onDisconnected() {
+
+    }
+
+    /**
+     * 启动通话界面
+     *
+     * @param context                  上下文
+     * @param callSession              通话实体
+     * @param startForCheckPermissions android6.0需要实时获取应用权限。
+     *                                 当需要实时获取权限时,设置startForCheckPermissions为true,
+     *                                 其它情况下设置为false。
+     */
+    private void startVoIPActivity(Context context, final RongCallSession callSession, boolean startForCheckPermissions) {
+        RLog.d("VoIPReceiver", "startVoIPActivity");
+        FinLog.d("VoIPReceiver", "startVoIPActivity");
+        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 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());
+            context.startActivity(intent);
+        } else {
+            if (callSession.getMediaType().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.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());
+            context.startActivity(intent);
+        }
+        mCallSession = null;
+    }
+}

+ 177 - 0
im/CallKit/src/main/java/io/rong/callkit/RongCallProxy.java

@@ -0,0 +1,177 @@
+package io.rong.callkit;
+
+import android.view.SurfaceView;
+
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import io.rong.calllib.IRongCallListener;
+import io.rong.calllib.RongCallCommon;
+import io.rong.calllib.RongCallSession;
+import io.rong.common.RLog;
+
+/**
+ * Created by jiangecho on 2016/10/27.
+ */
+
+public class RongCallProxy implements IRongCallListener {
+
+    private static final String TAG = "RongCallProxy";
+    private IRongCallListener mCallListener;
+    private Queue<CallDisconnectedInfo> 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 onCallOutgoing(RongCallSession callSession, SurfaceView localVideo) {
+        if (mCallListener != null) {
+            mCallListener.onCallOutgoing(callSession, localVideo);
+        }
+    }
+
+    @Override
+    public void onCallConnected(RongCallSession callSession, SurfaceView localVideo) {
+        if (mCallListener != null) {
+            mCallListener.onCallConnected(callSession, localVideo);
+        }
+    }
+
+    @Override
+    public void onCallDisconnected(RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) {
+        RLog.d(TAG, "RongCallProxy onCallDisconnected mCallListener = " + mCallListener);
+        if (mCallListener != null) {
+            mCallListener.onCallDisconnected(callSession, reason);
+        } else {
+            mCachedCallQueue.offer(new CallDisconnectedInfo(callSession, reason));
+        }
+    }
+
+    @Override
+    public void onRemoteUserRinging(String userId) {
+        if (mCallListener != null) {
+            mCallListener.onRemoteUserRinging(userId);
+        }
+    }
+
+    @Override
+    public void onRemoteUserJoined(String userId, RongCallCommon.CallMediaType mediaType, int userType, SurfaceView remoteVideo) {
+        if (mCallListener != null) {
+            mCallListener.onRemoteUserJoined(userId, mediaType, userType, remoteVideo);
+        }
+    }
+
+    @Override
+    public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) {
+        if (mCallListener != null) {
+            mCallListener.onRemoteUserInvited(userId, mediaType);
+        }
+    }
+
+    @Override
+    public void onRemoteUserLeft(String userId, RongCallCommon.CallDisconnectedReason reason) {
+        if (mCallListener != null) {
+            mCallListener.onRemoteUserLeft(userId, reason);
+        }
+    }
+
+    @Override
+    public void onMediaTypeChanged(String userId, RongCallCommon.CallMediaType mediaType, SurfaceView video) {
+        if (mCallListener != null) {
+            mCallListener.onMediaTypeChanged(userId, mediaType, video);
+        }
+    }
+
+    @Override
+    public void onError(RongCallCommon.CallErrorCode errorCode) {
+        if (mCallListener != null) {
+            mCallListener.onError(errorCode);
+        }
+    }
+
+    @Override
+    public void onRemoteCameraDisabled(String userId, boolean disabled) {
+        if (mCallListener != null) {
+            mCallListener.onRemoteCameraDisabled(userId, disabled);
+        }
+    }
+
+    @Override
+    public void onWhiteBoardURL(String url) {
+        if (mCallListener != null) {
+            mCallListener.onWhiteBoardURL(url);
+        }
+    }
+
+    @Override
+    public void onNetWorkLossRate(int lossRate) {
+        if (mCallListener != null) {
+            mCallListener.onNetWorkLossRate(lossRate);
+        }
+    }
+
+    @Override
+    public void onNotifySharingScreen(String userId, boolean isSharing) {
+        if (mCallListener != null) {
+            mCallListener.onNotifySharingScreen(userId, isSharing);
+        }
+    }
+
+    @Override
+    public void onNotifyDegradeNormalUserToObserver(String userId) {
+        if (mCallListener != null) {
+            mCallListener.onNotifyDegradeNormalUserToObserver(userId);
+        }
+    }
+
+    @Override
+    public void onNotifyAnswerObserverRequestBecomeNormalUser(String userId, long status) {
+        if (mCallListener != null) {
+            mCallListener.onNotifyAnswerObserverRequestBecomeNormalUser(userId, status);
+        }
+    }
+
+    @Override
+    public void onNotifyUpgradeObserverToNormalUser() {
+        if (mCallListener != null) {
+            mCallListener.onNotifyUpgradeObserverToNormalUser();
+        }
+    }
+
+    @Override
+    public void onNotifyHostControlUserDevice(String userId, int dType, int isOpen) {
+        if (mCallListener != null) {
+            mCallListener.onNotifyHostControlUserDevice(userId, dType, isOpen);
+        }
+    }
+
+    private static class CallDisconnectedInfo {
+        RongCallSession mCallSession;
+        RongCallCommon.CallDisconnectedReason mReason;
+
+        public CallDisconnectedInfo(RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) {
+            this.mCallSession = callSession;
+            this.mReason = reason;
+        }
+    }
+}

+ 16 - 0
im/CallKit/src/main/java/io/rong/callkit/RongVoIPIntent.java

@@ -0,0 +1,16 @@
+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";
+}

+ 925 - 0
im/CallKit/src/main/java/io/rong/callkit/SingleCallActivity.java

@@ -0,0 +1,925 @@
+package io.rong.callkit;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+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 com.bailingcloud.bailingvideo.engine.binstack.util.FinLog;
+import com.zhaohaoting.framework.utils.glid.GlideUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import io.rong.callkit.util.BluetoothUtil;
+import io.rong.callkit.util.CallKitUtils;
+import io.rong.callkit.util.HeadsetInfo;
+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.CallSTerminateMessage;
+import io.rong.common.RLog;
+import io.rong.imkit.RongContext;
+import io.rong.imkit.RongIM;
+import io.rong.imkit.utilities.PermissionCheckUtil;
+import io.rong.imkit.widget.AsyncImageView;
+import io.rong.imlib.RongIMClient;
+import io.rong.imlib.model.Conversation;
+import io.rong.imlib.model.UserInfo;
+
+public class SingleCallActivity extends BaseCallActivity implements Handler.Callback {
+    private static final String TAG = "VoIPSingleActivity";
+    private LayoutInflater inflater;
+    private RongCallSession callSession;
+    private FrameLayout mLPreviewContainer;
+    private FrameLayout mSPreviewContainer;
+    private FrameLayout mButtonContainer;
+    private LinearLayout mUserInfoContainer;
+    private Boolean isInformationShow = false;
+    private SurfaceView mLocalVideo = null;
+    private boolean muted = false;
+    private boolean handFree = false;
+    private boolean startForCheckPermissions = false;
+
+    private int EVENT_FULL_SCREEN = 1;
+
+    private String targetId = null;
+    private RongCallCommon.CallMediaType mediaType;
+
+    @Override
+    final public 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);
+        Log.i("AudioPlugin", "savedInstanceState != null=" + (savedInstanceState != null) + ",,,RongCallClient.getInstance() == null" + (RongCallClient.getInstance() == null));
+        if (savedInstanceState != null && RongCallClient.getInstance() == null) {
+            // 音视频请求权限时,用户在设置页面取消权限,导致应用重启,退出当前activity.
+            Log.i("AudioPlugin", "音视频请求权限时,用户在设置页面取消权限,导致应用重启,退出当前activity");
+            finish();
+            return;
+        }
+        Intent intent = getIntent();
+        mLPreviewContainer = (FrameLayout) 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);
+
+        startForCheckPermissions = intent.getBooleanExtra("checkPermissions", false);
+        RongCallAction callAction = RongCallAction.valueOf(intent.getStringExtra("callAction"));
+
+        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();
+            if (callSession != null) {
+                mediaType = callSession.getMediaType();
+            }
+        }
+        if (mediaType != null) {
+            inflater = LayoutInflater.from(this);
+            initView(mediaType, callAction);
+
+            if (requestCallPermissions(mediaType, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)) {
+                setupIntent();
+            }
+        } else {
+            RLog.w(TAG, "恢复的瞬间,对方已挂断");
+            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:
+                boolean permissionGranted;
+                if (mediaType == RongCallCommon.CallMediaType.AUDIO) {
+                    permissionGranted = PermissionCheckUtil.checkPermissions(this, AUDIO_CALL_PERMISSIONS);
+                } else {
+                    permissionGranted = PermissionCheckUtil.checkPermissions(this, VIDEO_CALL_PERMISSIONS);
+
+                }
+                if (permissionGranted) {
+                    if (startForCheckPermissions) {
+                        startForCheckPermissions = false;
+                        RongCallClient.getInstance().onPermissionGranted();
+                    } else {
+                        setupIntent();
+                    }
+                } else {
+                    Toast.makeText(this, getString(R.string.rc_permission_grant_needed), Toast.LENGTH_SHORT).show();
+                    if (startForCheckPermissions) {
+                        startForCheckPermissions = false;
+                        RongCallClient.getInstance().onPermissionDenied();
+                    } else {
+                        Log.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) {
+
+            String[] permissions;
+            if (mediaType == RongCallCommon.CallMediaType.AUDIO) {
+                permissions = AUDIO_CALL_PERMISSIONS;
+            } else {
+                permissions = VIDEO_CALL_PERMISSIONS;
+            }
+            if (PermissionCheckUtil.checkPermissions(this, permissions)) {
+                if (startForCheckPermissions) {
+                    RongCallClient.getInstance().onPermissionGranted();
+                } else {
+                    setupIntent();
+                }
+            } else {
+                if (startForCheckPermissions) {
+                    RongCallClient.getInstance().onPermissionDenied();
+                } else {
+                    Log.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();
+        } 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");
+
+            List<String> userIds = new ArrayList<>();
+            userIds.add(targetId);
+            RongCallClient.getInstance().startCall(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 = RongContext.getInstance().getUserInfoFromCache(targetId);
+        if (userInfo != null) {
+            if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO) || callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) {
+                AsyncImageView userPortrait = (AsyncImageView) mUserInfoContainer.findViewById(R.id.rc_voip_user_portrait);
+                if (userPortrait != null && userInfo.getPortraitUri() != null) {
+                    userPortrait.setResource(userInfo.getPortraitUri().toString(), R.drawable.rc_default_portrait);
+                }
+                TextView userName = (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name);
+                if (userName != null) {
+                    userName.setText(userInfo.getName());
+                }
+            }
+        }
+        if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL) && userInfo != null) {
+            ImageView iv_icoming_backgroud = (ImageView) mUserInfoContainer.findViewById(R.id.iv_icoming_backgroud);
+            iv_icoming_backgroud.setVisibility(View.VISIBLE);
+            GlideUtils.loadImageBlurTransformation(SingleCallActivity.this, null != userInfo ? userInfo.getPortraitUri().toString() : null, iv_icoming_backgroud);
+        }
+
+        createPowerManager();
+        createPickupDetector();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        FinLog.i("AudioPlugin", "---single activity onResume---");
+        if (pickupDetector != null && mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) {
+            pickupDetector.register(this);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.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) || callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) {
+            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);
+                userInfoLayout.findViewById(R.id.rc_voip_call_minimize).setVisibility(View.VISIBLE);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        if (callAction.equals(RongCallAction.ACTION_OUTGOING_CALL)) {
+            userInfoLayout.findViewById(R.id.rc_voip_call_minimize).setVisibility(View.VISIBLE);
+            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(false);
+            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();
+            }
+        } else if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) {
+            if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) {
+                findViewById(R.id.rc_voip_call_information).setBackgroundColor(getResources().getColor(R.color.rc_voip_background_color));
+                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();
+            }
+        }
+        mButtonContainer.removeAllViews();
+        mButtonContainer.addView(buttonLayout);
+        mUserInfoContainer.removeAllViews();
+        mUserInfoContainer.addView(userInfoLayout);
+
+        if (callAction.equals(RongCallAction.ACTION_INCOMING_CALL)) {
+            regisHeadsetPlugReceiver();
+            if (BluetoothUtil.hasBluetoothA2dpConnected() || BluetoothUtil.isWiredHeadsetOn(SingleCallActivity.this)) {
+                HeadsetInfo headsetInfo = new HeadsetInfo(true, HeadsetInfo.HeadsetType.BluetoothA2dp);
+                onEventMainThread(headsetInfo);
+            }
+        }
+    }
+
+
+    @Override
+    public void onCallOutgoing(RongCallSession callSession, SurfaceView localVideo) {
+        super.onCallOutgoing(callSession, localVideo);
+        this.callSession = callSession;
+        try {
+            UserInfo InviterUserIdInfo = RongContext.getInstance().getUserInfoFromCache(targetId);
+            UserInfo SelfUserInfo = RongContext.getInstance().getUserInfoFromCache(callSession.getSelfUserId());
+            if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.VIDEO)) {
+                mLPreviewContainer.setVisibility(View.VISIBLE);
+                localVideo.setTag(callSession.getSelfUserId());
+                mLPreviewContainer.addView(localVideo);
+                if (null != SelfUserInfo && null != SelfUserInfo.getName()) {
+                    //单人视频
+                    TextView callkit_voip_user_name_signleVideo = (TextView) mUserInfoContainer.findViewById(R.id.callkit_voip_user_name_signleVideo);
+                    callkit_voip_user_name_signleVideo.setText(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.loadImageBlurTransformation(SingleCallActivity.this, null != InviterUserIdInfo ? InviterUserIdInfo.getPortraitUri().toString() : null, iv_icoming_backgroud);
+                    iv_icoming_backgroud.setVisibility(View.VISIBLE);
+                    mUserInfoContainer.findViewById(R.id.iv_large_preview_Mask).setVisibility(View.VISIBLE);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        onOutgoingCallRinging();
+
+        regisHeadsetPlugReceiver();
+        if (BluetoothUtil.hasBluetoothA2dpConnected() || BluetoothUtil.isWiredHeadsetOn(this)) {
+            HeadsetInfo headsetInfo = new HeadsetInfo(true, HeadsetInfo.HeadsetType.BluetoothA2dp);
+            onEventMainThread(headsetInfo);
+        }
+    }
+
+    @Override
+    public void onCallConnected(RongCallSession callSession, SurfaceView localVideo) {
+        super.onCallConnected(callSession, localVideo);
+        this.callSession = callSession;
+        FinLog.i(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);
+        } else {
+            // 二人视频通话接通后 mUserInfoContainer 中更换为无头像的布局
+            mUserInfoContainer.removeAllViews();
+            inflater.inflate(R.layout.rc_voip_video_call_user_info, mUserInfoContainer);
+            UserInfo userInfo = RongContext.getInstance().getUserInfoFromCache(targetId);
+            if (userInfo != null) {
+                TextView userName = mUserInfoContainer.findViewById(R.id.rc_voip_user_name);
+                userName.setText(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());
+        }
+        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);
+        }
+
+        AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
+        if (audioManager.isWiredHeadsetOn() || BluetoothUtil.hasBluetoothA2dpConnected()) {
+            handFree = false;
+            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);
+            }
+        } else {
+            RongCallClient.getInstance().setEnableSpeakerphone(handFree);
+            View handFreeV = mButtonContainer.findViewById(R.id.rc_voip_handfree);
+            if (handFreeV != null) {
+                handFreeV.setSelected(handFree);
+            }
+        }
+        stopRing();
+    }
+
+    @Override
+    protected void onDestroy() {
+        stopRing();
+        if (wakeLock != null && wakeLock.isHeld()) {
+            wakeLock.setReferenceCounted(false);
+            wakeLock.release();
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public void onRemoteUserJoined(final String userId, RongCallCommon.CallMediaType mediaType, int userType, SurfaceView remoteVideo) {
+        super.onRemoteUserJoined(userId, mediaType, userType, remoteVideo);
+        FinLog.i(TAG, "onRemoteUserJoined userID=" + userId + ",mediaType=" + mediaType.getValue() + ",userType=" + userId);
+        if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) {
+            findViewById(R.id.rc_voip_call_information).setBackgroundColor(getResources().getColor(android.R.color.transparent));
+            mLPreviewContainer.setVisibility(View.VISIBLE);
+            mLPreviewContainer.removeAllViews();
+            remoteVideo.setTag(userId);
+
+            FinLog.i(TAG, "onRemoteUserJoined mLPreviewContainer.addView(remoteVideo)");
+            mLPreviewContainer.addView(remoteVideo);
+            mLPreviewContainer.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (isInformationShow) {
+                        hideVideoCallInformation();
+                    } else {
+                        showVideoCallInformation();
+                        handler.sendEmptyMessageDelayed(EVENT_FULL_SCREEN, 5 * 1000);
+                    }
+                }
+            });
+            mSPreviewContainer.setVisibility(View.VISIBLE);
+            mSPreviewContainer.removeAllViews();
+            FinLog.i(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 {
+                        SurfaceView fromView = (SurfaceView) mSPreviewContainer.getChildAt(0);
+                        SurfaceView toView = (SurfaceView) mLPreviewContainer.getChildAt(0);
+
+                        mLPreviewContainer.removeAllViews();
+                        mSPreviewContainer.removeAllViews();
+                        fromView.setZOrderOnTop(false);
+                        fromView.setZOrderMediaOverlay(false);
+                        mLPreviewContainer.addView(fromView);
+                        toView.setZOrderOnTop(true);
+                        toView.setZOrderMediaOverlay(true);
+                        mSPreviewContainer.addView(toView);
+                        if (null != fromView.getTag() && !TextUtils.isEmpty(fromView.getTag().toString())) {
+                            UserInfo userInfo = RongContext.getInstance().getUserInfoFromCache(fromView.getTag().toString());
+                            TextView userName = (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name);
+                            userName.setText(userInfo.getName());
+                        }
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            mButtonContainer.setVisibility(View.GONE);
+            mUserInfoContainer.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * 当通话中的某一个参与者切换通话类型,例如由 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);
+    }
+
+    /**
+     * 视频转语音
+     **/
+    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 = RongContext.getInstance().getUserInfoFromCache(targetId);
+        if (userInfo != null) {
+            TextView userName = (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name);
+            userName.setText(userInfo.getName());
+            if (callSession.getMediaType().equals(RongCallCommon.CallMediaType.AUDIO)) {
+                AsyncImageView userPortrait = (AsyncImageView) mUserInfoContainer.findViewById(R.id.rc_voip_user_portrait);
+                if (userPortrait != null) {
+                    userPortrait.setAvatar(userInfo.getPortraitUri().toString(), R.drawable.rc_default_portrait);
+                }
+            } else {//单人视频接听layout
+                ImageView iv_large_preview = mUserInfoContainer.findViewById(R.id.iv_large_preview);
+                iv_large_preview.setVisibility(View.VISIBLE);
+                GlideUtils.loadImageBlurTransformation(SingleCallActivity.this, null != userInfo ? userInfo.getPortraitUri().toString() : null, 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.loadImageBlurTransformation(SingleCallActivity.this, null != userInfo ? userInfo.getPortraitUri().toString() : null, iv_large_preview);
+            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();
+            FinLog.e(TAG + "_挂断单人视频出错 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) {
+            FinLog.e(TAG + "_接听单人视频出错 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.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                RongCallClient.getInstance().changeCallMediaType(RongCallCommon.CallMediaType.AUDIO);
+                callSession.setMediaType(RongCallCommon.CallMediaType.AUDIO);
+                initAudioCallView();
+            }
+        });
+    }
+
+    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();
+        switch (reason) {
+            case HANGUP:
+            case REMOTE_HANGUP:
+                long time = getTime();
+                if (time >= 3600) {
+                    extra = String.format("%d:%02d:%02d", time / 3600, (time % 3600) / 60, (time % 60));
+                } else {
+                    extra = String.format("%02d:%02d", (time % 3600) / 60, (time % 60));
+                }
+                break;
+        }
+
+        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");
+                RongIM.getInstance().insertOutgoingMessage(Conversation.ConversationType.PRIVATE, callSession.getTargetId(), io.rong.imlib.model.Message.SentStatus.SENT, message, serverTime, null);
+            } else {
+                message.setDirection("MT");
+                io.rong.imlib.model.Message.ReceivedStatus receivedStatus = new io.rong.imlib.model.Message.ReceivedStatus(0);
+                receivedStatus.setRead();
+                RongIM.getInstance().insertIncomingMessage(Conversation.ConversationType.PRIVATE, callSession.getTargetId(), senderId, receivedStatus, message, serverTime, null);
+            }
+        }
+        postRunnableDelay(new Runnable() {
+            @Override
+            public void run() {
+                finish();
+            }
+        });
+    }
+
+    @Override
+    public void onRestoreFloatBox(Bundle bundle) {
+        super.onRestoreFloatBox(bundle);
+        if (bundle == null)
+            return;
+        muted = bundle.getBoolean("muted");
+        handFree = bundle.getBoolean("handFree");
+
+        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 = RongContext.getInstance().getUserInfoFromCache(targetId);
+        if (userInfo != null) {
+//            TextView userName = (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name);
+//            userName.setText(userInfo.getName());
+            if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) {
+                AsyncImageView userPortrait = (AsyncImageView) mUserInfoContainer.findViewById(R.id.rc_voip_user_portrait);
+                if (userPortrait != null) {
+                    userPortrait.setAvatar(userInfo.getPortraitUri().toString(), R.drawable.rc_default_portrait);
+                }
+            } 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);
+                    GlideUtils.loadImageBlurTransformation(SingleCallActivity.this, null != userInfo ? userInfo.getPortraitUri().toString() : null, 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 && remoteVideo.getParent() != null) {
+            ((ViewGroup) remoteVideo.getParent()).removeView(remoteVideo);
+            onRemoteUserJoined(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());
+
+        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<CallUserProfile> 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());
+//        }
+    }
+
+    public void onEventMainThread(UserInfo userInfo) {
+        if (isFinishing()) {
+            return;
+        }
+        if (targetId != null && targetId.equals(userInfo.getUserId())) {
+            TextView userName = (TextView) mUserInfoContainer.findViewById(R.id.rc_voip_user_name);
+            if (userInfo.getName() != null)
+                userName.setText(userInfo.getName());
+//            AsyncImageView userPortrait = (AsyncImageView) mUserInfoContainer.findViewById(R.id.rc_voip_user_portrait);
+//            if (userPortrait != null && userInfo.getPortraitUri() != null) {
+//                userPortrait.setResource(userInfo.getPortraitUri().toString(), R.drawable.rc_default_portrait);
+//            }
+//            ImageView iv_large_preview=mUserInfoContainer.findViewById(R.id.iv_large_preview);
+//            GlideUtils.blurTransformation(SingleCallActivity.this,iv_large_preview,null!=userInfo?userInfo.getPortraitUri():null);
+        }
+    }
+
+//    @Override
+//    public void showOnGoingNotification() {
+//        Intent intent = new Intent(getIntent().getAction());
+//        Bundle bundle = new Bundle();
+//        onSaveFloatBoxState(bundle);
+//        intent.putExtra("floatbox", bundle);
+//        intent.putExtra("callAction", RongCallAction.ACTION_RESUME_CALL.getName());
+//        PendingIntent pendingIntent = PendingIntent.getActivity(this, 1000, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+//        NotificationUtil.showNotification(this, "todo", "coontent", pendingIntent, CALL_NOTIFICATION_ID);
+//    }
+    public void onEventMainThread(HeadsetInfo headsetInfo) {
+        if (headsetInfo == null || !BluetoothUtil.isForground(SingleCallActivity.this)) {
+            FinLog.i("bugtags", "SingleCallActivity 不在前台!");
+            return;
+        }
+        FinLog.i("bugtags", "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(false);
+                ImageView handFreeV = mButtonContainer.findViewById(R.id.rc_voip_handfree_btn);
+                if (handFreeV != null) {
+                    handFreeV.setSelected(false);
+                    handFreeV.setEnabled(true);
+                    handFreeV.setClickable(true);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            FinLog.i("bugtags", "SingleCallActivity->onEventMainThread Error=" + e.getMessage());
+        }
+    }
+}

+ 158 - 0
im/CallKit/src/main/java/io/rong/callkit/VideoPlugin.java

@@ -0,0 +1,158 @@
+package io.rong.callkit;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+import io.rong.callkit.util.CallKitUtils;
+import io.rong.calllib.RongCallClient;
+import io.rong.calllib.RongCallCommon;
+import io.rong.calllib.RongCallSession;
+import io.rong.common.RLog;
+import io.rong.imkit.RongExtension;
+import io.rong.imkit.RongIM;
+import io.rong.imkit.plugin.IPluginModule;
+import io.rong.imkit.plugin.IPluginRequestPermissionResultCallback;
+import io.rong.imkit.utilities.PermissionCheckUtil;
+import io.rong.imlib.RongIMClient;
+import io.rong.imlib.model.Conversation;
+import io.rong.imlib.model.Discussion;
+
+/**
+ * Created by weiqinxiao on 16/8/16.
+ */
+public class VideoPlugin implements IPluginModule, IPluginRequestPermissionResultCallback {
+    private static final String TAG = "VideoPlugin";
+    private ArrayList<String> 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, final RongExtension extension) {
+        context = currentFragment.getActivity().getApplicationContext();
+        conversationType = extension.getConversationType();
+        targetId = extension.getTargetId();
+
+        String[] permissions = CallKitUtils.getCallpermissions();
+        if (PermissionCheckUtil.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;
+        }
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+        if (networkInfo == null || !networkInfo.isConnected() || !networkInfo.isAvailable()) {
+            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)) {
+            RongIM.getInstance().getDiscussion(targetId, new RongIMClient.ResultCallback<Discussion>() {
+                @Override
+                public void onSuccess(Discussion discussion) {
+
+                    Intent intent = new Intent(context, CallSelectMemberActivity.class);
+                    allMembers = (ArrayList<String>) discussion.getMemberIdList();
+                    intent.putStringArrayListExtra("allMembers", allMembers);
+                    String myId = RongIMClient.getInstance().getCurrentUserId();
+                    ArrayList<String> 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(RongIMClient.ErrorCode 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<String> 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;
+        }
+
+        Intent intent = new Intent(RongVoIPIntent.RONG_INTENT_ACTION_VOIP_MULTIVIDEO);
+        ArrayList<String> userIds = data.getStringArrayListExtra("invited");
+        ArrayList<String> 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) {
+        if (PermissionCheckUtil.checkPermissions(fragment.getActivity(), permissions)) {
+            startVideoActivity(extension);
+        } else {
+            extension.showRequestPermissionFailedAlter(PermissionCheckUtil.getNotGrantedPermissionMsg(context, permissions, grantResults));
+        }
+        return true;
+    }
+}

+ 292 - 0
im/CallKit/src/main/java/io/rong/callkit/util/BluetoothUtil.java

@@ -0,0 +1,292 @@
+package io.rong.callkit.util;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.bailingcloud.bailingvideo.engine.binstack.util.FinLog;
+
+import org.w3c.dom.Text;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+import io.rong.common.RLog;
+
+import static android.content.Context.AUDIO_SERVICE;
+
+/**
+ * Created by degnxudong on 2018/8/24.
+ */
+
+public class BluetoothUtil {
+
+    private final static  String TAG="BluetoothUtil";
+
+    /**
+     * 是否连接了蓝牙耳机
+     * @return
+     */
+    @SuppressLint("WrongConstant")
+    public static boolean hasBluetoothA2dpConnected(){
+        boolean bool=false;
+        BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if(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<ActivityManager.RunningTaskInfo> 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;
+    }
+}

+ 65 - 0
im/CallKit/src/main/java/io/rong/callkit/util/BlurBitmapUtil.java

@@ -0,0 +1,65 @@
+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 BlurBitmapUtil sInstance;
+
+    private BlurBitmapUtil() {}
+
+    public static BlurBitmapUtil instance() {
+        if (sInstance == null) {
+            synchronized (BlurBitmapUtil.class) {
+                if (sInstance == null) {
+                    sInstance = new BlurBitmapUtil();
+                }
+            }
+        }
+        return 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;
+    }
+}

+ 21 - 0
im/CallKit/src/main/java/io/rong/callkit/util/CallKitSearchBarListener.java

@@ -0,0 +1,21 @@
+package io.rong.callkit.util;
+
+public interface CallKitSearchBarListener {
+    /**
+     * 开始搜索
+     * EditText 中输入内容后,会触发此回调
+     * @param keyword 搜索关键字
+     */
+    void onSearchStart(String keyword);
+
+    /**
+     * 软键盘中"搜索"被点击后,触发此回调
+     * 此回调被触发后,仅收起软键盘
+     */
+    void onSoftSearchKeyClick();
+
+    /**
+     * 搜索控件中,点击"清除"后,触发此回调
+     */
+    void onClearButtonClick();
+}

+ 140 - 0
im/CallKit/src/main/java/io/rong/callkit/util/CallKitSearchBarView.java

@@ -0,0 +1,140 @@
+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);
+        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;
+    }
+}

+ 176 - 0
im/CallKit/src/main/java/io/rong/callkit/util/CallKitUtils.java

@@ -0,0 +1,176 @@
+package io.rong.callkit.util;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.v4.app.AppOpsManagerCompat;
+import android.support.v4.content.ContextCompat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.Window;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+
+import java.math.BigDecimal;
+
+import io.rong.callkit.R;
+
+/**
+ * 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;
+
+    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());
+    }
+
+    /**
+     * 关闭软键盘
+     *
+     * @param activity
+     * @param view
+     */
+    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 = new BigDecimal(vl1);
+        BigDecimal b2 = new BigDecimal(vl2);
+        //4 表示表示需要精确到小数点以后几位。当发生除不尽的情况时,参数指定精度,以后的数字四舍五入。
+        return b1.divide(b2, 4, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+    /**
+     * 四舍五入把double转化int整型
+     *
+     * @param number
+     * @return
+     */
+    public static int getInt(double number) {
+        BigDecimal bd = new BigDecimal(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.PROCESS_OUTGOING_CALLS, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.INTERNET, Manifest.permission.READ_PHONE_STATE, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN};
+        return permissions;
+    }
+
+    /**
+     * 获取字符串指定拼接内容
+     *
+     * @param val1
+     * @param val2
+     * @return
+     */
+    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();
+    }
+}

+ 226 - 0
im/CallKit/src/main/java/io/rong/callkit/util/CallVerticalScrollView.java

@@ -0,0 +1,226 @@
+package io.rong.callkit.util;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+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.ScrollView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.rong.callkit.R;
+import io.rong.imkit.widget.AsyncImageView;
+import io.rong.imlib.model.UserInfo;
+
+/**
+ * 竖向滑动
+ * 多人语音——主叫方和通话中
+ */
+public class CallVerticalScrollView extends ScrollView implements ICallScrollView{
+    private Context context;
+    private boolean enableTitle;
+    private LinearLayout linearLayout;
+    private static int CHILDREN_PER_LINE = 4;
+    private final static int CHILDREN_SPACE = 24;
+
+    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);
+    }
+
+    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));
+        }
+        AsyncImageView imageView = (AsyncImageView)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) {
+            imageView.setAvatar(userInfo.getPortraitUri());
+            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);
+    }
+
+    public void removeChild(String childId) {
+        int containerCount = linearLayout.getChildCount();
+
+        LinearLayout lastContainer = null;
+        List<LinearLayout> 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) {
+                AsyncImageView imageView = (AsyncImageView)child.findViewById(R.id.rc_user_portrait);
+                imageView.setAvatar(userInfo.getPortraitUri());
+                if (enableTitle) {
+                    TextView textView = (TextView)child.findViewById(R.id.rc_user_name);
+                    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);
+            }
+        }
+    }
+}

+ 31 - 0
im/CallKit/src/main/java/io/rong/callkit/util/GlideBlurformation.java

@@ -0,0 +1,31 @@
+package io.rong.callkit.util;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.support.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) {
+    }
+}

+ 57 - 0
im/CallKit/src/main/java/io/rong/callkit/util/GlideRoundTransform.java

@@ -0,0 +1,57 @@
+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) {
+    }
+}

+ 57 - 0
im/CallKit/src/main/java/io/rong/callkit/util/GlideUtils.java

@@ -0,0 +1,57 @@
+package io.rong.callkit.util;
+
+import android.content.Context;
+import android.net.Uri;
+import android.widget.ImageView;
+
+import com.bailingcloud.bailingvideo.engine.binstack.util.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 showRemotePortrait(Context context, ImageView imageView ,Uri val){
+        RequestOptions requestOptions=new RequestOptions();
+        requestOptions.transform(new GlideRoundTransform());
+        requestOptions.priority(Priority.HIGH);
+        requestOptions.placeholder(R.drawable.rc_default_portrait);
+        if(val==null){
+            Glide.with(context)
+                    .load(R.drawable.rc_default_portrait)
+                    .apply(requestOptions)
+                    .into(imageView);
+        }else{
+            Glide.with(context)
+                    .load(val)
+                    .apply(requestOptions)
+                    .into(imageView);
+        }
+    }
+}

+ 53 - 0
im/CallKit/src/main/java/io/rong/callkit/util/HeadsetInfo.java

@@ -0,0 +1,53 @@
+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;
+        }
+    }
+}

+ 61 - 0
im/CallKit/src/main/java/io/rong/callkit/util/HeadsetPlugReceiver.java

@@ -0,0 +1,61 @@
+package io.rong.callkit.util;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.util.Log;
+
+import com.bailingcloud.bailingvideo.engine.binstack.util.FinLog;
+
+import io.rong.imkit.RongContext;
+
+/**
+ * Created by Dengxudong on 2018/8/23.
+ */
+
+public class HeadsetPlugReceiver extends BroadcastReceiver{
+
+    // 动态注册了监听有线耳机之后 默认会调用一次有限耳机拔出
+    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
+            RongContext.getInstance().getEventBus().post(headsetInfo);
+        }else{
+            FinLog.e("HeadsetPlugReceiver headsetInfo=null !");
+        }
+    }
+}

+ 23 - 0
im/CallKit/src/main/java/io/rong/callkit/util/ICallScrollView.java

@@ -0,0 +1,23 @@
+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 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);
+}

+ 191 - 0
im/CallKit/src/main/java/io/rong/callkit/util/SPUtils.java

@@ -0,0 +1,191 @@
+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.ArrayList;
+import java.util.Date;
+import java.util.List;
+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<String, ?> 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();
+        }
+    }
+}

BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_nav_back_x.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_search.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_search_delete_x.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_search_focused_x.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_ic_search_x.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_mult_video_user_clo_camera.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_mult_video_user_mute.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_mult_video_user_status.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_mute_unavailable.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/callkit_select_ic_nav_back_x.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_add.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_answer.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_answer_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_left_cancel.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_left_connected.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_right_cancel.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_audio_right_connected.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_camera.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_camera_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_disable_camera.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_disable_camera_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_float_audio.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_float_video.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_handfree.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_handfree_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_handup.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_hang_up.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_hang_up_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_add.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_camera.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_checked.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_checkbox_normal.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_icon_input_video_pressed.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_iphone.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_iphone_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_menu_bg.9.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_minimize.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_more.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_mute.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_mute_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_phone.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_1.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_2.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_3.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_4.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_5.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_signal_6.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_single_audio_answer_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_switch_camera.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_answer.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_answer_hover_new.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_answer_new.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_left.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_video_right.png


BIN=BIN
im/CallKit/src/main/res/drawable-xhdpi/rc_voip_whiteboard.png


+ 11 - 0
im/CallKit/src/main/res/drawable/bg_search.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape>
+            <solid android:color="#FFFFFF"></solid>
+
+            <corners android:radius="6dp"></corners>
+        </shape>
+    </item>
+
+</layer-list>

+ 0 - 0
im/CallKit/src/main/res/drawable/callkit_multiaudiouesrinfocontners.xml


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio