Просмотр исходного кода

增加live_teaching网络教室,和融云im模块

Pq 3 лет назад
Родитель
Сommit
8a815e4302
100 измененных файлов с 7222 добавлено и 43 удалено
  1. 15 11
      BaseLibrary/build.gradle
  2. 1 0
      BaseLibrary/src/main/AndroidManifest.xml
  3. 5 2
      BaseLibrary/src/main/java/com/cooleshow/base/common/BaseApplication.kt
  4. 2 1
      BaseLibrary/src/main/java/com/cooleshow/base/constanst/Constants.java
  5. 18 0
      BaseLibrary/src/main/java/com/cooleshow/base/data/net/HttpLogger.java
  6. 118 0
      BaseLibrary/src/main/java/com/cooleshow/base/data/net/RetrofitClientNoToken.java
  7. 119 0
      BaseLibrary/src/main/java/com/cooleshow/base/data/net/RetrofitClientUpFile.java
  8. 115 0
      BaseLibrary/src/main/java/com/cooleshow/base/data/net/RetrofitStringClient.java
  9. 58 0
      BaseLibrary/src/main/java/com/cooleshow/base/data/net/SSLSocketClient.java
  10. 7 0
      BaseLibrary/src/main/java/com/cooleshow/base/router/RouterPath.kt
  11. 18 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/DeviceUtil.java
  12. 99 1
      BaseLibrary/src/main/java/com/cooleshow/base/utils/FileUtils.java
  13. 294 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/GlideImageLoaderUtils.java
  14. 149 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/JsonUtil.java
  15. 172 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/NetworkUtil.java
  16. 15 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/PermissionUtils.java
  17. 50 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/SystemUtils.java
  18. 146 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/ToastUtil.java
  19. 160 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/UiUtils.java
  20. 114 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/ColorTextView.java
  21. 2 1
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/EmptyViewLayout.java
  22. 228 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/BaseDialogFragment.java
  23. 60 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/CommonDialog.java
  24. 64 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/DialogFragmentViewHolder.java
  25. 137 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/span/QMUIAlignMiddleImageSpan.java
  26. 85 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/transformation/BlurTransformation.java
  27. 57 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/transformation/CircleTransformation.java
  28. 90 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/transformation/RadiusTransformation.java
  29. 11 0
      BaseLibrary/src/main/res/anim/bottom_enter_anim.xml
  30. 13 0
      BaseLibrary/src/main/res/anim/bottom_exit_anim.xml
  31. 11 0
      BaseLibrary/src/main/res/anim/left_enter_anim.xml
  32. 13 0
      BaseLibrary/src/main/res/anim/left_exit_anim.xml
  33. 11 0
      BaseLibrary/src/main/res/anim/right_enter_anim.xml
  34. 13 0
      BaseLibrary/src/main/res/anim/right_exit_anim.xml
  35. BIN
      BaseLibrary/src/main/res/drawable-xxhdpi/ic_stu_student_head.png
  36. BIN
      BaseLibrary/src/main/res/drawable-xxhdpi/ic_stu_teacher_head.png
  37. BIN
      BaseLibrary/src/main/res/drawable/bg_loading.9.png
  38. 7 0
      BaseLibrary/src/main/res/drawable/bg_white_5dp.xml
  39. 6 0
      BaseLibrary/src/main/res/drawable/btn_gray_start_bottom_shape.xml
  40. 6 0
      BaseLibrary/src/main/res/drawable/btn_green_end_bottom_shape.xml
  41. 6 0
      BaseLibrary/src/main/res/drawable/btn_green_stu_line_shape.xml
  42. 5 0
      BaseLibrary/src/main/res/drawable/shape_toast.xml
  43. 136 0
      BaseLibrary/src/main/res/layout/common_popu.xml
  44. 2 2
      BaseLibrary/src/main/res/layout/empty_layout.xml
  45. 16 0
      BaseLibrary/src/main/res/layout/my_toast.xml
  46. 7 0
      BaseLibrary/src/main/res/values/attrs.xml
  47. 2 0
      BaseLibrary/src/main/res/values/colors.xml
  48. 26 0
      BaseLibrary/src/main/res/values/styles.xml
  49. 2 2
      app/build.gradle
  50. 2 23
      build.gradle
  51. 38 0
      config.gradle
  52. 1 0
      live_teaching/.gitignore
  53. 76 0
      live_teaching/build.gradle
  54. 37 0
      live_teaching/proguard-rules.pro
  55. BIN
      live_teaching/rong.key
  56. 26 0
      live_teaching/src/androidTest/java/com/daya/live_teaching/ExampleInstrumentedTest.java
  57. 27 0
      live_teaching/src/main/AndroidManifest.xml
  58. 65 0
      live_teaching/src/main/java/com/daya/live_teaching/LiveTeachingApp.java
  59. 12 0
      live_teaching/src/main/java/com/daya/live_teaching/api/ApiConstant.java
  60. 70 0
      live_teaching/src/main/java/com/daya/live_teaching/api/HttpClientManager.java
  61. 266 0
      live_teaching/src/main/java/com/daya/live_teaching/api/LiveTeachingApi.java
  62. 62 0
      live_teaching/src/main/java/com/daya/live_teaching/api/LiveTeachingUrls.java
  63. 44 0
      live_teaching/src/main/java/com/daya/live_teaching/api/WhiteBoardApi.java
  64. 44 0
      live_teaching/src/main/java/com/daya/live_teaching/api/retrofit/CallBackWrapper.java
  65. 13 0
      live_teaching/src/main/java/com/daya/live_teaching/api/retrofit/HttpLogger.java
  66. 120 0
      live_teaching/src/main/java/com/daya/live_teaching/api/retrofit/RetrofitClient.java
  67. 25 0
      live_teaching/src/main/java/com/daya/live_teaching/api/retrofit/RetrofitUtil.java
  68. 395 0
      live_teaching/src/main/java/com/daya/live_teaching/audioManager/AppRTCAudioManager.java
  69. 177 0
      live_teaching/src/main/java/com/daya/live_teaching/audioManager/AppRTCProximitySensor.java
  70. 151 0
      live_teaching/src/main/java/com/daya/live_teaching/audioManager/AppRTCUtils.java
  71. 274 0
      live_teaching/src/main/java/com/daya/live_teaching/audioManager/BluetoothUtil.java
  72. 62 0
      live_teaching/src/main/java/com/daya/live_teaching/common/ErrorCode.java
  73. 11 0
      live_teaching/src/main/java/com/daya/live_teaching/common/ResultCallback.java
  74. 33 0
      live_teaching/src/main/java/com/daya/live_teaching/common/ShowToastObserver.java
  75. 52 0
      live_teaching/src/main/java/com/daya/live_teaching/common/StateLiveData.java
  76. 49 0
      live_teaching/src/main/java/com/daya/live_teaching/common/ThreadManager.java
  77. 34 0
      live_teaching/src/main/java/com/daya/live_teaching/common/ToastBySelfComponent.java
  78. 336 0
      live_teaching/src/main/java/com/daya/live_teaching/im/IMManager.java
  79. 89 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/ApplyForSpeechMessage.java
  80. 82 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/AssistantTransferMessage.java
  81. 110 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/ControlDeviceNotifyMessage.java
  82. 122 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/DeviceStateChangedMessage.java
  83. 74 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/DisplayMessage.java
  84. 95 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/ExamSongDownloadMessage.java
  85. 105 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/ExamSongDownloadStatusMessage.java
  86. 152 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/MemberChangedMessage.java
  87. 100 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/MusicScoreDownloadMessage.java
  88. 108 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/PlayMidiMessage.java
  89. 133 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/RoleChangedMessage.java
  90. 118 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/RoleSingleChangedMessage.java
  91. 106 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/SpeechResultMessage.java
  92. 93 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/TicketExpiredMessage.java
  93. 87 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/TurnPageMessage.java
  94. 109 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/UpgradeRoleMessage.java
  95. 89 0
      live_teaching/src/main/java/com/daya/live_teaching/im/message/WhiteBoardMessage.java
  96. 29 0
      live_teaching/src/main/java/com/daya/live_teaching/im/provider/BaseNotificationProvider.java
  97. 40 0
      live_teaching/src/main/java/com/daya/live_teaching/im/provider/ClassFileMessageItemProvider.java
  98. 81 0
      live_teaching/src/main/java/com/daya/live_teaching/im/provider/ClassImageMessageItemProvider.java
  99. 75 0
      live_teaching/src/main/java/com/daya/live_teaching/im/provider/ClassMemberChangedNotificationProvider.java
  100. 32 0
      live_teaching/src/main/java/com/daya/live_teaching/im/provider/ClassTextMessageItemProvider.java

+ 15 - 11
BaseLibrary/build.gradle

@@ -66,7 +66,7 @@ dependencies {
 
     //kotlin
     api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1'
-    api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$rootProject.ext.android.kotlin_version"
     api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8"
 
 
@@ -86,8 +86,8 @@ dependencies {
     //Glide
 //    implementation "com.github.bumptech.glide:glide:$glide_version"
 
-    api "com.github.bumptech.glide:glide:$glide_version"
-    kapt "com.github.bumptech.glide:compiler:$glide_version"
+    api "com.github.bumptech.glide:glide:$rootProject.ext.android.glide_version"
+    kapt "com.github.bumptech.glide:compiler:$rootProject.ext.android.glide_version"
     //Banner
     api 'io.github.youth5201314:banner:2.2.2'
 
@@ -107,18 +107,18 @@ dependencies {
     // (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version)
     implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
     //Retrofit
-    api("com.squareup.okhttp3:okhttp:${ok_http_version}")
-    api "com.squareup.okhttp3:logging-interceptor:${ok_http_version}"
+    api("com.squareup.okhttp3:okhttp:$rootProject.ext.android.ok_http_version")
+    api "com.squareup.okhttp3:logging-interceptor:$rootProject.ext.android.ok_http_version"
 
-    api "com.squareup.retrofit2:retrofit:${retrofit_version}"
-    api "com.squareup.retrofit2:converter-gson:${retrofit_version}"
-    api "com.squareup.retrofit2:adapter-rxjava:${retrofit_version}"
+    api "com.squareup.retrofit2:retrofit:$rootProject.ext.android.retrofit_version"
+    api "com.squareup.retrofit2:converter-gson:$rootProject.ext.android.retrofit_version"
+    api "com.squareup.retrofit2:adapter-rxjava:$rootProject.ext.android.retrofit_version"
     api "com.squareup.retrofit2:adapter-rxjava3:2.9.0"
 
     //MulitStateView
-    api "com.github.Kennyc1012:MultiStateView:$multi_state_view_version"
-    api "com.alibaba:arouter-api:$arouter_api_version"
-    kapt "com.alibaba:arouter-compiler:$arouter_api_version"
+    api "com.github.Kennyc1012:MultiStateView:$rootProject.ext.android.multi_state_view_version"
+    api "com.alibaba:arouter-api:$rootProject.ext.android.arouter_api_version"
+    kapt "com.alibaba:arouter-compiler:$rootProject.ext.android.arouter_api_version"
 
     api "com.haibin:calendarview:3.7.1"
 
@@ -130,4 +130,8 @@ dependencies {
     api 'io.github.scwang90:refresh-footer-classics:2.0.5'    //经典加载
 
     api "de.hdodenhof:circleimageview:2.2.0"
+
+    api 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0'
+
+    api "com.github.tbruyelle:rxpermissions:0.12"
 }

+ 1 - 0
BaseLibrary/src/main/AndroidManifest.xml

@@ -3,6 +3,7 @@
     package="com.cooleshow.base">
 
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <application />
 
 </manifest>

+ 5 - 2
BaseLibrary/src/main/java/com/cooleshow/base/common/BaseApplication.kt

@@ -29,8 +29,11 @@ abstract class BaseApplication : Application() {
      */
     companion object {
         var isTeacherClient = false
-        lateinit var context: Context
+        var registrationId: String? = null
+        lateinit var context: Application
     }
 
-    abstract fun isTeacherClient(): Boolean
+    open fun isTeacherClient(): Boolean {
+        return false;
+    }
 }

+ 2 - 1
BaseLibrary/src/main/java/com/cooleshow/base/constanst/Constanst.java → BaseLibrary/src/main/java/com/cooleshow/base/constanst/Constants.java

@@ -7,9 +7,10 @@ import com.cooleshow.base.utils.AppUtils;
 /**
  * Author by pq, Date on 2022/4/22.
  */
-public class Constanst {
+public class Constants {
     public static final String CLIENT = BaseApplication.Companion.isTeacherClient() ? "teacher" : "student";
     public static final String COLEXIUAPPA = "COLEXIUAPPA";//H5 js接口注册interfaceName
     public static final int DEFAULT_DATA_SIZE = 10;//加载更多默认一页请求数据
+    public static final String WHITE_BOARD_ORIENTATION = "WHITE_BOARD_ORIENTATION";
 
 }

+ 18 - 0
BaseLibrary/src/main/java/com/cooleshow/base/data/net/HttpLogger.java

@@ -0,0 +1,18 @@
+package com.cooleshow.base.data.net;
+
+import android.util.Log;
+
+import com.cooleshow.base.utils.JsonUtil;
+
+import okhttp3.logging.HttpLoggingInterceptor;
+
+public class HttpLogger implements HttpLoggingInterceptor.Logger {
+        @Override
+        public void log(String msg) {
+            if ((msg.startsWith("{") && msg.endsWith("}"))
+                    || (msg.startsWith("[") && msg.endsWith("]"))) {
+                msg = JsonUtil.formatJson(JsonUtil.decodeUnicode(msg));
+            }
+            Log.i("HttpLogInfo", msg);
+        }
+    }

+ 118 - 0
BaseLibrary/src/main/java/com/cooleshow/base/data/net/RetrofitClientNoToken.java

@@ -0,0 +1,118 @@
+package com.cooleshow.base.data.net;
+
+
+import android.util.Log;
+
+import com.cooleshow.base.BuildConfig;
+import com.cooleshow.base.utils.NetworkUtil;
+import com.cooleshow.base.utils.Utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.Cache;
+import okhttp3.CacheControl;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class RetrofitClientNoToken {
+
+    private static final String                API_HOST = BuildConfig.BASE_SERVER_URL;
+    private static       RetrofitClientNoToken instance;
+    private static       OkHttpClient          okHttpClient;
+    private static       Retrofit              retrofit;
+
+    private RetrofitClientNoToken() {
+
+
+
+        okHttpClient = new OkHttpClient.Builder()
+
+                .connectTimeout(60, TimeUnit.SECONDS)
+                .readTimeout(60, TimeUnit.SECONDS)
+                .cache(createMCache())
+                .retryOnConnectionFailure(true)//错误重连
+                .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getTrustManager())
+                .hostnameVerifier(SSLSocketClient.getHostnameVerifier())
+                .addInterceptor(new HttpLoggingInterceptor(new HttpLogger()).setLevel(HttpLoggingInterceptor.Level.BODY))
+                .addInterceptor(cacheInterceptor())
+                .addNetworkInterceptor(cacheInterceptor())
+                .build();
+
+        retrofit = new Retrofit.Builder()
+                .baseUrl(API_HOST)
+                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
+                .addConverterFactory(GsonConverterFactory.create())   // 使用 gson 解析器解析 json
+                .client(okHttpClient)
+                .build();
+
+    }
+
+
+    public static RetrofitClientNoToken getInstance() {
+        if (instance == null) {
+            synchronized (RetrofitClientNoToken.class) {
+                if (instance == null) {
+                    instance = new RetrofitClientNoToken();
+                }
+            }
+        }
+        return instance;
+    }
+
+    public OkHttpClient getOkHttpClient() {
+        return okHttpClient;
+    }
+
+    public Retrofit getRetrofit() {
+        return retrofit;
+    }
+
+    private static Cache createMCache() {
+        File cacheFile = new File(String.valueOf(Utils.getApp().getCacheDir()));
+        return new Cache(cacheFile, 1024 * 1024 * 60);
+    }
+
+    private Interceptor cacheInterceptor() {
+        return new Interceptor() {
+            @Override
+            public Response intercept(Chain chain) throws IOException {
+                Request request = chain.request();
+
+                if (!NetworkUtil.isNetworkAvailable(Utils.getApp())) {
+                    request = request.newBuilder()
+                            //强制使用缓存
+                            .cacheControl(CacheControl.FORCE_CACHE)
+                            .build();
+                }
+
+                Response response = chain.proceed(request);
+
+                if (NetworkUtil.isNetworkAvailable(Utils.getApp())) {
+                    //有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置
+                    String cacheControl = request.cacheControl().toString();
+                    Log.i("has network", "cacheControl=" + cacheControl);
+                    return response.newBuilder()
+                            .header("Cache-Control", cacheControl)
+                            .removeHeader("Pragma")
+                            .build();
+                } else {
+                    int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+                    Log.i("network error ", "maxStale=" + maxStale);
+                    return response.newBuilder()
+                            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
+                            .removeHeader("Pragma")
+                            .build();
+                }
+
+            }
+        };
+    }
+}

+ 119 - 0
BaseLibrary/src/main/java/com/cooleshow/base/data/net/RetrofitClientUpFile.java

@@ -0,0 +1,119 @@
+package com.cooleshow.base.data.net;
+
+
+import android.util.Log;
+
+import com.cooleshow.base.BuildConfig;
+import com.cooleshow.base.common.BaseApplication;
+import com.cooleshow.base.utils.NetworkUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.Cache;
+import okhttp3.CacheControl;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class RetrofitClientUpFile {
+
+    private static final String API_HOST = BuildConfig.BASE_SERVER_URL;
+    private static RetrofitClientUpFile instance;
+    private static OkHttpClient okHttpClient;
+    private static Retrofit retrofit;
+
+    private RetrofitClientUpFile() {
+
+//        ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(BaseApplication.getApplication()));
+
+        okHttpClient = new OkHttpClient.Builder()
+                .connectTimeout(600, TimeUnit.SECONDS)
+                .readTimeout(600, TimeUnit.SECONDS)
+//                .cookieJar(cookieJar)
+                .cache(createMCache())
+                .retryOnConnectionFailure(true)//错误重连
+                .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getTrustManager())
+                .hostnameVerifier(SSLSocketClient.getHostnameVerifier())
+                .addInterceptor(new CommonInterceptor())
+                .build();
+
+        retrofit = new Retrofit.Builder()
+                .baseUrl(API_HOST)
+                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
+                .addConverterFactory(GsonConverterFactory.create())   // 使用 gson 解析器解析 json
+                .client(okHttpClient)
+                .build();
+
+    }
+
+
+    public static RetrofitClientUpFile getInstance() {
+        if (instance == null) {
+            synchronized (RetrofitClientUpFile.class) {
+                if (instance == null) {
+                    instance = new RetrofitClientUpFile();
+                }
+            }
+        }
+        return instance;
+    }
+
+    public OkHttpClient getOkHttpClient() {
+        return okHttpClient;
+    }
+
+    public Retrofit getRetrofit() {
+        return retrofit;
+    }
+
+    private static Cache createMCache() {
+        File cacheFile = new File(String.valueOf(BaseApplication.context.getCacheDir()));
+        return new Cache(cacheFile, 1024 * 1024 * 60);
+    }
+
+    private Interceptor cacheInterceptor() {
+        return new Interceptor() {
+            @Override
+            public Response intercept(Chain chain) throws IOException {
+                Request request = chain.request();
+
+                if (!NetworkUtil.isNetworkAvailable(BaseApplication.context)) {
+                    request = request.newBuilder()
+                            //强制使用缓存
+                            .cacheControl(CacheControl.FORCE_CACHE)
+                            .build();
+                }
+
+                Response response = chain.proceed(request);
+
+                if (NetworkUtil.isNetworkAvailable(BaseApplication.context)) {
+                    //有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置
+                    String cacheControl = request.cacheControl().toString();
+                    Log.i("has network", "cacheControl=" + cacheControl);
+                    return response.newBuilder()
+                            .header("Cache-Control", cacheControl)
+                            .removeHeader("Pragma")
+                            .build();
+                } else {
+                    int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+                    Log.i("network error ", "maxStale=" + maxStale);
+                    return response.newBuilder()
+                            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
+                            .removeHeader("Pragma")
+                            .build();
+                }
+
+            }
+        };
+    }
+
+
+
+
+}

+ 115 - 0
BaseLibrary/src/main/java/com/cooleshow/base/data/net/RetrofitStringClient.java

@@ -0,0 +1,115 @@
+package com.cooleshow.base.data.net;
+
+
+import android.util.Log;
+
+import com.cooleshow.base.BuildConfig;
+import com.cooleshow.base.utils.NetworkUtil;
+import com.cooleshow.base.utils.Utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.Cache;
+import okhttp3.CacheControl;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class RetrofitStringClient {
+
+    private static final String         API_HOST = BuildConfig.BASE_SERVER_URL;
+    private static RetrofitStringClient instance;
+    private static       OkHttpClient   okHttpClient;
+    private static       Retrofit       retrofit;
+
+    private RetrofitStringClient() {
+
+
+        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
+        okHttpClient = okHttpClientBuilder.connectTimeout(60, TimeUnit.SECONDS)
+                .readTimeout(60, TimeUnit.SECONDS)
+                .cache(createMCache())
+                .retryOnConnectionFailure(true)//错误重连
+                .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getTrustManager())
+                .hostnameVerifier(SSLSocketClient.getHostnameVerifier())
+                .addInterceptor(new CommonInterceptor())
+                .addInterceptor(new HttpLoggingInterceptor(new HttpLogger()).setLevel(HttpLoggingInterceptor.Level.BODY))
+                .build();
+
+        retrofit = new Retrofit.Builder()
+                .baseUrl(API_HOST)
+                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
+                .addConverterFactory(GsonConverterFactory.create())   // 使用 gson 解析器解析 json
+                .client(okHttpClient)
+                .build();
+
+    }
+
+
+    public static RetrofitStringClient getInstance() {
+        if (instance == null) {
+            synchronized (RetrofitStringClient.class) {
+                if (instance == null) {
+                    instance = new RetrofitStringClient();
+                }
+            }
+        }
+        return instance;
+    }
+
+    public OkHttpClient getOkHttpClient() {
+        return okHttpClient;
+    }
+
+    public Retrofit getRetrofit() {
+        return retrofit;
+    }
+
+    private static Cache createMCache() {
+        File cacheFile = new File(String.valueOf(Utils.getApp().getCacheDir()));
+        return new Cache(cacheFile, 1024 * 1024 * 60);
+    }
+
+    private Interceptor cacheInterceptor() {
+        return new Interceptor() {
+            @Override
+            public Response intercept(Chain chain) throws IOException {
+                Request request = chain.request();
+
+                if (!NetworkUtil.isNetworkAvailable(Utils.getApp())) {
+                    request = request.newBuilder()
+                            //强制使用缓存
+                            .cacheControl(CacheControl.FORCE_CACHE)
+                            .build();
+                }
+
+                Response response = chain.proceed(request);
+
+                if (NetworkUtil.isNetworkAvailable(Utils.getApp())) {
+                    //有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置
+                    String cacheControl = request.cacheControl().toString();
+                    Log.i("has network", "cacheControl=" + cacheControl);
+                    return response.newBuilder()
+                            .header("Cache-Control", cacheControl)
+                            .removeHeader("Pragma")
+                            .build();
+                } else {
+                    int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+                    Log.i("network error ", "maxStale=" + maxStale);
+                    return response.newBuilder()
+                            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
+                            .removeHeader("Pragma")
+                            .build();
+                }
+
+            }
+        };
+    }
+}

+ 58 - 0
BaseLibrary/src/main/java/com/cooleshow/base/data/net/SSLSocketClient.java

@@ -0,0 +1,58 @@
+package com.cooleshow.base.data.net;
+
+import android.util.Log;
+
+import com.cooleshow.base.utils.LogUtils;
+
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class SSLSocketClient {
+    //获取这个SSLSocketFactory
+    public static SSLSocketFactory getSSLSocketFactory() {
+        try {
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(null, new TrustManager[]{new TrustAllCerts()}, new SecureRandom());
+            return sslContext.getSocketFactory();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    //获取TrustManager
+    public static TrustAllCerts getTrustManager() {
+        return new TrustAllCerts();
+    }
+
+    private static class TrustAllCerts implements X509TrustManager {
+        public void checkClientTrusted(X509Certificate[] chain, String authType) {
+        }
+
+        public void checkServerTrusted(X509Certificate[] chain, String authType) {
+        }
+
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[]{};
+        }
+    }
+
+
+    // 获取HostnameVerifier
+    public static HostnameVerifier getHostnameVerifier() {
+        HostnameVerifier hostnameVerifier = new HostnameVerifier() {
+            @Override
+            public boolean verify(String s, SSLSession sslSession) {
+                LogUtils.e(s);
+                return true;
+            }
+        };
+        return hostnameVerifier;
+    }
+}

+ 7 - 0
BaseLibrary/src/main/java/com/cooleshow/base/router/RouterPath.kt

@@ -13,6 +13,13 @@ object RouterPath {
         }
     }
 
+    class LiveCenter{
+        companion object{
+            const val PATH_LIVE ="/com/daya/live_teaching/ui/LiveActivity"
+            const val ACTIVITY_PHOTO_PREVIEW ="/com/daya/live_teaching/ui/ACTIVITY_PHOTO_PREVIEW"
+        }
+    }
+
     //用户模块
     class UserCenter {
         companion object {

+ 18 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/DeviceUtil.java

@@ -0,0 +1,18 @@
+package com.cooleshow.base.utils;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+public class DeviceUtil {
+    private DeviceUtil() {
+        throw new UnsupportedOperationException("cannot be instantiated");
+    }
+
+    public static DisplayMetrics getDeviceSize(Context context){
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics metrics=new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(metrics);
+        return metrics;
+    }
+}

+ 99 - 1
BaseLibrary/src/main/java/com/cooleshow/base/utils/FileUtils.java

@@ -1,20 +1,27 @@
 package com.cooleshow.base.utils;
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Environment;
 import android.os.StatFs;
 import android.text.TextUtils;
 
 import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.URL;
 import java.security.DigestInputStream;
 import java.security.MessageDigest;
@@ -26,6 +33,8 @@ import java.util.List;
 
 import javax.net.ssl.HttpsURLConnection;
 
+import okhttp3.ResponseBody;
+
 /**
  * <pre>
  *     author: Blankj
@@ -35,7 +44,8 @@ import javax.net.ssl.HttpsURLConnection;
  * </pre>
  */
 public final class FileUtils {
-
+    private static final String filesDirectory = "coolexiu";
+    public static final String examDownloadDirectory = "examDownload";
     private static final String LINE_SEP = System.getProperty("line.separator");
 
     private FileUtils() {
@@ -1453,4 +1463,92 @@ public final class FileUtils {
     public interface OnReplaceListener {
         boolean onReplace(File srcFile, File destFile);
     }
+
+    public static boolean writeFileToSDCard(ResponseBody body, String filePath, String fileName) {
+        if (null == body) {
+            return false;
+        }
+        try {
+            File futureStudioIconFile = new File(filePath + File.separator + fileName);
+            InputStream inputStream = null;
+            OutputStream outputStream = null;
+            try {
+                byte[] fileReader = new byte[4096];
+                long fileSize = body.contentLength();
+                long fileSizeDownloaded = 0;
+                inputStream = body.byteStream();
+                outputStream = new FileOutputStream(futureStudioIconFile);
+                while (true) {
+                    int read = inputStream.read(fileReader);
+                    if (read == -1) {
+                        break;
+                    }
+                    outputStream.write(fileReader, 0, read);
+                    fileSizeDownloaded += read;
+                }
+                outputStream.flush();
+                return true;
+            } catch (IOException e) {
+                return false;
+            } finally {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            }
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    public static String getFilePath(Bitmap bitmap) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        bitmap.compress(Bitmap.CompressFormat.PNG, 80, baos);
+        String path = Environment.getExternalStorageDirectory() + "/dayaWhiteBroad";
+        File dir = new File(path);
+        if (!dir.exists()) {
+            dir.mkdirs();
+        }
+        File file = new File(path + "/" + System.currentTimeMillis() + ".png");
+        try {
+            file.createNewFile();
+            FileOutputStream fos = new FileOutputStream(file);
+            InputStream is = new ByteArrayInputStream(baos.toByteArray());
+            int x = 0;
+            byte[] b = new byte[1024 * 100];
+            while ((x = is.read(b)) != -1) {
+                fos.write(b, 0, x);
+            }
+            fos.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return file.getPath();
+    }
+
+
+    public static String getCacheDir(Context context, String directory) {
+        String fileDir;
+        try {
+            File file = new File(context.getExternalCacheDir() + File.separator + directory);
+            if (!file.exists()) {
+                file.mkdirs();
+            }
+            fileDir = file.getAbsolutePath();
+            return fileDir;
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    /**
+     * 判断SD卡上的文件是否存在
+     */
+    public static boolean isFileExist(String fileName) {
+        File file = new File(fileName);
+
+        return file == null ? false : file.exists();
+    }
 }

+ 294 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/GlideImageLoaderUtils.java

@@ -0,0 +1,294 @@
+package com.cooleshow.base.utils;
+
+import android.content.Context;
+import android.util.Log;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.request.FutureTarget;
+import com.bumptech.glide.request.RequestOptions;
+import com.cooleshow.base.R;
+import com.cooleshow.base.common.BaseApplication;
+import com.cooleshow.base.widgets.transformation.BlurTransformation;
+import com.cooleshow.base.widgets.transformation.CircleTransformation;
+import com.cooleshow.base.widgets.transformation.RadiusTransformation;
+
+import java.io.File;
+
+import androidx.annotation.DrawableRes;
+
+/**
+ * Description:
+ * Copyright  : Copyright (c) 2018
+ * Company    : 蓝鲸分期
+ * Author     : 刘瑞
+ * Date       : 2019/4/15 9:54
+ */
+public class GlideImageLoaderUtils {
+    private static GlideImageLoaderUtils mInstance;
+
+    private GlideImageLoaderUtils() {
+    }
+
+    public static GlideImageLoaderUtils getInstance() {
+        if (mInstance == null) {
+            synchronized (GlideImageLoaderUtils.class) {
+                if (mInstance == null) {
+                    mInstance = new GlideImageLoaderUtils();
+                }
+            }
+        }
+        return mInstance;
+    }
+
+    //    public static final int R.mipmap.bg_loading = R.mipmap.bg_loading;
+    //    public static final int errorSoWhite = R.mipmap.bg_loading;
+    // public static final int soWhite = R.mipmap.bg_loading;
+
+    /*
+     *加载图片(默认)
+     */
+    public void loadImageBanner(Context context, Object url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .centerCrop()
+                .placeholder(R.drawable.bg_loading) //占位图
+                .error(R.drawable.bg_loading)       //错误图
+                //             .priority(Priority.HIGH)
+                .diskCacheStrategy(DiskCacheStrategy.ALL);
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+    /**
+     * 加载图片(默认)
+     */
+    public void loadImage(Context context, Object url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .centerCrop()
+                .placeholder(R.drawable.bg_loading) //占位图
+                .error(R.drawable.bg_loading)       //错误图
+                .diskCacheStrategy(DiskCacheStrategy.NONE);
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+    /**
+     * 加载图片(默认)
+     */
+    public void loadImageCenterInside(Context context, Object url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .centerInside()
+                .placeholder(R.drawable.bg_loading) //占位图
+                .error(R.drawable.bg_loading)       //错误图
+                .diskCacheStrategy(DiskCacheStrategy.NONE);
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+    /**
+     * 指定图片大小;使用override()方法指定了一个图片的尺寸。
+     * Glide现在只会将图片加载成width*height像素的尺寸,而不会管你的ImageView的大小是多少了。
+     * 如果你想加载一张图片的原始尺寸的话,可以使用Target.SIZE_ORIGINAL关键字----override(Target.SIZE_ORIGINAL)
+     *
+     * @param context
+     * @param url
+     * @param imageView
+     * @param width
+     * @param height
+     */
+    public void loadImageSize(Context context, Object url, ImageView imageView, int width, int height) {
+        RequestOptions options = new RequestOptions()
+                .centerCrop()
+                .placeholder(R.drawable.bg_loading) //占位图
+                .error(R.drawable.bg_loading)       //错误图
+                .override(width, height)
+                // .priority(Priority.HIGH)
+                .diskCacheStrategy(DiskCacheStrategy.ALL);
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+
+    /**
+     * 禁用内存缓存功能
+     * diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收五种参数:
+     * <p>
+     * DiskCacheStrategy.NONE: 表示不缓存任何内容。
+     * DiskCacheStrategy.DATA: 表示只缓存原始图片。
+     * DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。
+     * DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
+     * DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。
+     */
+
+    public void loadImageSizekipMemoryCache(Context context, Object url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .placeholder(R.drawable.bg_loading) //占位图
+                .error(R.drawable.bg_loading)       //错误图S
+                .skipMemoryCache(true)//禁用掉Glide的内存缓存功能
+                .diskCacheStrategy(DiskCacheStrategy.ALL);
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+
+    /**
+     * 加载圆形图片
+     */
+    public void loadCircleImage(Context context, Object url, ImageView imageView) {
+        if (BaseApplication.Companion.isTeacherClient()) {
+            loadCircleImage(context, url, R.drawable.ic_stu_student_head, imageView);
+        } else {
+            loadCircleImage(context, url, R.drawable.ic_stu_student_head, imageView);
+        }
+    }
+
+    /**
+     * 加载圆形图片
+     */
+    public void loadCircleImage(Context context, Object url, @DrawableRes int error, ImageView imageView) {
+
+        RequestOptions options = new RequestOptions()
+                .centerCrop()
+                .circleCrop()
+                .diskCacheStrategy(DiskCacheStrategy.NONE)
+                .placeholder(error)
+                .error(error);
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+
+    /**
+     * 预先加载图片
+     * 在使用图片之前,预先把图片加载到缓存,调用了预加载之后,我们以后想再去加载这张图片就会非常快了,
+     * 因为Glide会直接从缓存当中去读取图片并显示出来
+     */
+    public void preloadImage(Context context, Object url) {
+        Glide.with(context)
+                .load(url)
+                .preload();
+
+    }
+
+    /**
+     * 加载圆角图片
+     */
+    public void loadRoundCircleImage(Context context, Object url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .centerCrop()
+                .circleCrop()//设置圆形
+                .placeholder(R.drawable.bg_loading)
+                .error(R.drawable.bg_loading)
+                //.priority(Priority.HIGH)
+                .bitmapTransform(new CircleTransformation())
+                .diskCacheStrategy(DiskCacheStrategy.ALL);
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+
+    /**
+     * 加载圆角图片-指定任意部分圆角(图片上、下、左、右四个角度任意定义)
+     *
+     * @param context
+     * @param url
+     * @param imageView
+     * @param radious
+     */
+    public void loadCustRoundCircleImage(Context context, Object url, ImageView imageView, int radious) {
+        RequestOptions options = new RequestOptions()
+                .placeholder(R.drawable.bg_loading)
+                .error(R.drawable.bg_loading)
+                //.priority(Priority.HIGH)
+                .transform(new RadiusTransformation(context, radious))
+                .diskCacheStrategy(DiskCacheStrategy.NONE);
+
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+
+    /**
+     * 加载模糊图片(自定义透明度)
+     *
+     * @param context
+     * @param url
+     * @param imageView
+     * @param blur      模糊度,一般1-100够了,越大越模糊
+     */
+    public void loadBlurImage(Context context, Object url, ImageView imageView, int blur) {
+        RequestOptions options = new RequestOptions()
+                .centerCrop()
+                .placeholder(R.drawable.bg_loading)
+                .error(R.drawable.bg_loading)
+                //.priority(Priority.HIGH)
+                .bitmapTransform(new BlurTransformation(context, blur))
+                .diskCacheStrategy(DiskCacheStrategy.ALL);
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+    /*
+     *加载灰度(黑白)图片(自定义透明度)
+     */
+    public void loadBlackImage(Context context, String url, ImageView imageView) {
+        //    RequestOptions options = new RequestOptions()
+        //            .centerCrop()
+        //            .placeholder(R.mipmap.bg_loading)
+        //            .error(errorSoWhite)
+        //            //.priority(Priority.HIGH)
+        //            .bitmapTransform(new GrayscaleTransformation())
+        //            .diskCacheStrategy(DiskCacheStrategy.ALL);
+        //    Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+    /**
+     * Glide.with(this).asGif()    //强制指定加载动态图片
+     * 如果加载的图片不是gif,则asGif()会报错, 当然,asGif()不写也是可以正常加载的。
+     * 加入了一个asBitmap()方法,这个方法的意思就是说这里只允许加载静态图片,不需要Glide去帮我们自动进行图片格式的判断了。
+     * 如果你传入的还是一张GIF图的话,Glide会展示这张GIF图的第一帧,而不会去播放它。
+     *
+     * @param context
+     * @param url       例如:https://image.niwoxuexi.com/blog/content/5c0d4b1972-loading.gif
+     * @param imageView
+     */
+    public static void loadGif(Context context, Object url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .diskCacheStrategy(DiskCacheStrategy.RESOURCE);
+        Glide.with(context)
+                .asGif()
+                .load(url)
+                .apply(options)
+                .into(imageView);
+    }
+
+    public static int dip2px(Context context, float dp) {
+        float scale = context.getResources().getDisplayMetrics().density;
+        return (int) (dp * scale + 0.5f);
+    }
+
+    public void downloadImage(final Context context, final Object url) {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    //String url = "http://www.guolin.tech/book.png";
+                    FutureTarget<File> target = Glide.with(context)
+                            .asFile()
+                            .load(url)
+                            .submit();
+                    final File imageFile = target.get();
+                    Log.d("logcat", "下载好的图片文件路径=" + imageFile.getPath());
+                    //                    runOnUiThread(new Runnable() {
+                    //                        @Override
+                    //                        public void run() {
+                    //                            Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show();
+                    //                        }
+                    //                    });
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }).start();
+
+
+    }
+}

+ 149 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/JsonUtil.java

@@ -0,0 +1,149 @@
+package com.cooleshow.base.utils;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import kotlin.jvm.internal.Intrinsics;
+
+public class JsonUtil {
+
+    @NotNull
+    public static String formatJson(@Nullable String jsonStr) {
+        if (jsonStr != null && !Intrinsics.areEqual("", jsonStr)) {
+            StringBuilder sb = new StringBuilder();
+            boolean last = false;
+            char current = 0;
+            int indent = 0;
+            int i = 0;
+
+            for(int var7 = jsonStr.length(); i < var7; ++i) {
+                char lastcur = current;
+                current = jsonStr.charAt(i);
+                switch(current) {
+                    case ',':
+                        sb.append(current);
+                        if (lastcur != '\\') {
+                            sb.append('\n');
+                            addIndentBlank(sb, indent);
+                        }
+                        break;
+                    case '[':
+                    case '{':
+                        sb.append(current);
+                        sb.append('\n');
+                        ++indent;
+                        addIndentBlank(sb, indent);
+                        break;
+                    case ']':
+                    case '}':
+                        sb.append('\n');
+                        --indent;
+                        addIndentBlank(sb, indent);
+                        Intrinsics.checkNotNullExpressionValue(sb.append(current), "sb.append(current)");
+                        break;
+                    default:
+                        Intrinsics.checkNotNullExpressionValue(sb.append(current), "sb.append(current)");
+                }
+            }
+
+            String var10000 = sb.toString();
+            Intrinsics.checkNotNullExpressionValue(var10000, "sb.toString()");
+            return var10000;
+        } else {
+            return "";
+        }
+    }
+
+    private static void addIndentBlank(StringBuilder sb, int indent) {
+        int var3 = 0;
+
+        for(int var4 = indent; var3 < var4; ++var3) {
+            sb.append('\t');
+        }
+
+    }
+
+    @NotNull
+    public static String decodeUnicode(@NotNull String theString) {
+        Intrinsics.checkNotNullParameter(theString, "theString");
+        boolean aChar = false;
+        int len = theString.length();
+        StringBuffer outBuffer = new StringBuffer(len);
+        int x = 0;
+
+        while(true) {
+            while(true) {
+                while(x < len) {
+                    char deChar = theString.charAt(x++);
+                    if (deChar == '\\') {
+                        deChar = theString.charAt(x++);
+                        if (deChar == 'u') {
+                            int value = 0;
+                            int var7 = 0;
+
+                            for(byte var8 = 3; var7 <= var8; ++var7) {
+                                deChar = theString.charAt(x++);
+                                switch(deChar) {
+                                    case '0':
+                                    case '1':
+                                    case '2':
+                                    case '3':
+                                    case '4':
+                                    case '5':
+                                    case '6':
+                                    case '7':
+                                    case '8':
+                                    case '9':
+                                        value = (value << 4) + deChar - 48;
+                                        break;
+                                    case 'A':
+                                    case 'B':
+                                    case 'C':
+                                    case 'D':
+                                    case 'E':
+                                    case 'F':
+                                        value = (value << 4) + 10 + deChar - 65;
+                                        break;
+                                    case 'a':
+                                    case 'b':
+                                    case 'c':
+                                    case 'd':
+                                    case 'e':
+                                    case 'f':
+                                        value = (value << 4) + 10 + deChar - 97;
+                                        break;
+                                    default:
+
+                                }
+                            }
+
+                            outBuffer.append((char)value);
+                        } else {
+                            if (deChar == 't') {
+                                deChar = '\t';
+                            } else if (deChar == 'r') {
+                                deChar = '\r';
+                            } else if (deChar == 'n') {
+                                deChar = '\n';
+                            } else if (deChar == 'f') {
+                                deChar = '\f';
+                            }
+
+                            outBuffer.append(deChar);
+                        }
+                    } else {
+                        outBuffer.append(deChar);
+                    }
+                }
+
+                String var10000 = outBuffer.toString();
+                Intrinsics.checkNotNullExpressionValue(var10000, "outBuffer.toString()");
+                return var10000;
+            }
+        }
+    }
+
+    private JsonUtil() {
+    }
+
+}

+ 172 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/NetworkUtil.java

@@ -0,0 +1,172 @@
+package com.cooleshow.base.utils;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * 网络链接工具类
+ */
+public class NetworkUtil {
+    /**
+     * 没有网络
+     */
+    public static final int NETWORKTYPE_INVALID = 0;
+    /**
+     * wap网络
+     */
+    public static final int NETWORKTYPE_WAP     = 1;
+    /**
+     * 2G网络
+     */
+    public static final int NETWORKTYPE_2G      = 2;
+    /**
+     * 3G和3G以上网络,或统称为快速网络
+     */
+    public static final int NETWORKTYPE_3G      = 3;
+    /**
+     * wifi网络
+     */
+    public static final int NETWORKTYPE_WIFI    = 4;
+    public static       int mNetWorkType;
+
+    private NetworkUtil() {
+        throw new UnsupportedOperationException("cannot be instantiated");
+    }
+
+    /**
+     * 网络链接是否可用
+     * @param context
+     * @return
+     */
+    public static boolean isNetworkAvailable(Context context) {
+        try {
+            ConnectivityManager connectivityManager =
+                    (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+            if (connectivityManager != null) {
+                NetworkInfo[] networkInfos = connectivityManager.getAllNetworkInfo();
+                for (NetworkInfo networkInfo : networkInfos) {
+                    NetworkInfo.State state = networkInfo.getState();
+                    if (state == NetworkInfo.State.CONNECTED) {
+                        return true;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            return false;
+        }
+
+        return false;
+    }
+
+    public static final boolean ping() {
+        String result = null;
+        try {
+            String ip = "www.baidu.com";// ping 的地址,可以换成任何一种可靠的外网
+            Process p = Runtime.getRuntime().exec("ping -c 3 -w 100 " + ip);// ping网址3次
+            //读取ping的内容,可以不加
+            InputStream input = p.getInputStream();
+            BufferedReader in = new BufferedReader(new InputStreamReader(input));
+            StringBuffer stringBuffer = new StringBuffer();
+            String content = "";
+            while ((content = in.readLine()) != null) {
+                stringBuffer.append(content);
+            }
+            Log.d("------ping-----", "result content : " + stringBuffer.toString());
+            // ping的状态
+            int status = p.waitFor();
+            if (status == 0) {
+                result = "success";
+                return true;
+            } else {
+                result = "failed";
+            }
+        } catch (IOException e) {
+            result = "IOException";
+        } catch (InterruptedException e) {
+            result = "InterruptedException";
+        } finally {
+            Log.d("----result---", "result = " + result);
+        }
+        return false;
+    }
+
+    /**
+     * 获取网络状态,wifi,wap,2g,3g.
+     * @param context
+     *         上下文
+     * @return int 网络状态 {@link #NETWORKTYPE_2G},{@link #NETWORKTYPE_3G},          *{@link #NETWORKTYPE_INVALID},{@link #NETWORKTYPE_WAP}* <p>{@link #NETWORKTYPE_WIFI}
+     */
+    public static int getNetWorkType(Context context) {
+        try {
+            ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+            NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+            if (networkInfo != null && networkInfo.isConnected()) {
+                String type = networkInfo.getTypeName();
+                if (type.equalsIgnoreCase("WIFI")) {
+                    mNetWorkType = NETWORKTYPE_WIFI;
+                } else if (type.equalsIgnoreCase("MOBILE")) {
+                    String proxyHost = android.net.Proxy.getDefaultHost();
+                    mNetWorkType = TextUtils.isEmpty(proxyHost)
+                            ? (isFastMobileNetwork(context) ? NETWORKTYPE_3G : NETWORKTYPE_2G)
+                            : NETWORKTYPE_WAP;
+                }
+            } else {
+                mNetWorkType = NETWORKTYPE_INVALID;
+            }
+        } catch (Exception e) {
+            return NETWORKTYPE_INVALID;
+        }
+
+        return mNetWorkType;
+    }
+
+    public static boolean isFastMobileNetwork(Context context) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        switch (telephonyManager.getNetworkType()) {
+            case TelephonyManager.NETWORK_TYPE_1xRTT:
+                return false; // ~ 50-100 kbps
+            case TelephonyManager.NETWORK_TYPE_CDMA:
+                return false; // ~ 14-64 kbps
+            case TelephonyManager.NETWORK_TYPE_EDGE:
+                return false; // ~ 50-100 kbps
+            case TelephonyManager.NETWORK_TYPE_EVDO_0:
+                return true; // ~ 400-1000 kbps
+            case TelephonyManager.NETWORK_TYPE_EVDO_A:
+                return true; // ~ 600-1400 kbps
+            case TelephonyManager.NETWORK_TYPE_GPRS:
+                return false; // ~ 100 kbps
+            case TelephonyManager.NETWORK_TYPE_HSDPA:
+                return true; // ~ 2-14 Mbps
+            case TelephonyManager.NETWORK_TYPE_HSPA:
+                return true; // ~ 700-1700 kbps
+            case TelephonyManager.NETWORK_TYPE_HSUPA:
+                return true; // ~ 1-23 Mbps
+            case TelephonyManager.NETWORK_TYPE_UMTS:
+                return true; // ~ 400-7000 kbps
+            case TelephonyManager.NETWORK_TYPE_EHRPD:
+                return true; // ~ 1-2 Mbps
+            case TelephonyManager.NETWORK_TYPE_EVDO_B:
+                return true; // ~ 5 Mbps
+            case TelephonyManager.NETWORK_TYPE_HSPAP:
+                return true; // ~ 10-20 Mbps
+            case TelephonyManager.NETWORK_TYPE_IDEN:
+                return false; // ~25 kbps
+            case TelephonyManager.NETWORK_TYPE_LTE:
+                return true; // ~ 10+ Mbps
+            case TelephonyManager.NETWORK_TYPE_UNKNOWN:
+                return false;
+            default:
+                return false;
+        }
+    }
+
+}

+ 15 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/PermissionUtils.java

@@ -2,6 +2,7 @@ package com.cooleshow.base.utils;
 
 import android.annotation.TargetApi;
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
@@ -553,6 +554,20 @@ public final class PermissionUtils {
         }
     }
 
+    public static void toSelfSetting(Context context) {
+        Intent mIntent = new Intent();
+        mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (Build.VERSION.SDK_INT >= 9) {
+            mIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
+            mIntent.setData(Uri.fromParts("package", context.getPackageName(), null));
+        } else if (Build.VERSION.SDK_INT <= 8) {
+            mIntent.setAction(Intent.ACTION_VIEW);
+            mIntent.setClassName("com.android.settings", "com.android.setting.InstalledAppDetails");
+            mIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
+        }
+        context.startActivity(mIntent);
+    }
+
     ///////////////////////////////////////////////////////////////////////////
     // interface
     ///////////////////////////////////////////////////////////////////////////

+ 50 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/SystemUtils.java

@@ -0,0 +1,50 @@
+package com.cooleshow.base.utils;
+
+import android.content.Context;
+import android.os.Build;
+import android.provider.Settings;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class SystemUtils {
+    /**
+     * ANDROID_ID(恢复出厂+刷机会变) + 序列号(android 10会unknown/android 9需要设备权限)+品牌    +机型
+     * @return
+     */
+    public static String getUniqueIdentificationCode(Context context) {
+        String androidId = Settings.System.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
+        String uniqueCode;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            /** 需要权限 且仅适用9.0。 10.0后又不能获取了*/
+            uniqueCode = androidId + Build.BRAND + Build.MODEL;
+        } else {
+            uniqueCode = androidId + Build.SERIAL + Build.BRAND + Build.MODEL;
+        }
+        return toMD5(uniqueCode);
+    }
+
+    /**
+     * MD5加密 格式一致
+     */
+    private static String toMD5(String text) {
+        MessageDigest messageDigest = null;
+        try {
+            messageDigest = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+        byte[] digest = messageDigest.digest(text.getBytes());
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < digest.length; i++) {
+            int digestInt = digest[i] & 0xff;
+            //将10进制转化为较短的16进制
+            String hexString = Integer.toHexString(digestInt);
+            if (hexString.length() < 2) {
+                sb.append(0);
+            }
+            sb.append(hexString);
+        }
+        return sb.toString().substring(8, 24);
+    }
+}

+ 146 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/ToastUtil.java

@@ -0,0 +1,146 @@
+package com.cooleshow.base.utils;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.cooleshow.base.R;
+
+public class ToastUtil {
+
+    public static ToastUtil toast;
+    private String toastStr;
+    Toast toast1 = null;
+    private TextView tv;
+    private Handler handler;
+
+    public static ToastUtil getInstance() {
+        if (null == toast) {
+            toast = new ToastUtil();
+        }
+        return toast;
+    }
+
+
+    /**
+     * 创建自定义Toasts :
+     */
+    public void show(Context context, String toastStr) {
+
+        if (null == context || TextUtils.isEmpty(toastStr)) {
+            return;
+        }
+        if (!TextUtils.isEmpty(this.toastStr) && toastStr.equals(this.toastStr)) {
+            if (handler == null) {
+                handler = new Handler();
+                handler.postDelayed(() -> {
+                    ToastUtil.this.toastStr = null;
+                    handler = null;
+                }, 2000);
+            }
+            return;
+        }
+        this.toastStr = toastStr;
+        toast1 = null;
+        toast1 = new Toast(context);
+        View toastRoot = LayoutInflater.from(context).inflate(R.layout.my_toast, null);
+        toast1.setView(toastRoot);
+        toast1.setGravity(Gravity.CENTER, 0, 0);
+        tv = toastRoot.findViewById(R.id.TextViewInfo);
+
+        if (toastStr.length() > 15) {
+            toast1.setDuration(Toast.LENGTH_LONG);
+        } else {
+            toast1.setDuration(Toast.LENGTH_SHORT);
+        }
+        if (tv != null) {
+            tv.setText(toastStr);
+        }
+        toast1.show();
+    }
+
+    public void cancel(){
+        if(toast1 != null){
+            toast1.cancel();
+        }
+    }
+
+    /**
+     * 创建自定义Toasts :
+     */
+    public void showSpecialScore(Context context, int score) {
+
+//        if(toast1 != null){
+//            toast1.cancel();
+//        }
+//        toast1 = null;
+//        toast1 = new Toast(context);
+//        View toastRoot = LayoutInflater.from(context).inflate(R.layout.layout_toast_score, null);
+//        toast1.setView(toastRoot);
+//        toast1.setGravity(Gravity.CENTER, 0, 0);
+//        TextView tv_layout_toast_score = toastRoot.findViewById(R.id.tv_layout_toast_score);
+//        toast1.setDuration(Toast.LENGTH_SHORT);
+//        ImageView iv_layout_toast_score = toastRoot.findViewById(R.id.iv_layout_toast_score);
+//
+//        if (score >= 80) {
+//            iv_layout_toast_score.setImageResource(R.mipmap.img_score_perfect);
+//            tv_layout_toast_score.setTextColor(Color.parseColor("#516AFF"));
+//        } else if (score >= 60) {
+//            iv_layout_toast_score.setImageResource(R.mipmap.img_score_great);
+//            tv_layout_toast_score.setTextColor(Color.parseColor("#FF8E5A"));
+//        } else if (score >= 40) {
+//            iv_layout_toast_score.setImageResource(R.mipmap.img_score_good);
+//            tv_layout_toast_score.setTextColor(Color.parseColor("#FF958B"));
+//        } else {
+//            iv_layout_toast_score.setImageResource(R.mipmap.img_score_bad);
+//            tv_layout_toast_score.setTextColor(Color.parseColor("#EE4C6A"));
+//        }
+//        if (tv_layout_toast_score != null) {
+//            tv_layout_toast_score.setText(score + "");
+//        }
+//        toast1.show();
+    }
+
+    /**
+     * 创建自定义Toasts :
+     */
+    public void showSpecialScore(Context context, int score, int x, int y) {
+//
+//        if(toast1 != null){
+//            toast1.cancel();
+//        }
+//        toast1 = null;
+//        toast1 = new Toast(context);
+//        View toastRoot = LayoutInflater.from(context).inflate(R.layout.layout_toast_score, null);
+//        toast1.setView(toastRoot);
+//        toast1.setGravity(Gravity.TOP | Gravity.LEFT, x, y);
+//        TextView tv_layout_toast_score = toastRoot.findViewById(R.id.tv_layout_toast_score);
+//        toast1.setDuration(Toast.LENGTH_SHORT);
+//        ImageView iv_layout_toast_score = toastRoot.findViewById(R.id.iv_layout_toast_score);
+//
+//        if (score >= 80) {
+//            iv_layout_toast_score.setImageResource(R.mipmap.img_score_perfect);
+//            tv_layout_toast_score.setTextColor(Color.parseColor("#516AFF"));
+//        } else if (score >= 60) {
+//            iv_layout_toast_score.setImageResource(R.mipmap.img_score_great);
+//            tv_layout_toast_score.setTextColor(Color.parseColor("#FF8E5A"));
+//        } else if (score >= 40) {
+//            iv_layout_toast_score.setImageResource(R.mipmap.img_score_good);
+//            tv_layout_toast_score.setTextColor(Color.parseColor("#FF958B"));
+//        } else {
+//            iv_layout_toast_score.setImageResource(R.mipmap.img_score_bad);
+//            tv_layout_toast_score.setTextColor(Color.parseColor("#EE4C6A"));
+//        }
+//        if (tv_layout_toast_score != null) {
+//            tv_layout_toast_score.setText(score + "");
+//        }
+//        toast1.show();
+    }
+}

+ 160 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/UiUtils.java

@@ -0,0 +1,160 @@
+package com.cooleshow.base.utils;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+import android.text.style.TextAppearanceSpan;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.cooleshow.base.widgets.span.QMUIAlignMiddleImageSpan;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Description:
+ * Copyright  : Copyright (c) 2019
+ * Company    : 大雅乐盟
+ * Author     : r
+ * Date       : 2019/8/22 15:08
+ */
+public class UiUtils {
+    private final static Handler mainHand = new Handler(Looper.getMainLooper());
+
+    public static void postDelayed(Runnable r, long delay) {
+        mainHand.postDelayed(r, delay);
+    }
+
+    public static void setMargins(View v, int l, int t, int r, int b) {
+        if (v.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
+            ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
+            p.setMargins(l, t, r, b);
+            v.requestLayout();
+        }
+    }
+
+    public static SpannableString matcherSearchText(Context context, String text, int keyword, int color) {
+        String key = keyword + "";
+        SpannableString ss = new SpannableString(text);
+        Pattern pattern = Pattern.compile(key);
+        Matcher matcher = pattern.matcher(ss);
+        while (matcher.find()) {
+            int start = matcher.start();
+            int end = matcher.end();
+            ss.setSpan(new TextAppearanceSpan(context, color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);//new ForegroundColorSpan(color)
+        }
+        return ss;
+    }
+
+    public static String formateTime(String dateString, String format) {
+        if (TextUtils.isEmpty(dateString)) return "";
+        try {
+            Date date = new SimpleDateFormat("yyyy-MM-dd").parse(dateString);
+            String ds = new SimpleDateFormat(format).format(date);
+            return ds;
+        } catch (ParseException e) {
+            return "";
+        }
+
+    }
+
+    public static String formateHourTime(String dateString, String format) {
+        if (TextUtils.isEmpty(dateString)) return "";
+        try {
+            Date date = new SimpleDateFormat("HH:mm:ss").parse(dateString);
+            String ds = new SimpleDateFormat(format).format(date);
+            return ds;
+        } catch (ParseException e) {
+            return "";
+        }
+
+    }
+
+    // 两次点击按钮之间的点击间隔不能少于1000毫秒
+    private static final int MIN_CLICK_DELAY_TIME = 1000;
+    private static long lastClickTime;
+
+    public static boolean isFastClick() {
+        boolean flag = false;
+        long curClickTime = System.currentTimeMillis();
+        if ((curClickTime - lastClickTime) >= MIN_CLICK_DELAY_TIME) {
+            flag = true;
+        }
+        lastClickTime = curClickTime;
+        return flag;
+    }
+
+    public static int getMaxHeightAtRatio16_9(Context context) {
+        float ratio = 16 / 9f;
+        return getMaxHeightAtAspectRatio(context, ratio);
+    }
+
+    public static int getMaxHeightAtAspectRatio(Context context, float ratio) {
+        DisplayMetrics deviceSize = DeviceUtil.getDeviceSize(context);
+        if (deviceSize != null) {
+            int width = deviceSize.widthPixels;
+            return (int) (width / ratio);
+        }
+        return 0;
+    }
+
+    public static SpannableString diffColorString(String bigSizeStr, String centerStr, String lastStr, int firstColor, int centerColor) {
+        String tmpStr = bigSizeStr + centerStr + lastStr;
+        SpannableString result = new SpannableString(tmpStr);
+        try{
+            result.setSpan(new ForegroundColorSpan(firstColor), 0, bigSizeStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            result.setSpan(new ForegroundColorSpan(centerColor), bigSizeStr.length(), bigSizeStr.length() + centerStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            result.setSpan(new ForegroundColorSpan(firstColor), bigSizeStr.length() + centerStr.length(), tmpStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    public static SpannableString diffColorString(String bigSizeStr, String lastStr, int firstColor, int lastColor) {
+        String tmpStr = bigSizeStr + lastStr;
+        SpannableString result = new SpannableString(tmpStr);
+        try {
+            result.setSpan(new ForegroundColorSpan(firstColor), 0, bigSizeStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            result.setSpan(new ForegroundColorSpan(lastColor), bigSizeStr.length(), tmpStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    public static SpannableString diffColorString(String bigSizeStr, String lastStr, int firstColor, int lastColor, Drawable iconDrawable) {
+        String tmpStr = bigSizeStr + lastStr;
+        try {
+            String icon = "[icon]";
+            SpannableString result = new SpannableString("[icon]" + tmpStr);
+
+            if (iconDrawable != null) {
+                iconDrawable.setBounds(0, 0, iconDrawable.getIntrinsicWidth(), iconDrawable.getIntrinsicHeight());
+            }
+            ImageSpan alignMiddleImageSpan = new QMUIAlignMiddleImageSpan(iconDrawable, QMUIAlignMiddleImageSpan.ALIGN_MIDDLE, 4);
+            result.setSpan(alignMiddleImageSpan, 0, icon.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+            result.setSpan(new ForegroundColorSpan(firstColor), icon.length(), icon.length() + bigSizeStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            result.setSpan(new ForegroundColorSpan(lastColor), icon.length() + bigSizeStr.length(), result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            return result;
+        } catch (Exception e) {
+            e.printStackTrace();
+            SpannableString spannableString = new SpannableString(tmpStr);
+            return spannableString;
+
+        }
+    }
+
+}

+ 114 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/ColorTextView.java

@@ -0,0 +1,114 @@
+package com.cooleshow.base.widgets;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+
+
+import com.cooleshow.base.R;
+
+import androidx.appcompat.widget.AppCompatTextView;
+
+
+public class ColorTextView extends AppCompatTextView {
+    private int ctvBackgroundColor;
+    private int cllBorderColor;
+    private int cllBorderWidth;
+    /**
+     * 圆角大小
+     */
+    private int mCornerSize;
+
+    /**
+     * 绘制时控制文本绘制的范围
+     */
+    private Rect mtitleBound;
+    private Paint mtitlePaint;
+
+    public ColorTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ColorTextView(Context context) {
+        this(context, null);
+    }
+
+    public void setCtvBackgroundColor(int ctvBackgroundColor) {
+        this.ctvBackgroundColor = ctvBackgroundColor;
+        postInvalidate();
+    }
+
+    /**
+     * 获得我自定义的样式属性
+     *
+     * @param context
+     * @param attrs
+     * @param defStyle
+     */
+    public ColorTextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        /**
+         * 获得我们所定义的自定义样式属性
+         */
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ColorTextView, defStyle, 0);
+        int n = a.getIndexCount();
+        for (int i = 0; i < n; i++) {
+            int attr = a.getIndex(i);
+            if (attr == R.styleable.ColorTextView_ctvBackground) {//默认为白色
+                ctvBackgroundColor = a.getColor(attr, Color.WHITE);
+            } else if (attr == R.styleable.ColorTextView_ctvCornerSize) {//默认圆角为0
+                mCornerSize = a.getDimensionPixelSize(attr, 0);
+            }
+            if (attr == R.styleable.ColorTextView_ctvBorderColor) {//默认圆角为0
+                cllBorderColor = a.getColor(attr, Color.WHITE);
+            }
+            if (attr == R.styleable.ColorTextView_ctvBorderWidth) {//默认圆角为0
+                cllBorderWidth = a.getDimensionPixelSize(attr, 0);
+            }
+        }
+
+        a.recycle();
+        mtitleBound = new Rect();
+        paint = new Paint(Paint.FILTER_BITMAP_FLAG);
+        paint.setAntiAlias(true);
+        paint.setColor(ctvBackgroundColor);
+        paint2 = new Paint(Paint.FILTER_BITMAP_FLAG);
+        paint2.setColor(cllBorderColor);
+        paint2.setAntiAlias(true);
+        paint2.setStrokeWidth(cllBorderWidth);
+        paint2.setStyle(Paint.Style.STROKE);
+    }
+
+    Paint paint;
+    Paint paint2;
+    RectF rec;
+    RectF rec2;
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        rec = new RectF(cllBorderWidth*1.0f/2, cllBorderWidth*1.0f/2,
+                getMeasuredWidth()-cllBorderWidth*1.0f/2,
+                getMeasuredHeight()-cllBorderWidth*1.0f/2);
+        rec2 = new RectF(cllBorderWidth*1.0f/2, cllBorderWidth*1.0f/2,
+                getMeasuredWidth()-cllBorderWidth*1.0f/2,
+                getMeasuredHeight()-cllBorderWidth*1.0f/2);
+    }
+    @Override
+    protected void onDraw(Canvas canvas) {
+        paint.setColor(ctvBackgroundColor);
+        paint2.setColor(cllBorderColor);
+        if (cllBorderWidth != 0) {
+            canvas.drawRoundRect(rec,mCornerSize,mCornerSize, paint);
+            canvas.drawRoundRect(rec2,mCornerSize,mCornerSize, paint2);
+        } else {
+            canvas.drawRoundRect(rec, mCornerSize, mCornerSize, paint);
+        }
+        super.onDraw(canvas);
+    }
+}

+ 2 - 1
BaseLibrary/src/main/java/com/cooleshow/base/widgets/EmptyViewLayout.java

@@ -15,7 +15,7 @@ import androidx.annotation.Nullable;
 /**
  * Author by pq, Date on 2022/5/5.
  */
-public class EmptyViewLayout extends FrameLayout {
+public class EmptyViewLayout extends LinearLayout {
 
     private ImageView mIvEmptyIcon;
     private TextView mTvEmptyTip;
@@ -34,6 +34,7 @@ public class EmptyViewLayout extends FrameLayout {
     }
 
     private void init() {
+        setOrientation(VERTICAL);
         LayoutInflater.from(getContext()).inflate(R.layout.empty_layout, this);
         mIvEmptyIcon = findViewById(R.id.iv_empty_icon);
         mTvEmptyTip = findViewById(R.id.tv_empty_tip);

+ 228 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/BaseDialogFragment.java

@@ -0,0 +1,228 @@
+package com.cooleshow.base.widgets.dialog;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.cooleshow.base.R;
+import com.cooleshow.base.utils.ScreenUtils;
+import com.cooleshow.base.utils.SizeUtils;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+
+/**
+ * Dialog通用样式
+ */
+public abstract class BaseDialogFragment extends DialogFragment {
+
+    private static final String MARGIN  = "margin";
+    private static final String WIDTH   = "width";
+    private static final String HEIGHT  = "height";
+    private static final String DIM     = "dim_amount";
+    private static final String GRAVITY = "gravity";
+    private static final String CANCEL  = "out_cancel";
+    private static final String THEME   = "theme";
+    private static final String ANIM    = "anim_style";
+    private static final String LAYOUT  = "layout_id";
+
+    private   int     margin;//左右边距
+    private   int     width;//宽度
+    private   int     height;//高度
+    private   float   dimAmount = 0.5f;//灰度深浅
+    private   int     gravity   = Gravity.CENTER;//显示的位置
+    private   boolean outCancel = true;//是否点击外部取消
+    @StyleRes
+    protected int     theme     = R.style.BaseDialog; // dialog主题
+    @StyleRes
+    private   int     animStyle;
+    @LayoutRes
+    protected int     layoutId;
+
+    public abstract int intLayoutId();
+
+    public abstract void convertView(DialogFragmentViewHolder holder, BaseDialogFragment dialog);
+
+    public int initTheme() {
+        return theme;
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setStyle(DialogFragment.STYLE_NO_TITLE, initTheme());
+
+        //恢复保存的数据
+        if (savedInstanceState != null) {
+            margin = savedInstanceState.getInt(MARGIN);
+            width = savedInstanceState.getInt(WIDTH);
+            height = savedInstanceState.getInt(HEIGHT);
+            dimAmount = savedInstanceState.getFloat(DIM);
+            gravity = savedInstanceState.getInt(GRAVITY);
+            outCancel = savedInstanceState.getBoolean(CANCEL);
+            theme = savedInstanceState.getInt(THEME);
+            animStyle = savedInstanceState.getInt(ANIM);
+            layoutId = savedInstanceState.getInt(LAYOUT);
+        }
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        layoutId = intLayoutId();
+        View view = inflater.inflate(layoutId, container, false);
+        convertView(DialogFragmentViewHolder.create(view), this);
+        return view;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        initParams();
+    }
+
+
+    /**
+     * 屏幕旋转等导致DialogFragment销毁后重建时保存数据
+     * @param outState
+     */
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(MARGIN, margin);
+        outState.putInt(WIDTH, width);
+        outState.putInt(HEIGHT, height);
+        outState.putFloat(DIM, dimAmount);
+        outState.putInt(GRAVITY, gravity);
+        outState.putBoolean(CANCEL, outCancel);
+        outState.putInt(THEME, theme);
+        outState.putInt(ANIM, animStyle);
+        outState.putInt(LAYOUT, layoutId);
+    }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        initParams();
+    }
+
+    private void initParams() {
+        Window window = getDialog().getWindow();
+        if (window != null) {
+            WindowManager.LayoutParams lp = window.getAttributes();
+            //调节灰色背景透明度[0-1],默认0.5f
+            lp.dimAmount = dimAmount;
+            if (gravity != 0) {
+                lp.gravity = gravity;
+            }
+            switch (gravity) {
+                case Gravity.LEFT:
+                case (Gravity.LEFT | Gravity.BOTTOM):
+                case (Gravity.LEFT | Gravity.TOP):
+                    if (animStyle == 0) {
+                        animStyle = R.style.LeftAnimation;
+                    }
+                    break;
+                case Gravity.TOP:
+                    if (animStyle == 0) {
+                        animStyle = R.style.TopAnimation;
+                    }
+                    break;
+                case Gravity.RIGHT:
+                case (Gravity.RIGHT | Gravity.BOTTOM):
+                case (Gravity.RIGHT | Gravity.TOP):
+                    if (animStyle == 0) {
+                        animStyle = R.style.RightAnimation;
+                    }
+                    break;
+                case Gravity.BOTTOM:
+                    if (animStyle == 0) {
+                        animStyle = R.style.BottomAnimation;
+                    }
+                    break;
+                default:
+                    break;
+
+            }
+
+            //设置dialog宽度
+            if (width == 0) {
+                lp.width = ScreenUtils.getScreenWidth() - 2 * SizeUtils.dp2px(margin);
+            } else if (width == -1) {
+                lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
+            } else {
+                lp.width = SizeUtils.dp2px(width);
+            }
+
+            //设置dialog高度
+            if (height == 0) {
+                lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            } else {
+                lp.height = SizeUtils.dp2px(height);
+            }
+
+            //设置dialog进入、退出的动画
+            window.setWindowAnimations(animStyle);
+            window.setAttributes(lp);
+        }
+        setCancelable(outCancel);
+    }
+
+    public BaseDialogFragment setMargin(int margin) {
+        this.margin = margin;
+        return this;
+    }
+
+    public BaseDialogFragment setWidth(int width) {
+        this.width = width;
+        return this;
+    }
+
+    public BaseDialogFragment setHeight(int height) {
+        this.height = height;
+        return this;
+    }
+
+    public BaseDialogFragment setDimAmount(float dimAmount) {
+        this.dimAmount = dimAmount;
+        return this;
+    }
+
+    public BaseDialogFragment setGravity(int gravity) {
+        this.gravity = gravity;
+        return this;
+    }
+
+    public BaseDialogFragment setOutCancel(boolean outCancel) {
+        this.outCancel = outCancel;
+        return this;
+    }
+
+    public BaseDialogFragment setAnimStyle(@StyleRes int animStyle) {
+        this.animStyle = animStyle;
+        return this;
+    }
+
+    public BaseDialogFragment show(FragmentManager manager) {
+
+        FragmentTransaction ft = manager.beginTransaction();
+        if (this.isAdded()) {
+            ft.remove(this).commit();
+        }
+        ft.add(this, String.valueOf(System.currentTimeMillis()));
+        ft.addToBackStack(null); // 为解决该问题  Can not perform this action after onSaveInstanceState
+        ft.commitAllowingStateLoss();
+        return this;
+    }
+
+}

+ 60 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/CommonDialog.java

@@ -0,0 +1,60 @@
+package com.cooleshow.base.widgets.dialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import com.cooleshow.base.R;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Author by pq, Date on 2022/5/6.
+ */
+public class CommonDialog extends Dialog {
+
+    private TextView mTvTitle;
+    private TextView mTvContent;
+    private TextView mBtncancel;
+    private TextView mBtnCommit;
+
+    public CommonDialog(@NonNull Context context) {
+        super(context, R.style.DialogStyle);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.common_popu);
+        mTvTitle = findViewById(R.id.tv_title);
+        mTvContent = findViewById(R.id.tv_content);
+        mBtncancel = findViewById(R.id.btn_cancel);
+        mBtnCommit = findViewById(R.id.btn_commit);
+    }
+
+    public void setTitle(String title) {
+        if (mTvTitle != null) {
+            mTvTitle.setText(title);
+        }
+    }
+
+    public void setContent(String content) {
+        if (mTvContent != null) {
+            mTvContent.setText(content);
+        }
+    }
+
+    public void setOnConfirmClickListener(View.OnClickListener onConfirmClickListener) {
+        if (mBtnCommit != null) {
+            mBtnCommit.setOnClickListener(onConfirmClickListener);
+        }
+    }
+
+    public void setOnCancelClickListener(View.OnClickListener onCancelClickListener) {
+        if (mBtncancel != null) {
+            mBtncancel.setOnClickListener(onCancelClickListener);
+        }
+    }
+}

+ 64 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/DialogFragmentViewHolder.java

@@ -0,0 +1,64 @@
+package com.cooleshow.base.widgets.dialog;
+
+import android.util.SparseArray;
+import android.view.View;
+import android.widget.TextView;
+
+public class DialogFragmentViewHolder {
+
+    private SparseArray<View> views;
+    private View convertView;
+
+    private DialogFragmentViewHolder(View view) {
+        convertView = view;
+        views = new SparseArray<>();
+    }
+
+    public static DialogFragmentViewHolder create(View view) {
+        return new DialogFragmentViewHolder(view);
+    }
+
+    public <T extends View> T getView(int viewId) {
+        View view = views.get(viewId);
+        if (view == null) {
+            view = convertView.findViewById(viewId);
+            views.put(viewId, view);
+        }
+        return (T) view;
+    }
+
+    public View getConvertView() {
+        return convertView;
+    }
+
+    public void setText(int viewId, String text) {
+        TextView textView = getView(viewId);
+        textView.setText(text);
+    }
+
+    public void setText(int viewId, int textId) {
+        TextView textView = getView(viewId);
+        textView.setText(textId);
+    }
+
+    public void setTextColor(int viewId, int colorId) {
+        TextView textView = getView(viewId);
+        textView.setTextColor(colorId);
+    }
+
+    public void setOnClickListener(int viewId, View.OnClickListener clickListener) {
+        View view = getView(viewId);
+        view.setOnClickListener(clickListener);
+    }
+
+    public void setBackgroundResource(int viewId, int resId) {
+        View view = getView(viewId);
+        view.setBackgroundResource(resId);
+    }
+
+    public void setBackgroundColor(int viewId, int colorId) {
+        View view = getView(viewId);
+        view.setBackgroundColor(colorId);
+    }
+    }
+ 

+ 137 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/span/QMUIAlignMiddleImageSpan.java

@@ -0,0 +1,137 @@
+/*
+ * Tencent is pleased to support the open source community by making QMUI_Android available.
+ *
+ * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the MIT License (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://opensource.org/licenses/MIT
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cooleshow.base.widgets.span;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.style.ImageSpan;
+
+import androidx.annotation.NonNull;
+
+/**
+ * 支持垂直居中的ImageSpan
+ *
+ * @author cginechen
+ * @date 2016-03-17
+ */
+public class QMUIAlignMiddleImageSpan extends ImageSpan  {
+
+    public static final int ALIGN_MIDDLE = -100; // 不要和父类重复
+
+    /**
+     * 规定这个Span占几个字的宽度
+     */
+    private float mFontWidthMultiple = -1f;
+
+    /**
+     * 是否避免父类修改FontMetrics,如果为 false 则会走父类的逻辑, 会导致FontMetrics被更改
+     */
+    private boolean mAvoidSuperChangeFontMetrics = false;
+
+    @SuppressWarnings("FieldCanBeLocal") private int mWidth;
+    private Drawable mDrawable;
+    private int mDrawableTintColorAttr;
+
+    /**
+     * @param d                 作为 span 的 Drawable
+     * @param verticalAlignment 垂直对齐方式, 如果要垂直居中, 则使用 {@link #ALIGN_MIDDLE}
+     */
+    public QMUIAlignMiddleImageSpan(Drawable d, int verticalAlignment) {
+        this(d, verticalAlignment, 0);
+    }
+
+    /**
+     * @param d                 作为 span 的 Drawable
+     * @param verticalAlignment 垂直对齐方式, 如果要垂直居中, 则使用 {@link #ALIGN_MIDDLE}
+     * @param fontWidthMultiple 设置这个Span占几个中文字的宽度, 当该值 > 0 时, span 的宽度为该值*一个中文字的宽度; 当该值 <= 0 时, span 的宽度由 {@link #mAvoidSuperChangeFontMetrics} 决定
+     */
+    public QMUIAlignMiddleImageSpan(@NonNull Drawable d, int verticalAlignment, float fontWidthMultiple) {
+        super(d.mutate(), verticalAlignment);
+        mDrawable = getDrawable();
+        if (fontWidthMultiple >= 0) {
+            mFontWidthMultiple = fontWidthMultiple;
+        }
+    }
+
+//    public void setSkinSupportWithTintColor(View skinFollowView, int drawableTintColorAttr) {
+//        mDrawableTintColorAttr = drawableTintColorAttr;
+//        if (mDrawable != null && skinFollowView != null && drawableTintColorAttr != 0) {
+//            QMUIDrawableHelper.setDrawableTintColor(mDrawable,
+//                    QMUISkinHelper.getSkinColor(skinFollowView, drawableTintColorAttr));
+//            skinFollowView.invalidate();
+//        }
+//    }
+
+    @Override
+    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
+        if (mAvoidSuperChangeFontMetrics) {
+            Drawable d = getDrawable();
+            Rect rect = d.getBounds();
+            mWidth = rect.right;
+        } else {
+            mWidth = super.getSize(paint, text, start, end, fm);
+        }
+        if (mFontWidthMultiple > 0) {
+            mWidth = (int) (paint.measureText("子") * mFontWidthMultiple);
+        }
+        return mWidth;
+    }
+
+    @Override
+    public void draw(Canvas canvas, CharSequence text, int start, int end,
+                     float x, int top, int y, int bottom, Paint paint) {
+        if (mVerticalAlignment == ALIGN_MIDDLE) {
+            Drawable d = mDrawable;
+            canvas.save();
+
+//            // 注意如果这样实现会有问题:TextView 有 lineSpacing 时,这里 bottom 偏大,导致偏下
+//            int transY = bottom - d.getBounds().bottom; // 底对齐
+//            transY -= (paint.getFontMetricsInt().bottom - paint.getFontMetricsInt().top) / 2 - d.getBounds().bottom / 2; // 居中对齐
+//            canvas.translate(x, transY);
+//            d.draw(canvas);
+//            canvas.restore();
+
+            Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
+            int fontTop = y + fontMetricsInt.top;
+            int fontMetricsHeight = fontMetricsInt.bottom - fontMetricsInt.top;
+            int iconHeight = d.getBounds().bottom - d.getBounds().top;
+            int iconTop = fontTop + (fontMetricsHeight - iconHeight) / 2;
+            canvas.translate(x, iconTop);
+            d.draw(canvas);
+            canvas.restore();
+        } else {
+            super.draw(canvas, text, start, end, x, top, y, bottom, paint);
+        }
+    }
+
+    /**
+     * 是否避免父类修改FontMetrics,如果为 false 则会走父类的逻辑, 会导致FontMetrics被更改
+     */
+    public void setAvoidSuperChangeFontMetrics(boolean avoidSuperChangeFontMetrics) {
+        mAvoidSuperChangeFontMetrics = avoidSuperChangeFontMetrics;
+    }
+
+//    @Override
+//    public void handle(@NotNull View view, @NotNull QMUISkinManager manager, int skinIndex, @NotNull Resources.Theme theme) {
+//        if (mDrawableTintColorAttr != 0) {
+//            QMUIDrawableHelper.setDrawableTintColor(mDrawable,
+//                    QMUIResHelper.getAttrColor(theme, mDrawableTintColorAttr));
+//        }
+//    }
+}

+ 85 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/transformation/BlurTransformation.java

@@ -0,0 +1,85 @@
+package com.cooleshow.base.widgets.transformation;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+import com.bumptech.glide.util.Util;
+
+import java.security.MessageDigest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+/**
+ * @author sunfusheng on 2018/6/25.
+ */
+public class BlurTransformation extends BitmapTransformation {
+    private final String ID = getClass().getName();
+
+    private static int MAX_RADIUS = 25;
+    private static int DEFAULT_SAMPLING = 1;
+
+    private Context context;
+    private int radius; //模糊半径0~25
+    private int sampling; //取样0~25
+
+    public BlurTransformation(Context context) {
+        this(context, MAX_RADIUS, DEFAULT_SAMPLING);
+    }
+
+    public BlurTransformation(Context context, int radius) {
+        this(context, radius, DEFAULT_SAMPLING);
+    }
+
+    public BlurTransformation(Context context, int radius, int isamplng) {
+        this.context = context;
+        this.radius = radius > MAX_RADIUS ? MAX_RADIUS : radius;
+        this.sampling = sampling > MAX_RADIUS ? MAX_RADIUS : sampling;
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+    @Override
+    protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
+        int width = toTransform.getWidth();
+        int height = toTransform.getHeight();
+        int scaledWidth = width / sampling;
+        int scaledHeight = height / sampling;
+
+        Bitmap bitmap = pool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        canvas.scale(1 / (float) sampling, 1 / (float) sampling);
+        Paint paint = new Paint();
+        paint.setFlags(Paint.FILTER_BITMAP_FLAG);
+        canvas.drawBitmap(toTransform, 0, 0, paint);
+//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+//            bitmap = BlurUtils.rsBlur(context, bitmap, radius);
+//        } else {
+//            bitmap = BlurUtils.blur(bitmap, radius);
+//        }
+        return bitmap;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof BlurTransformation) {
+            BlurTransformation other = (BlurTransformation) obj;
+            return radius == other.radius && sampling == other.sampling;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Util.hashCode(ID.hashCode(), Util.hashCode(radius, Util.hashCode(sampling)));
+    }
+
+    @Override
+    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
+        messageDigest.update((ID + radius * 10 + sampling).getBytes(CHARSET));
+    }
+}

+ 57 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/transformation/CircleTransformation.java

@@ -0,0 +1,57 @@
+package com.cooleshow.base.widgets.transformation;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+import com.bumptech.glide.util.Util;
+
+import java.security.MessageDigest;
+
+import androidx.annotation.NonNull;
+
+/**
+ * @author by sunfusheng on 2017/6/6.
+ */
+public class CircleTransformation extends BitmapTransformation {
+    private final String ID = getClass().getName();
+
+    @Override
+    protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
+        int size = Math.min(toTransform.getWidth(), toTransform.getHeight());
+        int x = (toTransform.getWidth() - size) / 2;
+        int y = (toTransform.getHeight() - size) / 2;
+
+        Bitmap square = Bitmap.createBitmap(toTransform, x, y, size, size);
+        Bitmap circle = pool.get(size, size, Bitmap.Config.ARGB_8888);
+
+        Canvas canvas = new Canvas(circle);
+        Paint paint = new Paint();
+        paint.setShader(new BitmapShader(square, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
+        paint.setAntiAlias(true);
+        float r = size / 2f;
+        canvas.drawCircle(r, r, r, paint);
+        return circle;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof BlurTransformation) {
+            return this == obj;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Util.hashCode(ID.hashCode());
+    }
+
+    @Override
+    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
+        messageDigest.update(ID.getBytes(CHARSET));
+    }
+}

+ 90 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/transformation/RadiusTransformation.java

@@ -0,0 +1,90 @@
+package com.cooleshow.base.widgets.transformation;
+
+import android.content.Context;
+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 com.bumptech.glide.load.resource.bitmap.TransformationUtils;
+import com.bumptech.glide.util.Util;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+
+import androidx.annotation.NonNull;
+
+
+/**
+ * @author by sunfusheng on 2017/6/6.
+ */
+public class RadiusTransformation extends BitmapTransformation {
+    private static final String ID       = "com.bumptech.glide.transformations.GlideRoundTransform";
+    private static final byte[] ID_BYTES = ID.getBytes(Charset.forName("UTF-8"));
+    private              float  radius   = 0f;
+
+    public RadiusTransformation(Context context) {
+        this(context, 5);
+    }
+
+    public RadiusTransformation(Context context, int dp) {
+        super();
+        this.radius = Resources.getSystem().getDisplayMetrics().density * dp;
+    }
+
+    @Override
+    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
+        Bitmap bitmap = TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight);
+        return roundCrop(pool, bitmap);
+    }
+
+    private 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;
+    }
+
+    public String getId() {
+        return getClass().getName() + Math.round(radius);
+    }
+
+    //下面三个方法需要实现,不然会出现刷新闪烁
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof RadiusTransformation) {
+            RadiusTransformation other = (RadiusTransformation) o;
+            return radius == other.radius;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Util.hashCode(ID.hashCode(), Util.hashCode(radius));
+    }
+
+    @Override
+    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
+        messageDigest.update(ID_BYTES);
+
+        byte[] radiusData = ByteBuffer.allocate(4).putFloat(radius).array();
+        messageDigest.update(radiusData);
+    }
+}

+ 11 - 0
BaseLibrary/src/main/res/anim/bottom_enter_anim.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="200">
+    <translate
+        android:fromYDelta="100%p"
+        android:toYDelta="0%p" />
+
+    <alpha
+        android:fromAlpha="0.0"
+        android:toAlpha="1.0" />
+</set>

+ 13 - 0
BaseLibrary/src/main/res/anim/bottom_exit_anim.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="200">
+    <translate
+        android:duration="@android:integer/config_mediumAnimTime"
+        android:fromYDelta="0%p"
+        android:toYDelta="100%p" />
+
+    <alpha
+        android:duration="@android:integer/config_mediumAnimTime"
+        android:fromAlpha="1.0"
+        android:toAlpha="0.3" />
+</set>

+ 11 - 0
BaseLibrary/src/main/res/anim/left_enter_anim.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="200">
+    <translate
+        android:fromXDelta="-100%p"
+        android:toXDelta="0%p" />
+
+    <alpha
+        android:fromAlpha="0.0"
+        android:toAlpha="1.0" />
+</set>

+ 13 - 0
BaseLibrary/src/main/res/anim/left_exit_anim.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="200">
+    <translate
+        android:duration="@android:integer/config_mediumAnimTime"
+        android:fromXDelta="0%p"
+        android:toXDelta="-100%p" />
+
+    <alpha
+        android:duration="@android:integer/config_mediumAnimTime"
+        android:fromAlpha="1.0"
+        android:toAlpha="0.3" />
+</set>

+ 11 - 0
BaseLibrary/src/main/res/anim/right_enter_anim.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="200">
+    <translate
+        android:fromXDelta="100%p"
+        android:toXDelta="0%p" />
+
+    <alpha
+        android:fromAlpha="0.0"
+        android:toAlpha="1.0" />
+</set>

+ 13 - 0
BaseLibrary/src/main/res/anim/right_exit_anim.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="200">
+    <translate
+        android:duration="@android:integer/config_mediumAnimTime"
+        android:fromXDelta="0%p"
+        android:toXDelta="100%p" />
+
+    <alpha
+        android:duration="@android:integer/config_mediumAnimTime"
+        android:fromAlpha="1.0"
+        android:toAlpha="0.3" />
+</set>

BIN
BaseLibrary/src/main/res/drawable-xxhdpi/ic_stu_student_head.png


BIN
BaseLibrary/src/main/res/drawable-xxhdpi/ic_stu_teacher_head.png


BIN
BaseLibrary/src/main/res/drawable/bg_loading.9.png


+ 7 - 0
BaseLibrary/src/main/res/drawable/bg_white_5dp.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/white" />
+    <corners
+        android:radius="5dp" />
+</shape>

+ 6 - 0
BaseLibrary/src/main/res/drawable/btn_gray_start_bottom_shape.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/gray_EEEFF3" />
+    <corners android:bottomLeftRadius="@dimen/dp_5" />
+</shape>

+ 6 - 0
BaseLibrary/src/main/res/drawable/btn_green_end_bottom_shape.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/colorPrimary" />
+    <corners android:bottomRightRadius="@dimen/dp_5" />
+</shape>

+ 6 - 0
BaseLibrary/src/main/res/drawable/btn_green_stu_line_shape.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="@color/colorPrimaryStudent"/>
+    <corners android:radius="5dp"/>
+</shape>

+ 5 - 0
BaseLibrary/src/main/res/drawable/shape_toast.xml

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

+ 136 - 0
BaseLibrary/src/main/res/layout/common_popu.xml

@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/dp_32"
+        android:layout_marginEnd="@dimen/dp_32"
+        android:background="@drawable/bg_white_5dp">
+
+
+        <ImageView
+            android:id="@+id/iv_icon"
+            android:layout_width="4dp"
+            android:layout_height="@dimen/dp_18"
+            android:layout_marginStart="@dimen/dp_20"
+            android:layout_marginTop="@dimen/dp_24"
+            android:background="@drawable/btn_green_stu_line_shape"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <TextView
+            android:id="@+id/tv_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/dp_16"
+            android:layout_marginEnd="@dimen/dp_16"
+            android:text="确定退课吗?"
+            android:textColor="@color/black_444"
+            android:textSize="@dimen/dp_18"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toBottomOf="@id/iv_icon"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/iv_icon"
+            app:layout_constraintTop_toTopOf="@+id/iv_icon" />
+
+        <TextView
+            android:id="@+id/tv_content"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/dp_22"
+            android:lineSpacingExtra="10dp"
+            android:text=""
+            android:textColor="@color/black_444"
+            android:textSize="@dimen/dp_16"
+            app:layout_constraintEnd_toEndOf="@+id/tv_title"
+            app:layout_constraintStart_toStartOf="@+id/iv_icon"
+            app:layout_constraintTop_toBottomOf="@+id/iv_icon" />
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recyclerView"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_marginTop="@dimen/dp_15"
+            android:background="@color/white"
+            android:overScrollMode="never"
+            android:scrollbars="none"
+            android:visibility="gone"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/tv_content"
+            app:layout_goneMarginTop="@dimen/dp_15" />
+
+        <View
+            android:id="@+id/horizontal_line"
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:layout_marginTop="@dimen/dp_22"
+            android:background="@color/divide_color"
+            android:visibility="invisible"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/recyclerView" />
+
+        <TextView
+            android:id="@+id/btn_cancel"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/dp_50"
+            android:background="@drawable/btn_gray_start_bottom_shape"
+            android:gravity="center"
+            android:text="取消"
+            android:textColor="@color/colorPrimaryStudent"
+            android:textSize="@dimen/dp_14"
+            app:layout_constraintEnd_toStartOf="@id/vertical_line"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            app:layout_constraintHorizontal_weight="1"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/horizontal_line"
+            app:layout_goneMarginTop="@dimen/dp_15" />
+
+        <View
+            android:id="@+id/vertical_line"
+            android:layout_width="@dimen/dp_1"
+            android:layout_height="@dimen/dp_50"
+            android:background="@color/divide_color"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/btn_commit"
+            app:layout_constraintStart_toEndOf="@id/btn_cancel"
+            app:layout_constraintTop_toTopOf="@id/btn_cancel" />
+
+        <TextView
+            android:id="@+id/btn_commit"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/dp_50"
+            android:background="@drawable/btn_green_end_bottom_shape"
+            android:gravity="center"
+            android:text="确定"
+            android:textColor="@color/white"
+            android:textSize="@dimen/dp_14"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_weight="1"
+            app:layout_constraintStart_toEndOf="@id/vertical_line"
+            app:layout_constraintTop_toBottomOf="@+id/horizontal_line"
+            app:layout_goneMarginTop="@dimen/dp_15" />
+
+        <TextView
+            android:id="@+id/btn_cancel_match"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/dp_50"
+            android:background="@color/white"
+            android:gravity="center"
+            android:text="取消"
+            android:textColor="@color/colorPrimaryStudent"
+            android:textSize="@dimen/dp_14"
+            android:visibility="gone"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/horizontal_line"
+            app:layout_goneMarginTop="@dimen/dp_15" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</LinearLayout>

+ 2 - 2
BaseLibrary/src/main/res/layout/empty_layout.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:gravity="center"
     android:orientation="vertical"
@@ -16,4 +16,4 @@
         android:textSize="@dimen/sp_14"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
-</LinearLayout>
+</merge>

+ 16 - 0
BaseLibrary/src/main/res/layout/my_toast.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@+id/TextViewInfo"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_gravity="center_vertical"
+          android:background="@drawable/shape_toast"
+          android:paddingStart="39dp"
+          android:paddingTop="@dimen/dp_11"
+          android:lineSpacingExtra="@dimen/dp_5"
+          android:paddingEnd="38dp"
+          android:paddingBottom="@dimen/dp_10"
+          android:text="验证码错误"
+          android:textColor="@color/white"
+          android:textSize="16dp"
+    />

+ 7 - 0
BaseLibrary/src/main/res/values/attrs.xml

@@ -13,6 +13,13 @@
         <attr name="contentText" format="string"/>
     </declare-styleable>
 
+    <declare-styleable name="ColorTextView">
+        <attr name="ctvBackground" format="color"/>
+        <attr name="ctvCornerSize" format="dimension"/>
+        <attr name="ctvBorderColor" format="color"/>
+        <attr name="ctvBorderWidth" format="dimension"/>
+    </declare-styleable>
+
     <declare-styleable name="QMUIRadiusImageView">
         <attr name="qmui_border_width" format="dimension"/>
         <attr name="qmui_border_color" format="color"/>

+ 2 - 0
BaseLibrary/src/main/res/values/colors.xml

@@ -19,6 +19,8 @@
     <color name="black_444">#ff444444</color>
     <color name="gray_777">#777777</color>
     <color name="white">#ffffff</color>
+    <color name="gray_EEEFF3">#EEEFF3</color>
+    <color name="black_80">#80000000</color>
 
     <color name="text_dark">#999999</color>
     <color name="text_light_dark">#333333</color>

+ 26 - 0
BaseLibrary/src/main/res/values/styles.xml

@@ -260,4 +260,30 @@
         <!-- 半透明 -->
         <item name="android:windowIsTranslucent">true</item>
     </style>
+
+    <style name="BaseDialog" parent="Theme.AppCompat.Light.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowSoftInputMode">adjustPan</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
+
+
+    <style name="BottomAnimation" parent="android:Animation">
+        <item name="android:windowEnterAnimation">@anim/bottom_enter_anim</item>
+        <item name="android:windowExitAnimation">@anim/bottom_exit_anim</item>
+    </style>
+
+    <style name="RightAnimation" parent="android:Animation">
+        <item name="android:windowEnterAnimation">@anim/right_enter_anim</item>
+        <item name="android:windowExitAnimation">@anim/right_exit_anim</item>
+    </style>
+
+    <style name="LeftAnimation" parent="android:Animation">
+        <item name="android:windowEnterAnimation">@anim/left_enter_anim</item>
+        <item name="android:windowExitAnimation">@anim/left_exit_anim</item>
+    </style>
+
 </resources>

+ 2 - 2
app/build.gradle

@@ -65,6 +65,6 @@ dependencies {
     implementation project(path: ':BaseLibrary')
     api project(path: ':usercenter')
 
-    implementation "com.alibaba:arouter-api:$arouter_api_version"
-    kapt "com.alibaba:arouter-compiler:$arouter_api_version"
+    implementation "com.alibaba:arouter-api:$rootProject.ext.android.arouter_api_version"
+    kapt "com.alibaba:arouter-compiler:$rootProject.ext.android.arouter_api_version"
 }

+ 2 - 23
build.gradle

@@ -1,25 +1,5 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
+apply from: "config.gradle"
 buildscript {
-    ext.kotlin_version = '1.5.31'
-    ext.anko_version= '0.10.8'
-    ext.rx_lifecycle_version = '2.2.1'
-    ext.glide_version = "3.7.0"
-
-//ext.ok_http_version = '4.9.3'
-    ext.ok_http_version = '3.4.1'
-    ext.retrofit_version = '2.1.0'
-
-    ext.rx_kotlin_version = '2.0.0'
-    ext.rx_android_version ='1.2.1'
-
-    ext.glide_version = '4.13.0'
-    ext.dagger_version = '2.41'
-
-    ext.multi_state_view_version = '2.2.0'
-
-    ext.arouter_api_version = '1.5.2'
-
 
     repositories {
         google()
@@ -38,8 +18,7 @@ buildscript {
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:4.2.0'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-//        classpath "com.alibaba:arouter-register:1.0.2"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
     }
 }
 

+ 38 - 0
config.gradle

@@ -0,0 +1,38 @@
+/**
+ 引用步骤:
+ (1) 在 project 下的 build.gradle 文件第一行添加 apply from : "config.gradle"
+ (2) 在各个 module 中用 implemetions rootProject.ext.dependencies['xxx'] 和 annotationProcessor rootProject.ext..dependencies['xxx']
+ **/
+ext {
+    android = [
+            stuApplicationId        : 'com.daya.studaya_android',
+            teapplicationId         : 'com.dayayuemeng.teacher',
+            compileSdkVersion       : 31,
+            buildToolsVersion       : "29.0.0",
+            minSdkVersion           : 21,
+            targetSdkVersion        : 29,
+
+            versionCode             : 357,
+            studentVersionName      : "3.5.7",
+            teacherVersionName      : "3.5.7",
+
+
+            kotlin_version          : '1.5.31',
+            anko_version            : '0.10.8',
+            rx_lifecycle_version    : '2.2.1',
+            glide_version           : "3.7.0",
+
+            ok_http_version         : '3.4.1',
+            retrofit_version        : '2.1.0',
+
+            rx_kotlin_version       : '2.0.0',
+            rx_android_version      : '1.2.1',
+
+            glide_version           : '4.13.0',
+            dagger_version          : '2.41',
+
+            multi_state_view_version: '2.2.0',
+
+            arouter_api_version     : '1.5.2',
+    ]
+}

+ 1 - 0
live_teaching/.gitignore

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

+ 76 - 0
live_teaching/build.gradle

@@ -0,0 +1,76 @@
+apply plugin: 'com.android.library'
+android {
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+    defaultConfig {
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
+        versionCode rootProject.ext.android.versionCode
+        versionName rootProject.ext.android.versionName
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        ndk {
+            abiFilters "armeabi-v7a", "arm64-v8a"
+        }
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments = [AROUTER_MODULE_NAME: project.getName()]
+                includeCompileClasspath = true
+            }
+        }
+        multiDexEnabled true
+        flavorDimensions "app"//多渠道打包维度不同问题
+    }
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    buildTypes {
+        release {
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    sourceSets {
+        main {
+            //控制两种模式下的资源和代码配置情况
+            manifest.srcFile 'src/main/AndroidManifest.xml'
+            //集成开发模式下排除debug文件夹中的所有Java文件
+            java {
+                exclude 'debug/**'
+            }
+
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+
+    buildFeatures{
+        viewBinding = true
+    }
+}
+
+dependencies {
+    api fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'com.google.android.material:material:1.4.0'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test:runner:1.2.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+    implementation 'androidx.lifecycle:lifecycle-livedata:2.1.0-alpha04'
+    implementation 'androidx.lifecycle:lifecycle-viewmodel:2.1.0-alpha04'
+    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
+    implementation 'com.github.duty-os:white-sdk-android:2.11.2'
+    api project(path: ':rong_im:kit')
+    api project(path: ':rong_im:live')
+    api project(path: ':rong_im:sight')
+    implementation project(path: ':usercenter')
+    implementation project(path: ':BaseLibrary')
+    implementation 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:1.0.0'
+
+}

+ 37 - 0
live_teaching/proguard-rules.pro

@@ -0,0 +1,37 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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 *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+-keepattributes Exceptions,InnerClasses
+
+-keepattributes Signature
+#RongRTCLib
+-keep public class cn.rongcloud.** {*;}
+
+#RongIMLib
+-keep class io.rong.** {*;}
+-keep class cn.rongcloud.** {*;}
+-keep class * implements io.rong.imlib.model.MessageContent {*;}
+-dontwarn io.rong.push.**
+-dontnote com.xiaomi.**
+-dontnote com.google.android.gms.gcm.**
+-dontnote io.rong.**
+
+-ignorewarnings

BIN
live_teaching/rong.key


+ 26 - 0
live_teaching/src/androidTest/java/com/daya/live_teaching/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.daya.live_teaching;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("com.daya.live_teaching.test", appContext.getPackageName());
+    }
+}

+ 27 - 0
live_teaching/src/main/AndroidManifest.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.daya.live_teaching">
+
+    <application>
+
+        <activity
+            android:name=".ui.LiveActivity"
+            android:configChanges="orientation|screenSize|keyboardHidden"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustResize"
+            tools:ignore="LockedOrientationActivity"></activity>
+    </application>
+
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+
+</manifest>

+ 65 - 0
live_teaching/src/main/java/com/daya/live_teaching/LiveTeachingApp.java

@@ -0,0 +1,65 @@
+package com.daya.live_teaching;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Process;
+
+import com.cooleshow.base.common.BaseApplication;
+import com.daya.live_teaching.im.IMManager;
+import com.daya.live_teaching.utils.log.SLog;
+
+import java.util.Iterator;
+import java.util.List;
+
+
+public class LiveTeachingApp extends BaseApplication {
+    private static LiveTeachingApp instance;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        instance = this;
+        // 初始化日志打印
+        SLog.init(this);
+        if (!getApplicationInfo().packageName.equals(getCurProcessName(getApplicationContext()))) {
+            return;
+        }
+        IMManager.init(this,"c9kqb3rdc451j");
+    }
+
+
+    /**
+     * 获取当前进程的名称
+     * @param context
+     * @return
+     */
+    public String getCurProcessName(Context context) {
+        int pid = Process.myPid();
+        ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfoList = mActivityManager.getRunningAppProcesses();
+        if (runningAppProcessInfoList == null) {
+            return null;
+        } else {
+            Iterator processInfoIterator = runningAppProcessInfoList.iterator();
+
+            ActivityManager.RunningAppProcessInfo appProcess;
+            do {
+                if (!processInfoIterator.hasNext()) {
+                    return null;
+                }
+
+                appProcess = (ActivityManager.RunningAppProcessInfo) processInfoIterator.next();
+            } while (appProcess.pid != pid);
+
+            return appProcess.processName;
+        }
+    }
+
+    /**
+     * 获取当前 Application 实例
+     * @return
+     */
+    public static LiveTeachingApp getApplication() {
+        return instance;
+    }
+}

+ 12 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/ApiConstant.java

@@ -0,0 +1,12 @@
+package com.daya.live_teaching.api;
+
+
+public class ApiConstant {
+    public final static int CONNECT_TIME_OUT = 10;
+    public final static int READ_TIME_OUT = 10;
+    public final static int WRITE_TIME_OUT = 10;
+
+    public final static String SP_NAME_NET = "net";
+    public final static String SP_KEY_NET_COOKIE_SET = "cookie_set";
+    public final static String SP_KEY_NET_HEADER_AUTH = "header_auth";
+}

+ 70 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/HttpClientManager.java

@@ -0,0 +1,70 @@
+package com.daya.live_teaching.api;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.daya.live_teaching.api.retrofit.RetrofitClient;
+
+import static android.content.Context.MODE_PRIVATE;
+
+
+public class HttpClientManager {
+    private static final String            TAG = "HttpClientManager";
+    private static       HttpClientManager instance;
+    private              Context           context;
+    private              RetrofitClient    client;
+
+    private HttpClientManager(Context context) {
+        this.context = context;
+        client = new RetrofitClient(context, LiveTeachingUrls.DOMAIN);
+    }
+
+    public static HttpClientManager getInstance(Context context) {
+        if (instance == null) {
+            synchronized (HttpClientManager.class) {
+                if (instance == null) {
+                    instance = new HttpClientManager(context);
+                }
+            }
+        }
+
+        return instance;
+    }
+
+    public RetrofitClient getClient() {
+        return client;
+    }
+
+    /**
+     * 设置用户登录认证
+     *
+     * @param auth
+     */
+    public void setAuthHeader(String auth) {
+        SharedPreferences.Editor config = context.getSharedPreferences(ApiConstant.SP_NAME_NET, MODE_PRIVATE)
+                .edit();
+        config.putString(ApiConstant.SP_KEY_NET_HEADER_AUTH, auth);
+        config.commit();
+    }
+
+    /**
+     * 获取用户登录认证
+     *
+     * @return
+     */
+    public String getCurrentAuth() {
+        SharedPreferences sharedPreferences = context.getSharedPreferences(ApiConstant.SP_NAME_NET, MODE_PRIVATE);
+        return sharedPreferences.getString(ApiConstant.SP_KEY_NET_HEADER_AUTH, null);
+    }
+
+    /**
+     * 清除包括cookie和登录认证
+     */
+    public void clearRequestCache() {
+        SharedPreferences.Editor config = context.getSharedPreferences(ApiConstant.SP_NAME_NET, MODE_PRIVATE)
+                .edit();
+        config.remove(ApiConstant.SP_KEY_NET_HEADER_AUTH);
+        config.remove(ApiConstant.SP_KEY_NET_COOKIE_SET);
+        config.commit();
+    }
+}

+ 266 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/LiveTeachingApi.java

@@ -0,0 +1,266 @@
+package com.daya.live_teaching.api;
+
+import com.cooleshow.base.data.net.BaseResponse;
+import com.daya.live_teaching.model.ClassMember;
+import com.daya.live_teaching.model.FileUploadBean;
+import com.daya.live_teaching.model.LoginResult;
+import com.daya.live_teaching.model.Result;
+
+import java.util.List;
+
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+import retrofit2.http.Multipart;
+import retrofit2.http.POST;
+import retrofit2.http.Part;
+import retrofit2.http.Query;
+import retrofit2.http.Streaming;
+import retrofit2.http.Url;
+
+public interface LiveTeachingApi {
+    /**
+     * 签到
+     * @return
+     */
+    @FormUrlEncoded
+    @POST(LiveTeachingUrls.SIGN_IN)
+    Call<Result<String>> signIn(@Field("roomId") String roomId);
+
+    /**
+     * 签到
+     * @return
+     */
+    @GET(LiveTeachingUrls.QUERY_NO_JOIN_STU)
+    Call<Result<List<ClassMember>>> queryNoJoinStu(@Query("roomId") String roomId);
+
+    /**
+     * 开关节拍器
+     * @return
+     */
+    @POST(LiveTeachingUrls.SEND_IMPLAY_MIDI_MESSAGE)
+    Call<Result<Boolean>> sendImPlayMidiMessage(@Body RequestBody body);
+
+    /**
+     * 登录
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.LOGIN)
+    Call<Result<LoginResult>> login(@Body RequestBody body);
+
+
+    /**
+     * 退出房间
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.ROOM_LEAVE)
+    Call<Result<Boolean>> leave(@Body RequestBody body);
+
+
+    /**
+     * 踢人
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.KICK_OFF)
+    Call<Result<Boolean>> kickOff(@Body RequestBody body);
+
+    /**
+     * 控制设备
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.DEVICE_CONTROL)
+    Call<Result<Boolean>> deviceControl(@Body RequestBody body);
+
+    /**
+     * 控制全员设备
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.DEVICE_BATCH_CONTROL)
+    Call<Result<Boolean>> deviceBatchControl(@Body RequestBody body);
+
+    /**
+     * 同意开启设备
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.DEVICE_APPROVE)
+    Call<Result<Boolean>> deviceApprove(@Body RequestBody body);
+
+    /**
+     * 拒绝开启设备
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.DEVICE_REJECT)
+    Call<Result<Boolean>> deviceReject(@Body RequestBody body);
+
+
+    /**
+     * 同步设备状态
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.DEVICE_SYNC)
+    Call<Result<Boolean>> deviceSync(@Body RequestBody body);
+
+
+    /**
+     * 降级
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.DOWNGRADE)
+    Call<Result<Boolean>> downgrade(@Body RequestBody body);
+
+    /**
+     * 申请发言
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.SPEECH_APPLY)
+    Call<Result<Boolean>> applySpeech(@Body RequestBody body);
+
+
+    /**
+     * 同意发言
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.SPEECH_APPROVE)
+    Call<Result<Boolean>> applyApprove(@Body RequestBody body);
+
+
+    /**
+     * 拒绝发言
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.SPEECH_REJECT)
+    Call<Result<Boolean>> applyReject(@Body RequestBody body);
+
+
+    /**
+     * 转移角色
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.TRANSFER_ROLE)
+    Call<Result<Boolean>> transferRole(@Body RequestBody body);
+
+
+    /**
+     * 要求升级
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.UPGRADE_INVITE)
+    Call<Result<Boolean>> upgradeInvite(@Body RequestBody body);
+
+
+    /**
+     * 接受升级
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.UPGRADE_APPROVE)
+    Call<Result<Boolean>> upgradeApprove(@Body RequestBody body);
+
+
+    /**
+     * 拒绝升级
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.UPGRADE_REJECT)
+    Call<Result<Boolean>> upgradeReject(@Body RequestBody body);
+
+
+    /**
+     * 设置角色, 设置为老师
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.CHANGE_ROLE)
+    Call<Result<Boolean>> changeRole(@Body RequestBody body);
+
+
+    /**
+     * 创建白板
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.WHITE_BOARD_CREATE)
+    Call<Result<String>> createWhiteBoard(@Body RequestBody body);
+
+
+    /**
+     * 删除白板
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.WHITE_BOARD_DELETE)
+    Call<Result<Boolean>> deleteWhiteBoard(@Body RequestBody body);
+
+
+    /**
+     * 切换共享画布区内容显示Id
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.SWITCH_DISPLAY)
+    Call<Result<Boolean>> switchDisplay(@Body RequestBody body);
+
+    /**
+     * 通知学生下载伴奏
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.PUSH_DOWNLOAD_EXAM_SONG_MSG)
+    Call<Result<Boolean>> pushDownloadexamSongMsg(@Body RequestBody body);
+
+    /**
+     * 学员伴奏下载回执
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.ADJUST_EXAM_SONG)
+    Call<Result<Boolean>> adjustExamSong(@Body RequestBody body);
+
+    /**
+     * 同步房间状态
+     * @param body
+     * @return
+     */
+    @POST(LiveTeachingUrls.JOIN_ROOM_STATUS_NOTIFY)
+    Call<Result<Boolean>> joinRoomStatusNotify(@Body RequestBody body);
+
+
+    /**
+     * 文件上传
+     * @param file
+     * @return
+     */
+    @Multipart
+    @POST("api-teacher/uploadFile")
+    Call<BaseResponse<FileUploadBean>> uploadFile(@Part MultipartBody.Part file);
+
+    /**
+     * 文件下载
+     * @return
+     */
+    @Streaming
+    @GET
+    @Headers({"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:0.9.4)"})
+    Call<ResponseBody> downloadFileWithFixedUrl(@Url String url);
+}

+ 62 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/LiveTeachingUrls.java

@@ -0,0 +1,62 @@
+package com.daya.live_teaching.api;
+
+
+import com.cooleshow.base.BuildConfig;
+
+public class LiveTeachingUrls {
+
+//            public static final String DOMAIN = "http://192.168.3.38:8000/";
+
+    public static final String DOMAIN    = BuildConfig.BASE_SERVER_URL;
+    static final        String api       = "api-im/";
+    public static final String LOGIN     = api + "room/join";
+    public static final String hereWhite = api + "hereWhite/create";
+    public static final String SIGN_IN   = api + "room/signIn";
+
+
+    public static final String ROOM_LEAVE               = api + "room/leave";
+    public static final String SEND_IMPLAY_MIDI_MESSAGE = api + "room/sendImPlayMidiMessage";
+    public static final String QUERY_NO_JOIN_STU        = api + "room/queryNoJoinStu";
+    // 列表操作
+    public static final String KICK_OFF                 = api + "room/kick";
+
+    public static final String DEVICE_CONTROL       = api + "room/device/control";
+    public static final String DEVICE_BATCH_CONTROL = api + "room/device/batchControl";
+
+    public static final String DEVICE_APPROVE = api + "room/device/approve";
+
+    public static final String DEVICE_REJECT = api + "room/device/reject";
+
+    public static final String DEVICE_SYNC = api + "room/device/sync";
+
+
+    public static final String DOWNGRADE = api + "room/downgrade";
+
+    public static final String SPEECH_APPLY = api + "room/speech/apply";
+
+    public static final String SPEECH_APPROVE = api + "room/speech/approve";
+
+    public static final String SPEECH_REJECT = api + "room/speech/reject";
+
+    public static final String TRANSFER_ROLE = api + "room/transfer";
+
+    public static final String UPGRADE_INVITE = api + "room/upgrade/invite";
+
+    public static final String UPGRADE_APPROVE = api + "room/upgrade/approve";
+
+    public static final String UPGRADE_REJECT = api + "room/upgrade/reject";
+
+    public static final String CHANGE_ROLE = api + "room/change-role";
+
+    public static final String WHITE_BOARD_CREATE = api + "room/whiteboard/create";
+
+    public static final String WHITE_BOARD_DELETE = api + "room/whiteboard/delete";
+
+
+    public static final String SWITCH_DISPLAY              = api + "room/display";
+    public static final String PUSH_DOWNLOAD_EXAM_SONG_MSG = api + "room/pushDownloadMusicScoreMsg";
+
+    public static final String ADJUST_EXAM_SONG = api + "room/adjustMusicScore";
+    public static final String JOIN_ROOM_STATUS_NOTIFY = api + "room/joinRoomStatusNotify";
+
+}

+ 44 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/WhiteBoardApi.java

@@ -0,0 +1,44 @@
+package com.daya.live_teaching.api;
+
+
+import com.cooleshow.base.data.net.BaseResponse;
+import com.daya.live_teaching.model.WhiteCreateBean;
+
+import io.reactivex.rxjava3.core.Observable;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.POST;
+
+/**
+ * 白板API
+ */
+public interface WhiteBoardApi {
+    String TAG                         = "WhiteBoardApi";
+    String HERE_WHITE_URL              = "https://cloudcapiv4.herewhite.com/";
+    String CREATE_ROOM                 = "room?token=%s";
+    String JOIN_ROOM                   = "room/join?uuid=%s&token=%s";
+    String DELETE_ROOM                 = "room/close?token=%s";
+    // 参考服务提供商文档,token写在客户端是不安全的,你“可以”将 Mini Token 写入业务服务器的代码中,或服务端的配置文件中。https://developer.herewhite.com/#/zh-CN/v2/concept
+    String MINI_TOKEN                  = "WHITEcGFydG5lcl9pZD02dFBKT1lzMG52MHFoQzN2Z1BRUXVmN0t0RnVOVGl0bzBhRFAmc2lnPTMyZTRiNTMwNjkyN2RhN2I3NzI4MjMwOTJlZTNmNDJhNWI3MGMyMjU6YWRtaW5JZD0yMTEmcm9sZT1taW5pJmV4cGlyZV90aW1lPTE1ODkzNzY1MjEmYWs9NnRQSk9ZczBudjBxaEMzdmdQUVF1ZjdLdEZ1TlRpdG8wYURQJmNyZWF0ZV90aW1lPTE1NTc4MTk1Njkmbm9uY2U9MTU1NzgxOTU2OTQyNTAw";
+    String WHITE_BOARD_KEY             = "rongRTCWhite";
+    String WHITE_BOARD_SCENE_PATH      = "/DAYA";
+    String WHITE_BOARD_INIT_SCENE_PATH = "/init";
+
+    /**
+     * 创建白板房间
+     * @param
+     * @return
+     */
+    @FormUrlEncoded
+    @POST(LiveTeachingUrls.hereWhite)
+    Observable<BaseResponse<WhiteCreateBean>> hereWhiteCreat(@Field("name") String name, @Field("userNum") int userNum, @Field("courseScheduleId") String courseScheduleId);
+
+    /**
+     * 提交考勤申述
+     * @return
+     */
+    @FormUrlEncoded
+    @POST("api-teacher/teacherAttendance/addComplaints")
+    Observable<BaseResponse<String>> addComplaints(@Field("courseScheduleId") String courseScheduleId, @Field("url") String url, @Field("content") String content, @Field("complaintsType") String complaintsType);
+
+}

+ 44 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/retrofit/CallBackWrapper.java

@@ -0,0 +1,44 @@
+package com.daya.live_teaching.api.retrofit;
+
+
+import com.cooleshow.base.utils.LogUtils;
+import com.daya.live_teaching.common.ErrorCode;
+import com.daya.live_teaching.common.ResultCallback;
+import com.daya.live_teaching.model.Result;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class CallBackWrapper<R> implements Callback<Result<R>> {
+    private ResultCallback<R> mCallBack;
+
+    public CallBackWrapper(ResultCallback<R> callBack) {
+        mCallBack = callBack;
+    }
+
+    @Override
+    public void onResponse(Call<Result<R>> call, Response<Result<R>> response) {
+        Result<R> body = response.body();
+        if (body != null) {
+            int errCode = body.getErrCode();
+
+            if (errCode == 0 || body.getCode() == 200) {
+                mCallBack.onSuccess(body.getDataResult());
+            } else {
+                LogUtils.e("url:" + call.request().url().toString()
+                        + " ,errorMsg:" + body.getErrMsg() + ", errorDetail:" + body.getErrDetail());
+                mCallBack.onFail(errCode, body.getErrMsg());
+            }
+        } else {
+            LogUtils.e("url:" + call.request().url().toString() + ", no response body");
+            mCallBack.onFail(ErrorCode.API_ERR_OTHER.getCode(), null);
+        }
+    }
+
+    @Override
+    public void onFailure(Call<Result<R>> call, Throwable t) {
+        LogUtils.e(call.request().url().toString() + " - " + (t != null ? t.getMessage() : ""));
+        mCallBack.onFail(ErrorCode.NETWORK_ERROR.getCode(), null);
+    }
+}

+ 13 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/retrofit/HttpLogger.java

@@ -0,0 +1,13 @@
+package com.daya.live_teaching.api.retrofit;
+
+
+import com.cooleshow.base.utils.LogUtils;
+
+import okhttp3.logging.HttpLoggingInterceptor;
+
+public class HttpLogger implements HttpLoggingInterceptor.Logger {
+        @Override
+        public void log(String message) {
+            LogUtils.i( message);
+        }
+    }

+ 120 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/retrofit/RetrofitClient.java

@@ -0,0 +1,120 @@
+package com.daya.live_teaching.api.retrofit;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.cooleshow.base.data.net.SSLSocketClient;
+import com.daya.live_teaching.api.ApiConstant;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+import static android.content.Context.MODE_PRIVATE;
+
+public class RetrofitClient {
+    private Context mContext;
+    private Retrofit mRetrofit;
+
+    public RetrofitClient(Context context, String baseUrl) {
+        mContext = context;
+
+        OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder()
+                .retryOnConnectionFailure(true)//错误重连
+                .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(),  SSLSocketClient.getTrustManager())
+                .hostnameVerifier(SSLSocketClient.getHostnameVerifier())
+                .addInterceptor(new AddHeaderInterceptor(mContext))
+                .addInterceptor(new ReceivedCookiesInterceptor(mContext))
+                .addInterceptor(new HttpLoggingInterceptor(new HttpLogger()).setLevel(HttpLoggingInterceptor.Level.BODY))
+                .connectTimeout(ApiConstant.CONNECT_TIME_OUT, TimeUnit.SECONDS)
+                .readTimeout(ApiConstant.READ_TIME_OUT, TimeUnit.SECONDS)
+                .writeTimeout(ApiConstant.WRITE_TIME_OUT, TimeUnit.SECONDS);
+
+        mRetrofit = new Retrofit.Builder()
+                .client(okHttpBuilder.build())
+                .baseUrl(baseUrl) //设置网络请求的Url地址
+                .addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
+                .build();
+    }
+
+    /**
+     * 接受cookie拦截器
+     */
+    public class ReceivedCookiesInterceptor implements Interceptor {
+        private Context mContext;
+
+        public ReceivedCookiesInterceptor(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public Response intercept(Chain chain) throws IOException {
+            Response originalResponse = chain.proceed(chain.request());
+
+            if (!originalResponse.headers("Set-Cookie").isEmpty()) {
+                HashSet<String> cookiesSet = new HashSet<>();
+
+                for (String header : originalResponse.headers("Set-Cookie")) {
+                    cookiesSet.add(header);
+                }
+
+                SharedPreferences.Editor config = mContext.getSharedPreferences(ApiConstant.SP_NAME_NET, MODE_PRIVATE)
+                        .edit();
+                config.putStringSet(ApiConstant.SP_KEY_NET_COOKIE_SET, cookiesSet);
+                config.commit();
+            }
+
+            return originalResponse;
+        }
+    }
+
+    /**
+     * 添加header包含cookie拦截器
+     */
+    public class AddHeaderInterceptor implements Interceptor {
+        private Context mContext;
+
+        public AddHeaderInterceptor(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public Response intercept(Chain chain) throws IOException {
+            Request.Builder builder = chain.request().newBuilder();
+            SharedPreferences preferences = mContext.getSharedPreferences(ApiConstant.SP_NAME_NET,
+                    Context.MODE_PRIVATE);
+
+            //添加cookie
+            HashSet<String> cookieSet = (HashSet<String>) preferences.getStringSet(ApiConstant.SP_KEY_NET_COOKIE_SET, null);
+            if (cookieSet != null) {
+                for (String cookie : cookieSet) {
+                    builder.addHeader("Cookie", cookie);
+                }
+            }
+
+            //添加用户登录认证
+            String auth = preferences.getString(ApiConstant.SP_KEY_NET_HEADER_AUTH, null);
+            if(auth != null) {
+                builder.addHeader("Authorization", auth);
+            }
+
+            return chain.proceed(builder.build());
+        }
+    }
+
+    public <T> T createService(Class<T> service) {
+        return mRetrofit.create(service);
+    }
+
+    private static void allowAllSSL() {
+
+    }
+}

+ 25 - 0
live_teaching/src/main/java/com/daya/live_teaching/api/retrofit/RetrofitUtil.java

@@ -0,0 +1,25 @@
+package com.daya.live_teaching.api.retrofit;
+
+import com.google.gson.Gson;
+
+import java.util.HashMap;
+
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+
+public class RetrofitUtil {
+    private final static MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json;charset=UTF-8");
+
+
+    /**
+     * 通过参数 Map 合集
+     * @param paramsMap
+     * @return
+     */
+    public static RequestBody createJsonRequest(HashMap<String, Object> paramsMap) {
+        Gson gson = new Gson();
+        String strEntity = gson.toJson(paramsMap);
+        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON,strEntity);
+        return body;
+    }
+}

+ 395 - 0
live_teaching/src/main/java/com/daya/live_teaching/audioManager/AppRTCAudioManager.java

@@ -0,0 +1,395 @@
+package com.daya.live_teaching.audioManager;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import cn.rongcloud.rtc.core.rongRTC.DevicesUtils;
+
+/**
+ * AppRTCAudioManager manages all audio related parts of the AppRTC demo.
+ */
+public class AppRTCAudioManager {
+    private static final String TAG = "AppRTCAudioManager";
+
+    /**
+     * AudioDevice is the names of possible audio devices that we currently
+     * support.
+     */
+    // TODO(henrika): add support for BLUETOOTH as well.
+    public enum AudioDevice {
+        SPEAKER_PHONE,
+        WIRED_HEADSET,
+        EARPIECE,
+    }
+
+    private final Context      apprtcContext;
+    private final Runnable     onStateChangeListener;
+    private       boolean      initialized           = false;
+    private       AudioManager audioManager;
+    private       int          savedAudioMode        = AudioManager.MODE_INVALID;
+    private       boolean      savedIsSpeakerPhoneOn = false;
+    private       boolean      savedIsMicrophoneMute = false;
+
+    private static boolean isCurrentSpeakerOn = true;
+
+    // For now; always use the speaker phone as default device selection when
+    // there is a choice between SPEAKER_PHONE and EARPIECE.
+    // TODO(henrika): it is possible that EARPIECE should be preferred in some
+    // cases. If so, we should set this value at construction instead.
+    private final AudioDevice defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
+
+    // Proximity sensor object. It measures the proximity of an object in cm
+    // relative to the view screen of a device and can therefore be used to
+    // assist device switching (close to ear <=> use headset earpiece if
+    // available, far from ear <=> use speaker phone).
+    private AppRTCProximitySensor proximitySensor = null;
+
+    // Contains the currently selected audio device.
+    private AudioDevice selectedAudioDevice;
+
+    // Contains a list of available audio devices. A Set collection is used to
+    // avoid duplicate elements.
+    private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>();
+
+    // Broadcast receiver for wired headset intent broadcasts.
+    private BroadcastReceiver wiredHeadsetReceiver;
+
+    // This method is called when the proximity sensor reports a state change,
+    // e.g. from "NEAR to FAR" or from "FAR to NEAR".
+    private void onProximitySensorChangedState() {
+        // The proximity sensor should only be activated when there are exactly two
+        // available audio devices.
+        if (audioDevices.size() == 2
+                && audioDevices.contains(AudioDevice.EARPIECE)
+                && audioDevices.contains(
+                AudioDevice.SPEAKER_PHONE)) {
+            if (proximitySensor.sensorReportsNearState()) {
+                // Sensor reports that a "handset is being held up to a person's ear",
+                // or "something is covering the light sensor".
+                setAudioDevice(AudioDevice.EARPIECE);
+            } else {
+                // Sensor reports that a "handset is removed from a person's ear", or
+                // "the light sensor is no longer covered".
+                setAudioDevice(AudioDevice.SPEAKER_PHONE);
+            }
+        }
+    }
+
+    /**
+     * Construction
+     */
+    public static AppRTCAudioManager create(Context context,
+                                            Runnable deviceStateChangeListener) {
+        return new AppRTCAudioManager(context, deviceStateChangeListener);
+    }
+
+    private AppRTCAudioManager(Context context,
+                               Runnable deviceStateChangeListener) {
+        apprtcContext = context;
+        onStateChangeListener = deviceStateChangeListener;
+        audioManager = ((AudioManager) context.getSystemService(
+                Context.AUDIO_SERVICE));
+
+        // Create and initialize the proximity sensor.
+        // Tablet devices (e.g. Nexus 7) does not support proximity sensors.
+        // Note that, the sensor will not be active until start() has been called.
+        proximitySensor = AppRTCProximitySensor.create(context, new Runnable() {
+            // This method will be called each time a state change is detected.
+            // Example: user holds his hand over the device (closer than ~5 cm),
+            // or removes his hand from the device.
+            public void run() {
+                //          if (isCurrentSpeakerOn && !BluetoothUtil.hasBluetoothA2dpConnected())
+                //            onProximitySensorChangedState();
+            }
+        });
+        AppRTCUtils.logDeviceInfo(TAG);
+    }
+
+    public void init() {
+        Log.d(TAG, "init");
+        if (initialized) {
+            return;
+        }
+
+        // Store current audio state so we can restore it when close() is called.
+        savedAudioMode = audioManager.getMode();
+        savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
+        savedIsMicrophoneMute = audioManager.isMicrophoneMute();
+
+        // Request audio focus before making any device switch.
+        audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+
+        // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
+        // required to be in this mode when playout and/or recording starts for
+        // best possible VoIP performance.
+        // TODO(henrika): we migh want to start with RINGTONE mode here instead.
+        int mode = DevicesUtils.getAudioMode() == AudioManager.STREAM_MUSIC ? AudioManager.MODE_NORMAL
+                : AudioManager.MODE_IN_COMMUNICATION;
+        Log.d(TAG, "setMode =" + mode);
+        audioManager.setMode(AudioManager.MODE_NORMAL);
+
+        // Always disable microphone mute during a WebRTC call.
+        setMicrophoneMute(false);
+
+        // Do initial selection of audio device. This setting can later be changed
+        // either by adding/removing a wired headset or by covering/uncovering the
+        // proximity sensor.
+        //修改在app启动前耳机已经插上,打开app时还是通过speaker播放的问题。
+        //wiredHeadsetReceiver可以收到启动app前已经插上耳机的Intent
+        //if (!BluetoothUtil.hasBluetoothA2dpConnected())
+        //  updateAudioDeviceState(hasWiredHeadset());
+
+        // Register receiver for broadcast intents related to adding/removing a
+        // wired headset (Intent.ACTION_HEADSET_PLUG).
+        registerForWiredHeadsetIntentBroadcast();
+
+        initialized = true;
+    }
+
+    public void close() {
+        Log.d(TAG, "close");
+        if (!initialized) {
+            return;
+        }
+
+        unregisterForWiredHeadsetIntentBroadcast();
+
+        // Restore previously stored audio states.
+        setSpeakerphoneOn(savedIsSpeakerPhoneOn);
+        setMicrophoneMute(savedIsMicrophoneMute);
+        audioManager.setMode(savedAudioMode);
+        audioManager.abandonAudioFocus(null);
+        if (BluetoothUtil.hasBluetoothA2dpConnected()) {
+            audioManager.stopBluetoothSco();
+            audioManager.setBluetoothScoOn(false);
+        }
+
+        if (proximitySensor != null) {
+            proximitySensor.stop();
+            proximitySensor = null;
+        }
+
+        initialized = false;
+    }
+
+    /**
+     * Changes selection of the currently active audio device.
+     */
+    public void setAudioDevice(AudioDevice device) {
+        Log.d(TAG, "setAudioDevice(device=" + device + ")");
+        AppRTCUtils.assertIsTrue(audioDevices.contains(device));
+
+        switch (device) {
+            case SPEAKER_PHONE:
+                setSpeakerphoneOn(true);
+                selectedAudioDevice = AudioDevice.SPEAKER_PHONE;
+                break;
+            case EARPIECE:
+                setSpeakerphoneOn(false);
+                selectedAudioDevice = AudioDevice.EARPIECE;
+                break;
+            case WIRED_HEADSET:
+                setSpeakerphoneOn(false);
+                selectedAudioDevice = AudioDevice.WIRED_HEADSET;
+                break;
+            default:
+                Log.e(TAG, "Invalid audio device selection");
+                break;
+        }
+        onAudioManagerChangedState();
+    }
+
+    /**
+     * Returns current set of available/selectable audio devices.
+     */
+    public Set<AudioDevice> getAudioDevices() {
+        return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices));
+    }
+
+    /**
+     * Returns the currently selected audio device.
+     */
+    public AudioDevice getSelectedAudioDevice() {
+        return selectedAudioDevice;
+    }
+
+    /**
+     * Registers receiver for the broadcasted intent when a wired headset is
+     * plugged in or unplugged. The received intent will have an extra
+     * 'state' value where 0 means unplugged, and 1 means plugged.
+     */
+    private void registerForWiredHeadsetIntentBroadcast() {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+
+        /** Receiver which handles changes in wired headset availability. */
+        wiredHeadsetReceiver = new BroadcastReceiver() {
+            private static final int STATE_UNPLUGGED = 0;
+            private static final int STATE_PLUGGED = 1;
+            private static final int HAS_NO_MIC = 0;
+            private static final int HAS_MIC = 1;
+
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (BluetoothUtil.hasBluetoothA2dpConnected())
+                    return;
+                int state = intent.getIntExtra("state", STATE_UNPLUGGED);
+                int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
+                String name = intent.getStringExtra("name");
+                Log.d(TAG, "BroadcastReceiver.onReceive" + AppRTCUtils.getThreadInfo()
+                        + ": "
+                        + "a=" + intent.getAction()
+                        + ", s=" + (state == STATE_UNPLUGGED ? "unplugged" : "plugged")
+                        + ", m=" + (microphone == HAS_MIC ? "mic" : "no mic")
+                        + ", n=" + name
+                        + ", sb=" + isInitialStickyBroadcast());
+
+                boolean hasWiredHeadset = (state == STATE_PLUGGED) ? true : false;
+                switch (state) {
+                    case STATE_UNPLUGGED:
+                        updateAudioDeviceState(hasWiredHeadset);
+                        audioManager.setSpeakerphoneOn(isCurrentSpeakerOn);
+                        break;
+                    case STATE_PLUGGED:
+                        if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) {
+                            updateAudioDeviceState(hasWiredHeadset);
+                        }
+                        break;
+                    default:
+                        Log.e(TAG, "Invalid state");
+                        break;
+                }
+            }
+        };
+        apprtcContext.registerReceiver(wiredHeadsetReceiver, filter);
+    }
+
+    /**
+     * Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent.
+     */
+    private void unregisterForWiredHeadsetIntentBroadcast() {
+        apprtcContext.unregisterReceiver(wiredHeadsetReceiver);
+        wiredHeadsetReceiver = null;
+    }
+
+    /**
+     * Sets the speaker phone mode.
+     */
+    private void setSpeakerphoneOn(boolean on) {
+        boolean wasOn = audioManager.isSpeakerphoneOn();
+        if (wasOn == on) {
+            return;
+        }
+
+        audioManager.setSpeakerphoneOn(on);
+    }
+
+    public void onToggleSpeaker(boolean mute) {
+        isCurrentSpeakerOn = !mute;
+        if (selectedAudioDevice == AudioDevice.WIRED_HEADSET) {
+            return;
+        }
+        setSpeakerphoneOn(!mute);
+        //    setMicrophoneMute(mute);
+    }
+
+    /**
+     * Sets the microphone mute state.
+     */
+    private void setMicrophoneMute(boolean on) {
+        boolean wasMuted = audioManager.isMicrophoneMute();
+        if (wasMuted == on) {
+            return;
+        }
+        audioManager.setMicrophoneMute(on);
+    }
+
+    /**
+     * Gets the current earpiece state.
+     */
+    private boolean hasEarpiece() {
+        return apprtcContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
+    }
+
+    /**
+     * Checks whether a wired headset is connected or not.
+     * This is not a valid indication that audio playback is actually over
+     * the wired headset as audio routing depends on other conditions. We
+     * only use it as an early indicator (during initialization) of an attached
+     * wired headset.
+     */
+    @Deprecated
+    private boolean hasWiredHeadset() {
+        return audioManager.isWiredHeadsetOn();
+    }
+
+    public AudioManager getAudioManager() {
+        return audioManager;
+    }
+
+    /**
+     * Update list of possible audio devices and make new device selection.
+     */
+    private void updateAudioDeviceState(boolean hasWiredHeadset) {
+        // Update the list of available audio devices.
+        audioDevices.clear();
+        if (hasWiredHeadset) {
+            // If a wired headset is connected, then it is the only possible option.
+            audioDevices.add(AudioDevice.WIRED_HEADSET);
+        } else {
+            // No wired headset, hence the audio-device list can contain speaker
+            // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
+            audioDevices.add(AudioDevice.SPEAKER_PHONE);
+            if (hasEarpiece()) {
+                audioDevices.add(AudioDevice.EARPIECE);
+            }
+        }
+        Log.d(TAG, "audioDevices: " + audioDevices);
+
+        // Switch to correct audio device given the list of available audio devices.
+        if (hasWiredHeadset) {
+            setAudioDevice(AudioDevice.WIRED_HEADSET);
+        } else {
+            setAudioDevice(defaultAudioDevice);
+        }
+    }
+
+    /**
+     * Called each time a new audio device has been added or removed.
+     */
+    private void onAudioManagerChangedState() {
+        Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices
+                + ", selected=" + selectedAudioDevice);
+
+        // Enable the proximity sensor if there are two available audio devices
+        // in the list. Given the current implementation, we know that the choice
+        // will then be between EARPIECE and SPEAKER_PHONE.
+        if (audioDevices.size() == 2) {
+            AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE)
+                    && audioDevices.contains(AudioDevice.SPEAKER_PHONE));
+            // Start the proximity sensor.
+            proximitySensor.start();
+        } else if (audioDevices.size() == 1) {
+            // Stop the proximity sensor since it is no longer needed.
+            proximitySensor.stop();
+        } else {
+            Log.e(TAG, "Invalid device list");
+        }
+
+        if (onStateChangeListener != null) {
+            // Run callback to notify a listening client. The client can then
+            // use public getters to query the new state.
+            onStateChangeListener.run();
+        }
+    }
+}

+ 177 - 0
live_teaching/src/main/java/com/daya/live_teaching/audioManager/AppRTCProximitySensor.java

@@ -0,0 +1,177 @@
+/*
+ *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+package com.daya.live_teaching.audioManager;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * AppRTCProximitySensor manages functions related to the proximity sensor in
+ * the AppRTC demo.
+ * On most device, the proximity sensor is implemented as a boolean-sensor.
+ * It returns just two shape_corner "NEAR" or "FAR". Thresholding is done on the LUX
+ * value i.e. the LUX value of the light sensor is compared with a threshold.
+ * A LUX-value more than the threshold means the proximity sensor returns "FAR".
+ * Anything less than the threshold value and the sensor  returns "NEAR".
+ */
+public class AppRTCProximitySensor implements SensorEventListener {
+  private static final String TAG = "AppRTCProximitySensor";
+
+  // This class should be created, started and stopped on one thread
+  // (e.g. the main thread). We use |nonThreadSafe| to ensure that this is
+  // the case. Only active when |DEBUG| is set to true.
+  private final AppRTCUtils.NonThreadSafe nonThreadSafe = new AppRTCUtils.NonThreadSafe();
+
+  private final Runnable onSensorStateListener;
+  private final SensorManager sensorManager;
+  private Sensor proximitySensor = null;
+  private boolean lastStateReportIsNear = false;
+
+  /** Construction */
+  static AppRTCProximitySensor create(Context context,
+      Runnable sensorStateListener) {
+    return new AppRTCProximitySensor(context, sensorStateListener);
+  }
+
+  private AppRTCProximitySensor(Context context, Runnable sensorStateListener) {
+    Log.d(TAG, "AppRTCProximitySensor" + AppRTCUtils.getThreadInfo());
+    onSensorStateListener = sensorStateListener;
+    sensorManager = ((SensorManager) context.getSystemService(
+        Context.SENSOR_SERVICE));
+  }
+
+  /**
+   * Activate the proximity sensor. Also do initializtion if called for the
+   * first time.
+   */
+  public boolean start() {
+    checkIfCalledOnValidThread();
+    Log.d(TAG, "start" + AppRTCUtils.getThreadInfo());
+    if (!initDefaultSensor()) {
+      // Proximity sensor is not supported on this device.
+      return false;
+    }
+    sensorManager.registerListener(
+        this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
+    return true;
+  }
+
+  /** Deactivate the proximity sensor. */
+  public void stop() {
+    checkIfCalledOnValidThread();
+    Log.d(TAG, "stop" + AppRTCUtils.getThreadInfo());
+    if (proximitySensor == null) {
+      return;
+    }
+    sensorManager.unregisterListener(this, proximitySensor);
+  }
+
+  /** Getter for last reported state. Set to true if "near" is reported. */
+  public boolean sensorReportsNearState() {
+    checkIfCalledOnValidThread();
+    return lastStateReportIsNear;
+  }
+
+  @Override
+  public final void onAccuracyChanged(Sensor sensor, int accuracy) {
+    checkIfCalledOnValidThread();
+    AppRTCUtils.assertIsTrue(sensor.getType() == Sensor.TYPE_PROXIMITY);
+    if (accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
+      Log.e(TAG, "The shape_corner returned by this sensor cannot be trusted");
+    }
+  }
+
+  @Override
+  public final void onSensorChanged(SensorEvent event) {
+    checkIfCalledOnValidThread();
+    AppRTCUtils.assertIsTrue(event.sensor.getType() == Sensor.TYPE_PROXIMITY);
+    // As a best practice; do as little as possible within this method and
+    // avoid blocking.
+    float distanceInCentimeters = event.values[0];
+    if (distanceInCentimeters < proximitySensor.getMaximumRange()) {
+      Log.d(TAG, "Proximity sensor => NEAR state");
+      lastStateReportIsNear = true;
+    } else {
+      Log.d(TAG, "Proximity sensor => FAR state");
+      lastStateReportIsNear = false;
+    }
+
+    // Report about new state to listening client. Client can then call
+    // sensorReportsNearState() to query the current state (NEAR or FAR).
+    if (onSensorStateListener != null) {
+      onSensorStateListener.run();
+    }
+
+    Log.d(TAG, "onSensorChanged" + AppRTCUtils.getThreadInfo() + ": "
+        + "accuracy=" + event.accuracy
+        + ", timestamp=" + event.timestamp + ", distance=" + event.values[0]);
+  }
+
+  /**
+   * Get default proximity sensor if it exists. Tablet devices (e.g. Nexus 7)
+   * does not support this type of sensor and false will be retured in such
+   * cases.
+   */
+  private boolean initDefaultSensor() {
+    if (proximitySensor != null) {
+      return true;
+    }
+    proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+    if (proximitySensor == null) {
+      return false;
+    }
+    logProximitySensorInfo();
+    return true;
+  }
+
+  /** Helper method for logging information about the proximity sensor. */
+  private void logProximitySensorInfo() {
+    if (proximitySensor == null) {
+      return;
+    }
+    StringBuilder info = new StringBuilder("Proximity sensor: ");
+    info.append("name=" + proximitySensor.getName());
+    info.append(", vendor: " + proximitySensor.getVendor());
+    info.append(", power: " + proximitySensor.getPower());
+    info.append(", resolution: " + proximitySensor.getResolution());
+    info.append(", max range: " + proximitySensor.getMaximumRange());
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+      // Added in API level 9.
+      info.append(", min delay: " + proximitySensor.getMinDelay());
+    }
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
+      // Added in API level 20.
+      info.append(", type: " + proximitySensor.getStringType());
+    }
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      // Added in API level 21.
+      info.append(", max delay: " + proximitySensor.getMaxDelay());
+      info.append(", reporting mode: " + proximitySensor.getReportingMode());
+      info.append(", isWakeUpSensor: " + proximitySensor.isWakeUpSensor());
+    }
+    Log.d(TAG, info.toString());
+  }
+
+  /**
+   * Helper method for debugging purposes. Ensures that method is
+   * called on same thread as this object was created on.
+   */
+  private void checkIfCalledOnValidThread() {
+    if (!nonThreadSafe.calledOnValidThread()) {
+      throw new IllegalStateException("Method is not called on valid thread");
+    }
+  }
+}

+ 151 - 0
live_teaching/src/main/java/com/daya/live_teaching/audioManager/AppRTCUtils.java

@@ -0,0 +1,151 @@
+/*
+ *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+package com.daya.live_teaching.audioManager;
+
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.regex.Pattern;
+
+/**
+ * AppRTCUtils provides helper functions for managing thread safety.
+ */
+public final class AppRTCUtils {
+  //自定义的key
+  public static final String CUSTOM_CMPKEY="CUSTOM_CMPAddress_KEY";
+  public static final String APPID_KEY="appid_key";
+  //生产环境key
+  public static final String SELECT_KEY="SELECT_KEY";
+
+  public static final String CER_URL="cerUrl";
+
+
+  private AppRTCUtils() {
+  }
+
+  /**
+   * NonThreadSafe is a helper class used to help verify that methods of a
+   * class are called from the same thread.
+   */
+  public static class NonThreadSafe {
+    private final Long threadId;
+
+    public NonThreadSafe() {
+      // Store thread ID of the creating thread.
+      threadId = Thread.currentThread().getId();
+    }
+
+   /** Checks if the method is called on the valid/creating thread. */
+    public boolean calledOnValidThread() {
+       return threadId.equals(Thread.currentThread().getId());
+    }
+  }
+
+  /** Helper method which throws an exception  when an assertion has failed. */
+  public static void assertIsTrue(boolean condition) {
+    if (!condition) {
+      throw new AssertionError("Expected condition to be true");
+    }
+  }
+
+  /** Helper method for building a string of thread information.*/
+  public static String getThreadInfo() {
+    return "@[name=" + Thread.currentThread().getName()
+        + ", id=" + Thread.currentThread().getId() + "]";
+  }
+
+  /** Information about the current build, taken from system properties. */
+  public static void logDeviceInfo(String tag) {
+    Log.d(tag, "Android SDK: " + Build.VERSION.SDK_INT + ", "
+        + "Release: " + Build.VERSION.RELEASE + ", "
+        + "Brand: " + Build.BRAND + ", "
+        + "Device: " + Build.DEVICE + ", "
+        + "Id: " + Build.ID + ", "
+        + "Hardware: " + Build.HARDWARE + ", "
+        + "Manufacturer: " + Build.MANUFACTURER + ", "
+        + "Model: " + Build.MODEL + ", "
+        + "Product: " + Build.PRODUCT);
+  }
+
+  /**
+   * 从网络Url中下载文件
+   *
+   * @param urlStr
+   * @throws IOException
+   */
+  public static InputStream downLoadFromUrl(String urlStr) throws IOException {
+      InputStream inputStream = null;
+      HttpURLConnection conn=null;
+      try {
+          URL url = new URL(urlStr);
+          conn = (HttpURLConnection) url.openConnection();
+          conn.setConnectTimeout(3 * 1000);
+          conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
+          inputStream = conn.getInputStream();
+      } catch (Exception e) {
+          e.printStackTrace();
+      }
+//        byte[] getData = readInputStream(inputStream);
+//        //文件保存位置
+//        File saveDir = new File(savePath);
+//        if (!saveDir.exists()) {
+//            saveDir.mkdir();
+//        }
+//        File file = new File(saveDir + File.separator + fileName);
+//        FileOutputStream fos = new FileOutputStream(file);
+//        fos.write(getData);
+//        if (fos != null) {
+//            fos.close();
+//        }
+//        if (inputStream != null) {
+//            inputStream.close();
+//        }
+//        System.out.println("info:" + url + " download success");
+    return inputStream;
+  }
+//生产
+  //cmpServer=cmp.blinkcloud.cn:443,,snifferServer=cmp.blinkcloud.cn:443
+  // tokenServerURL=https://api.blinkcloud.cn:8800/token
+  // cerUrl=https://api.blinktalk.online:8081/key/prod/blinktalk.crt
+
+
+  /**
+   * 验证是否是网址
+   *
+   * @param url
+   * @return true:是
+   */
+  private static boolean isURL(String url) {
+    if (TextUtils.isEmpty(url)) {
+      return false;
+    }
+    Pattern pattern = Pattern.compile("^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\\/])+$");
+    return pattern.matcher(url).matches();
+  }
+
+  /**
+   * 验证端口号或ip
+   * @param str
+   * @return
+   */
+  private static boolean isCmpServer(String str) {
+    if (TextUtils.isEmpty(str)) {
+      return false;
+    }
+    Pattern pattern = Pattern.compile("^(\\d|[1-9]\\d|1\\d{2}|2[0-5][0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-5][0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-5][0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-5][0-5]):([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-5]{2}[0-3][0-5])$");
+    return pattern.matcher(str).matches();
+  }
+}

+ 274 - 0
live_teaching/src/main/java/com/daya/live_teaching/audioManager/BluetoothUtil.java

@@ -0,0 +1,274 @@
+package com.daya.live_teaching.audioManager;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothProfile;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioManager;
+import android.text.TextUtils;
+
+import java.util.List;
+
+import static android.content.Context.AUDIO_SERVICE;
+
+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;
+        }
+        return bool;
+    }
+}

+ 62 - 0
live_teaching/src/main/java/com/daya/live_teaching/common/ErrorCode.java

@@ -0,0 +1,62 @@
+package com.daya.live_teaching.common;
+
+
+import com.daya.live_teaching.R;
+
+public enum ErrorCode {
+    API_ERR_REQUEST_PARA_ERR(1, R.string.error_api_common_error),
+    API_ERR_INVALID_AUTH(2, R.string.error_api_invalid_auth),
+    API_ERR_ACCESS_DENIED(3, R.string.error_api_permission_denied),
+    API_ERR_BAD_REQUEST(4, R.string.error_api_common_error),
+    API_ERR_OTHER(255, R.string.error_api_common_error),
+    API_ERR_IM_TOKEN_ERROR(10, R.string.error_api_server_error),
+    API_ERR_CREATE_ROOM_ERROR(11, R.string.error_api_create_room_error),
+    API_ERR_JOIN_ROOM_ERROR(12, R.string.error_api_join_room_error),
+    API_ERR_MESSAGE_ERROR(13, R.string.error_api_server_error),
+    API_ERR_ROOM_NOT_EXIST(20, R.string.error_api_room_not_exist),
+    API_ERR_USER_NOT_EXIST_IN_ROOM(21, R.string.error_api_user_not_in_room),
+    API_ERR_EXIT_ROOM_ERROR(22, R.string.error_api_exit_room_error),
+    API_ERR_LECTURER_NOT_EXIST_IN_ROOM(23, R.string.error_api_lecturer_not_in_room),
+    API_ERR_ASSISTANT_NOT_EXIST_IN_ROOM(24, R.string.error_api_assistant_not_in_room),
+    API_ERR_CREATE_WHITE_BOARD(25, R.string.error_api_create_white_board_error),
+    API_ERR_WHITE_BOARD_NOT_EXIST(26, R.string.error_api_white_board_not_exist),
+    API_ERR_DELETE_WHITE_BOARD(27, R.string.error_api_delete_white_error),
+    API_ERR_USER_EXIST_IN_ROOM(28, R.string.error_api_already_in_room),
+    API_ERR_CHANGE_SELF_ROLE(29, R.string.error_api_can_not_change_self_role),
+    API_ERR_APPLY_TICKET_INVALID(30, R.string.error_api_ticket_invalid),
+    API_ERR_OVER_MAX_COUNT(31, R.string.error_api_over_max_count),
+    API_ERR_LECTURER_EXIST_IN_ROOM(32, R.string.error_api_lecturer_has_exist),
+    API_ERR_DOWNGRADE_ROLE(33, R.string.error_api_downgrade_error),
+    API_ERR_CHANGE_ROLE(34, R.string.error_api_change_role_error),
+    NETWORK_ERROR(10000, R.string.error_network_error),
+    IM_ERROR(10003, R.string.error_im_common_error),
+    RTC_ERROR(10004, R.string.error_rtc_common_error),
+    UNKNOWN_ERROR(99999, R.string.error_unkown_error),
+    NONE_ERROR(-1, 0);
+
+    private int code;
+    private int messageResId;
+
+    ErrorCode(int code, int messageResId) {
+        this.code = code;
+        this.messageResId = messageResId;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public int getMessageResId() {
+        return messageResId;
+    }
+
+    public static ErrorCode fromCode(int code) {
+        for (ErrorCode errorCode : ErrorCode.values()) {
+            if (errorCode.code == code)
+                return errorCode;
+        }
+
+        return UNKNOWN_ERROR;
+    }
+
+}

+ 11 - 0
live_teaching/src/main/java/com/daya/live_teaching/common/ResultCallback.java

@@ -0,0 +1,11 @@
+package com.daya.live_teaching.common;
+
+/**
+ * 执行 Task 的回调
+ * @param <Result> 请求成功时的结果类
+ */
+public interface ResultCallback<Result> {
+    void onSuccess(Result result);
+
+    void onFail(int errorCode, String errorStr);
+}

+ 33 - 0
live_teaching/src/main/java/com/daya/live_teaching/common/ShowToastObserver.java

@@ -0,0 +1,33 @@
+package com.daya.live_teaching.common;
+
+import com.daya.live_teaching.model.RequestState;
+import com.daya.live_teaching.utils.ToastUtils;
+
+import androidx.lifecycle.Observer;
+
+/**
+ * 用于请求结果出错时弹出提示用监听
+ */
+public class ShowToastObserver implements Observer<RequestState> {
+    private ToastBySelfComponent component;
+
+    public ShowToastObserver() {
+    }
+
+    public ShowToastObserver(Object toastComponent) {
+        if(toastComponent instanceof ToastBySelfComponent) {
+            this.component = (ToastBySelfComponent)toastComponent;
+        }
+    }
+
+    @Override
+    public void onChanged(RequestState state) {
+        if (state.getState() == RequestState.State.FAILED) {
+            if (component != null) {
+                component.showToast(state.getErrorCode().getMessageResId());
+            } else {
+                ToastUtils.showToast(state.getErrorCode().getMessageResId());
+            }
+        }
+    }
+}

+ 52 - 0
live_teaching/src/main/java/com/daya/live_teaching/common/StateLiveData.java

@@ -0,0 +1,52 @@
+package com.daya.live_teaching.common;
+
+import com.daya.live_teaching.model.RequestState;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
+
+/**
+ *  记录请求状态用的 LiveData
+ *  内部实现了当请求成功或失败时取消掉监听的功能
+ */
+public class StateLiveData extends MutableLiveData<RequestState> {
+    private LifecycleOwner observerOwner;
+
+    @Override
+    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super RequestState> observer) {
+        observerOwner = owner;
+        ObserverWrapper observerWrapper = new ObserverWrapper(observer);
+        super.observe(owner, observerWrapper);
+    }
+
+    public void loading() {
+        postValue(RequestState.loading());
+    }
+
+    public void success() {
+        postValue(RequestState.success());
+    }
+
+    public void failed(int errorCode) {
+        postValue(RequestState.failed(errorCode));
+    }
+
+    private class ObserverWrapper implements Observer<RequestState> {
+        Observer<? super RequestState> origin;
+
+        ObserverWrapper(Observer<? super RequestState> origin) {
+            this.origin = origin;
+        }
+
+        @Override
+        public void onChanged(RequestState o) {
+            origin.onChanged(o);
+
+            if (o.getState() != RequestState.State.LOADING) {
+                removeObservers(observerOwner);
+            }
+        }
+    }
+}

+ 49 - 0
live_teaching/src/main/java/com/daya/live_teaching/common/ThreadManager.java

@@ -0,0 +1,49 @@
+package com.daya.live_teaching.common;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class ThreadManager {
+    private static final String TAG = "ThreadManager";
+
+    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
+
+    private static ThreadManager sInstance;
+
+    private static Executor mWorkThreadExecutor = Executors.newFixedThreadPool(CORE_POOL_SIZE);
+    private static Handler mMainThreadHandler;
+
+
+    private ThreadManager() {
+    }
+
+    public void init(Context context) {
+        mMainThreadHandler = new Handler(Looper.getMainLooper());
+    }
+
+    public static ThreadManager getInstance() {
+        if (sInstance == null) {
+            synchronized (ThreadManager.class) {
+                if (sInstance == null) {
+                    sInstance = new ThreadManager();
+                }
+            }
+        }
+
+        return sInstance;
+    }
+
+    public void runOnWorkThread(Runnable runnable) {
+        mWorkThreadExecutor.execute(runnable);
+    }
+
+    public void runOnUIThread(Runnable runnable) {
+        mMainThreadHandler.post(runnable);
+    }
+
+}

+ 34 - 0
live_teaching/src/main/java/com/daya/live_teaching/common/ToastBySelfComponent.java

@@ -0,0 +1,34 @@
+package com.daya.live_teaching.common;
+
+/**
+ * 内部显示 toast 信息的控件
+ */
+public interface ToastBySelfComponent {
+    /**
+     * 设定显示时间的 toast 消息
+     *
+     * @param content  显示内容
+     * @param duration 显示时长,单位毫秒
+     */
+    void showToast(String content, long duration);
+
+    /**
+     * 使用默认时长的 toast 消息
+     *
+     * @param content
+     */
+    void showToast(String content);
+
+    /**
+     * 设定显示时间的 toast 消息
+     *
+     * @param resId    显示内容的 String Resource id
+     * @param duration 显示时长,单位毫秒
+     */
+    void showToast(int resId, long duration);
+
+    /**
+     * 使用默认时长的 toast 消息
+     */
+    void showToast(int resId);
+}

+ 336 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/IMManager.java

@@ -0,0 +1,336 @@
+package com.daya.live_teaching.im;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.cooleshow.base.common.BaseApplication;
+import com.cooleshow.base.utils.LogUtils;
+import com.daya.live_teaching.R;
+import com.daya.live_teaching.common.ErrorCode;
+import com.daya.live_teaching.common.ResultCallback;
+import com.daya.live_teaching.im.message.ApplyForSpeechMessage;
+import com.daya.live_teaching.im.message.AssistantTransferMessage;
+import com.daya.live_teaching.im.message.ControlDeviceNotifyMessage;
+import com.daya.live_teaching.im.message.DeviceStateChangedMessage;
+import com.daya.live_teaching.im.message.DisplayMessage;
+import com.daya.live_teaching.im.message.ExamSongDownloadMessage;
+import com.daya.live_teaching.im.message.ExamSongDownloadStatusMessage;
+import com.daya.live_teaching.im.message.MemberChangedMessage;
+import com.daya.live_teaching.im.message.MusicScoreDownloadMessage;
+import com.daya.live_teaching.im.message.PlayMidiMessage;
+import com.daya.live_teaching.im.message.RoleChangedMessage;
+import com.daya.live_teaching.im.message.RoleSingleChangedMessage;
+import com.daya.live_teaching.im.message.SpeechResultMessage;
+import com.daya.live_teaching.im.message.TicketExpiredMessage;
+import com.daya.live_teaching.im.message.TurnPageMessage;
+import com.daya.live_teaching.im.message.UpgradeRoleMessage;
+import com.daya.live_teaching.im.message.WhiteBoardMessage;
+import com.daya.live_teaching.im.provider.ClassMemberChangedNotificationProvider;
+import com.daya.live_teaching.im.provider.ClassTextMessageItemProvider;
+import com.daya.live_teaching.im.provider.RoleChangedMessageItemProvider;
+import com.daya.live_teaching.model.RoleChangedUser;
+import com.daya.live_teaching.ui.LiveActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.rong.imkit.RongIM;
+import io.rong.imkit.config.DataProcessor;
+import io.rong.imkit.config.RongConfigCenter;
+import io.rong.imkit.conversation.extension.RongExtensionManager;
+import io.rong.imkit.conversation.messgelist.provider.AnnouncementProvider;
+import io.rong.imkit.manager.UnReadMessageManager;
+import io.rong.imkit.widget.CustomMessage;
+import io.rong.imlib.IRongCallback;
+import io.rong.imlib.RongIMClient;
+import io.rong.imlib.model.Conversation;
+import io.rong.imlib.model.MentionedInfo;
+import io.rong.imlib.model.Message;
+import io.rong.imlib.model.UserInfo;
+import io.rong.message.TextMessage;
+import io.rong.sight.SightExtensionModule;
+
+
+/**
+ * Rong IM 业务相关封装
+ */
+public class IMManager {
+    private static final String TAG = IMManager.class.getSimpleName();
+    private static IMManager sInstance;
+    private final int DEFAULT_MESSAGE_COUNT = -1;//-1代表不拉取历史消息
+    private List<RongIMClient.OnReceiveMessageListener> listenerList = new ArrayList<>();
+
+    public static IMManager getInstance() {
+        if (sInstance == null) {
+            synchronized (IMManager.class) {
+                if (sInstance == null) {
+                    sInstance = new IMManager();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    private IMManager() {
+    }
+
+    /**
+     * 初始化,需要在使用前初始化一次
+     *
+     * @param appkey
+     * @param context
+     */
+    public static void init(Context context, String appkey) {
+        final IMManager imManager = getInstance();
+        RongIMClient.getInstance().setReconnectKickEnable(true);//是否踢出重连设备,在init之前调用 打开后会有连接被判定为重连
+
+        /*
+         * 初始化 SDK,在整个应用程序全局,只需要调用一次。建议在 Application 继承类中调用。
+         */
+        // 可在初始 SDK 时直接带入融云 IM 申请的APP KEY
+        RongIM.init(BaseApplication.context, appkey, true);
+        // 注册自定义消息
+        RongIMClient.registerMessageType(ApplyForSpeechMessage.class);
+        RongIMClient.registerMessageType(AssistantTransferMessage.class);
+        RongIMClient.registerMessageType(ControlDeviceNotifyMessage.class);
+        RongIMClient.registerMessageType(DeviceStateChangedMessage.class);
+        RongIMClient.registerMessageType(DisplayMessage.class);
+        RongIMClient.registerMessageType(MemberChangedMessage.class);
+        RongIMClient.registerMessageType(RoleChangedMessage.class);
+        RongIMClient.registerMessageType(SpeechResultMessage.class);
+        RongIMClient.registerMessageType(TicketExpiredMessage.class);
+        RongIMClient.registerMessageType(TurnPageMessage.class);
+        RongIMClient.registerMessageType(UpgradeRoleMessage.class);
+        RongIMClient.registerMessageType(WhiteBoardMessage.class);
+        RongIMClient.registerMessageType(RoleSingleChangedMessage.class);
+        RongIMClient.registerMessageType(ExamSongDownloadMessage.class);//伴奏下载
+        RongIMClient.registerMessageType(PlayMidiMessage.class);//播放节拍器消息
+        RongIMClient.registerMessageType(ExamSongDownloadStatusMessage.class);//学生伴奏下载状态
+        RongIMClient.registerMessageType(MusicScoreDownloadMessage.class);//学生伴奏下载
+        RongIMClient.registerMessageType(CustomMessage.class);//学生伴奏下载
+
+
+        // 设置自定义文本显示
+        RongIM.registerMessageTemplate(new ClassTextMessageItemProvider());
+        RongIM.registerMessageTemplate(new ClassMemberChangedNotificationProvider());
+        RongIM.registerMessageTemplate(new RoleChangedMessageItemProvider());
+        RongIM.registerMessageTemplate(new AnnouncementProvider());
+
+        RongExtensionManager.getInstance().registerExtensionModule(new SightExtensionModule());
+
+
+        Conversation.ConversationType[] types = new Conversation.ConversationType[]{
+                Conversation.ConversationType.PRIVATE,
+                Conversation.ConversationType.GROUP,
+        };
+        RongIM.getInstance().setReadReceiptConversationTypeList(types);
+
+        RongConfigCenter.conversationListConfig().setDataProcessor(new DataProcessor<Conversation>() {
+            @Override
+            public Conversation.ConversationType[] supportedTypes() {
+                Conversation.ConversationType[] supportedTypes = {Conversation.ConversationType.PRIVATE,
+                        Conversation.ConversationType.GROUP, Conversation.ConversationType.SYSTEM,
+                        Conversation.ConversationType.APP_PUBLIC_SERVICE, Conversation.ConversationType.PUBLIC_SERVICE,
+                };
+                return supportedTypes;
+            }
+
+            @Override
+            public List<Conversation> filtered(List<Conversation> data) {
+                for (Conversation conversation : data) {
+                    if (conversation.getTargetId().contains("S") || conversation.getTargetId().contains("DAYA") || conversation.getTargetId().contains("I")) {
+                        data.remove(conversation); //此处以过滤 "XXX" 这个群组会话为例。
+                    }
+                }
+                return data;
+            }
+
+            @Override
+            public boolean isGathered(Conversation.ConversationType type) {
+                if (type.equals(Conversation.ConversationType.SYSTEM)) {
+                    return true; //以配置系统会话聚合显示为例
+                } else {
+                    return false;
+                }
+            }
+        });
+        /*
+         * 管理消息监听,由于同一时间只能有一个消息监听加入 融云 的消息监听,所以做一个消息管理来做消息路由
+         */
+
+        RongIM.addOnReceiveMessageListener(new RongIMClient.OnReceiveMessageWrapperListener() {
+            @Override
+            public boolean onReceived(Message message, int left, boolean b, boolean b1) {
+
+                String targetId = message.getTargetId();
+//                if (!ActivityManager.getInstance().isOpenActivity(LiveActivity.class) && !TextUtils.isEmpty(targetId) && (targetId.contains("S") || targetId.contains("DAYA") || targetId.contains("I"))) {
+//                    //    清除视频课消息未读状态
+//                    RongIM.getInstance().clearMessagesUnreadStatus(Conversation.ConversationType.GROUP, targetId, null);
+//                }
+                LogUtils.e(message.toString());
+                boolean result = false;
+                synchronized (imManager.listenerList) {
+                    if (imManager.listenerList.size() > 0) {
+                        for (RongIMClient.OnReceiveMessageListener listener : imManager.listenerList) {
+                            result = listener.onReceived(message, left);
+                            if (result) {
+                                break;
+                            }
+                        }
+                    }
+                }
+                return result;
+            }
+        });
+    }
+
+
+    /**
+     * 设置用户信息
+     *
+     * @param userInfo
+     */
+    public void setCurrentUserInfo(UserInfo userInfo) {
+        RongIM.getInstance().setCurrentUserInfo(userInfo);
+    }
+
+    /**
+     * 登录 IM 服务器
+     *
+     * @param token
+     */
+    public void login(final String token, final ResultCallback<String> callBack) {
+        RongIM.connect(token, new RongIMClient.ConnectCallback() {
+            @Override
+            public void onSuccess(String s) {
+                if (null != callBack) {
+                    callBack.onSuccess(s);
+                }
+            }
+
+            @Override
+            public void onError(RongIMClient.ConnectionErrorCode connectionErrorCode) {
+                if (null != callBack) {
+                    if (connectionErrorCode == RongIMClient.ConnectionErrorCode.RC_CONNECTION_EXIST) {
+                        callBack.onSuccess(null);
+                    } else {
+                        callBack.onFail(ErrorCode.IM_ERROR.getCode(), null);
+                    }
+
+                }
+            }
+
+            @Override
+            public void onDatabaseOpened(RongIMClient.DatabaseOpenStatus databaseOpenStatus) {
+
+            }
+        });
+    }
+
+
+    /**
+     * 退出登录
+     */
+    public void logout() {
+        RongIM.getInstance().logout();
+    }
+
+
+    /**
+     * 加入消息监听
+     *
+     * @param listener
+     */
+    public void addMessageReceiveListener(RongIMClient.OnReceiveMessageListener listener) {
+        synchronized (listenerList) {
+            listenerList.add(listener);
+        }
+    }
+
+    /**
+     * 移除消息监听
+     *
+     * @param listener
+     */
+    public void removeMessageReceiveListener(RongIMClient.OnReceiveMessageListener listener) {
+        synchronized (listenerList) {
+            listenerList.remove(listener);
+        }
+    }
+
+    /**
+     * 设置未读消息数监听
+     *
+     * @param listener
+     */
+    public void addOnUnReadMessageListener(UnReadMessageManager.IUnReadMessageObserver listener) {
+        RongIM.getInstance().addUnReadMessageCountChangedObserver(listener, Conversation.ConversationType.GROUP, Conversation.ConversationType.PRIVATE);
+    }
+
+    /**
+     * 移除未读消息数监听
+     *
+     * @param listener
+     */
+    public void removeOnUnReadMessageListener(UnReadMessageManager.IUnReadMessageObserver listener) {
+        RongIM.getInstance().removeUnReadMessageCountChangedObserver(listener);
+    }
+
+    /**
+     * 将私聊消息存为房间消息
+     *
+     * @param message
+     * @param roomId
+     */
+    public void savePrivateMessageToRoom(Message message, String roomId) {
+        RongIM.getInstance().insertIncomingMessage(Conversation.ConversationType.GROUP, roomId, message.getSenderUserId(), message.getReceivedStatus(), message.getContent(), null);
+    }
+
+    /**
+     * 将多用户角色改变消息拆分保存成单一角色改变
+     */
+    public void saveRoleChangedMessageToSingle(List<RoleChangedUser> roleChangedUserList, String optUserId, String roomId) {
+        if (roleChangedUserList != null && roleChangedUserList.size() > 0) {
+            for (RoleChangedUser user : roleChangedUserList) {
+                RoleSingleChangedMessage roleSingleChangedMessage = new RoleSingleChangedMessage();
+                roleSingleChangedMessage.setUser(user);
+                roleSingleChangedMessage.setOpUserId(optUserId);
+                Message.ReceivedStatus receivedStatus = new Message.ReceivedStatus(0);
+                RongIM.getInstance().insertIncomingMessage(Conversation.ConversationType.GROUP, roomId, optUserId, receivedStatus, roleSingleChangedMessage, null);
+            }
+        }
+    }
+
+    /**
+     * 群发消息
+     *
+     * @param targetId
+     * @param conversationType
+     * @param message
+     * @return
+     */
+    public void sendToAll(String targetId, Conversation.ConversationType conversationType, String message) {
+
+        TextMessage textMessage = TextMessage.obtain(BaseApplication.context.getString(R.string.profile_group_notice_prefix) + message);
+        MentionedInfo mentionedInfo = new MentionedInfo(MentionedInfo.MentionedType.ALL, null, null);
+        textMessage.setMentionedInfo(mentionedInfo);
+
+        RongIM.getInstance().sendMessage(Message.obtain(targetId, conversationType, textMessage), null, null, new IRongCallback.ISendMessageCallback() {
+            @Override
+            public void onAttached(Message message) {
+
+            }
+
+            @Override
+            public void onSuccess(Message message) {
+
+            }
+
+            @Override
+            public void onError(Message message, RongIMClient.ErrorCode errorCode) {
+            }
+        });
+    }
+
+}

+ 89 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/ApplyForSpeechMessage.java

@@ -0,0 +1,89 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 旁听申请发言消息
+ */
+@MessageTag(value = "SC:RSMsg", flag = MessageTag.NONE)
+public class ApplyForSpeechMessage extends MessageContent {
+    private final static String TAG = ApplyForSpeechMessage.class.getSimpleName();
+
+    private String reqUserId;
+    private String reqUserName;
+    private String ticket;
+
+    public ApplyForSpeechMessage(byte[] data){
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            reqUserId = jsonObject.optString("reqUserId");
+            reqUserName = jsonObject.optString("reqUserName");
+            ticket = jsonObject.optString("ticket");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public ApplyForSpeechMessage(Parcel parcel){
+        reqUserId = parcel.readString();
+        reqUserName = parcel.readString();
+        ticket = parcel.readString();
+    }
+
+    public String getReqUserId() {
+        return reqUserId;
+    }
+
+    public String getReqUserName() {
+        return reqUserName;
+    }
+
+    public String getTicket() {
+        return ticket;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(reqUserId);
+        dest.writeString(reqUserName);
+        dest.writeString(ticket);
+    }
+
+    public static final Creator<ApplyForSpeechMessage> CREATOR = new Creator<ApplyForSpeechMessage>() {
+        @Override
+        public ApplyForSpeechMessage createFromParcel(Parcel source) {
+            return new ApplyForSpeechMessage(source);
+        }
+
+        @Override
+        public ApplyForSpeechMessage[] newArray(int size) {
+            return new ApplyForSpeechMessage[size];
+        }
+    };
+}

+ 82 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/AssistantTransferMessage.java

@@ -0,0 +1,82 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 助教转移消息
+ */
+@MessageTag(value = "SC:ATMsg", flag = MessageTag.NONE)
+public class AssistantTransferMessage extends MessageContent {
+    private final static String TAG = AssistantTransferMessage.class.getSimpleName();
+
+    private String opUserId;//操作者 ID
+    private String toUserId;//接受者 ID
+
+    public AssistantTransferMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            opUserId = jsonObject.optString("opUserId");
+            toUserId = jsonObject.optString("toUserId");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public AssistantTransferMessage(Parcel parcel) {
+        opUserId = parcel.readString();
+        toUserId = parcel.readString();
+    }
+
+    public String getOpUserId() {
+        return opUserId;
+    }
+
+    public String getToUserId() {
+        return toUserId;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(opUserId);
+        dest.writeString(toUserId);
+    }
+
+    public static final Creator<AssistantTransferMessage> CREATOR = new Creator<AssistantTransferMessage>() {
+        @Override
+        public AssistantTransferMessage createFromParcel(Parcel source) {
+            return new AssistantTransferMessage(source);
+        }
+
+        @Override
+        public AssistantTransferMessage[] newArray(int size) {
+            return new AssistantTransferMessage[size];
+        }
+    };
+}

+ 110 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/ControlDeviceNotifyMessage.java

@@ -0,0 +1,110 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+import android.util.Log;
+
+import com.daya.live_teaching.model.DeviceType;
+import com.daya.live_teaching.model.InviteAction;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 邀请开启设备消息
+ * 包括开启摄像头和开启麦克风
+ */
+@MessageTag(value = "SC:CDNMsg", flag = MessageTag.NONE)
+public class ControlDeviceNotifyMessage extends MessageContent {
+    private final static String TAG = ControlDeviceNotifyMessage.class.getSimpleName();
+
+    private int action;  // 1.邀请;2.拒绝;3.接受
+    private String ticket;
+    private int type;       //邀请类型:0.麦克风;1.摄像头
+    private String opUserId; // 操作人用户id
+    private String opUserName; // 操作人用户名
+
+    public ControlDeviceNotifyMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+            Log.e("ControlDeviceNotify",jsonStr);
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            action = jsonObject.optInt("action");
+            ticket = jsonObject.optString("ticket");
+            type = jsonObject.optInt("type");
+            opUserId = jsonObject.optString("opUserId");
+            opUserName = jsonObject.optString("opUserName");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public ControlDeviceNotifyMessage(Parcel parcel) {
+        action = parcel.readInt();
+        ticket = parcel.readString();
+        type = parcel.readInt();
+        opUserId = parcel.readString();
+        opUserName = parcel.readString();
+    }
+
+    public InviteAction getAction() {
+        return InviteAction.getAction(action);
+    }
+
+    public String getTicket() {
+        return ticket;
+    }
+
+    public DeviceType getType() {
+        return DeviceType.getDeviceType(type);
+    }
+
+    public String getOpUserId() {
+        return opUserId;
+    }
+
+    public String getOpUserName() {
+        return opUserName;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(action);
+        dest.writeString(ticket);
+        dest.writeInt(type);
+        dest.writeString(opUserId);
+        dest.writeString(opUserName);
+    }
+
+    public static final Creator<ControlDeviceNotifyMessage> CREATOR = new Creator<ControlDeviceNotifyMessage>() {
+        @Override
+        public ControlDeviceNotifyMessage createFromParcel(Parcel source) {
+            return new ControlDeviceNotifyMessage(source);
+        }
+
+        @Override
+        public ControlDeviceNotifyMessage[] newArray(int size) {
+            return new ControlDeviceNotifyMessage[size];
+        }
+    };
+}

+ 122 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/DeviceStateChangedMessage.java

@@ -0,0 +1,122 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.daya.live_teaching.model.DeviceType;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.common.ParcelUtils;
+import io.rong.common.RLog;
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 设备状态同步消息
+ */
+@MessageTag(value = "SC:DRMsg", flag = MessageTag.NONE)
+public class DeviceStateChangedMessage extends MessageContent {
+    private final static String TAG = DeviceStateChangedMessage.class.getSimpleName();
+
+    private Boolean enable; // 开启状态
+    private int type; // 设备类型:0.麦克风;1.摄像头 5 原生音量  6 伴奏音量
+    private String userId;
+    private String musicScoreAccompanimentId;
+    private int soundVolume;
+
+
+    public DeviceStateChangedMessage(byte[] data) {
+        Log.i("VOLEUM_TAG","musicScoreAccompanimentId");
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+            Log.e("VOLEUM_TAG",jsonStr);
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            enable = jsonObject.optBoolean("enable");
+            type = jsonObject.optInt("type");
+            userId = jsonObject.optString("userId");
+            if (jsonObject.has("soundVolume")) {
+                soundVolume = jsonObject.optInt("soundVolume");
+            }
+            musicScoreAccompanimentId = jsonObject.optString("musicScoreAccompanimentId");
+            Log.i("VOLEUM_TAG","musicScoreAccompanimentId  jsonObject " + jsonObject.toString());
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public DeviceStateChangedMessage(Parcel parcel) {
+        enable = parcel.readInt() != 0;
+        type = parcel.readInt();
+        userId = parcel.readString();
+        soundVolume = parcel.readInt();
+        musicScoreAccompanimentId = parcel.readString();
+        Log.i("VOLEUM_TAG","musicScoreAccompanimentId_Parcel _soundVolume=" +soundVolume);
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public DeviceType getType() {
+        return DeviceType.getDeviceType(type);
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public int getSoundVolume() {
+        return soundVolume;
+    }
+
+    public void setSoundVolume(int soundVolume) {
+        this.soundVolume = soundVolume;
+    }
+
+    public String getMusicScoreAccompanimentId() {
+        return musicScoreAccompanimentId;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(enable ? 1 : 0);
+        dest.writeInt(type);
+        dest.writeString(userId);
+        dest.writeInt(soundVolume);
+        dest.writeString(musicScoreAccompanimentId);
+
+    }
+
+    public static final Creator<DeviceStateChangedMessage> CREATOR = new Creator<DeviceStateChangedMessage>() {
+        @Override
+        public DeviceStateChangedMessage createFromParcel(Parcel source) {
+            return new DeviceStateChangedMessage(source);
+        }
+
+        @Override
+        public DeviceStateChangedMessage[] newArray(int size) {
+            return new DeviceStateChangedMessage[size];
+        }
+    };
+}

+ 74 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/DisplayMessage.java

@@ -0,0 +1,74 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+import android.util.Log;
+
+import com.daya.live_teaching.model.ScreenDisplay;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 切换显示消息
+ */
+@MessageTag(value = "SC:DisplayMsg", flag = MessageTag.NONE)
+public class DisplayMessage extends MessageContent {
+    private final static String TAG = DisplayMessage.class.getSimpleName();
+    private String display;
+
+    public DisplayMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            display = jsonObject.optString("display");
+        } catch (JSONException e) {
+            Log.e(TAG, e.toString());
+        }
+    }
+
+    public DisplayMessage(Parcel parcel) {
+        display = parcel.readString();
+
+    }
+
+    public ScreenDisplay getDisplay() {
+        return ScreenDisplay.createScreenDisplay(display);
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(display);
+    }
+
+    public static final Creator<DisplayMessage> CREATOR = new Creator<DisplayMessage>() {
+        @Override
+        public DisplayMessage createFromParcel(Parcel source) {
+            return new DisplayMessage(source);
+        }
+
+        @Override
+        public DisplayMessage[] newArray(int size) {
+            return new DisplayMessage[size];
+        }
+    };
+}

+ 95 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/ExamSongDownloadMessage.java

@@ -0,0 +1,95 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.cooleshow.base.utils.LogUtils;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 伴奏下载消息
+ */
+@MessageTag(value = "DY:examSongDownloadMessage", flag = MessageTag.NONE)
+public class ExamSongDownloadMessage extends MessageContent {
+    private final static String TAG = ExamSongDownloadMessage.class.getSimpleName();
+
+
+    private String examSongName;
+    private String examSongId;
+    private String url;
+
+    public ExamSongDownloadMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+            LogUtils.e(jsonStr);
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            examSongName = jsonObject.optString("examSongName");
+            examSongId = jsonObject.optString("examSongId");
+            url = jsonObject.optString("url");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public ExamSongDownloadMessage(Parcel parcel) {
+
+        examSongName = parcel.readString();
+        examSongId = parcel.readString();
+        url = parcel.readString();
+    }
+
+
+    public String getExamSongName() {
+        return examSongName;
+    }
+
+    public String getExamSongId() {
+        return examSongId;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+
+        dest.writeString(examSongName);
+        dest.writeString(examSongId);
+        dest.writeString(url);
+    }
+
+    public static final Creator<ExamSongDownloadMessage> CREATOR = new Creator<ExamSongDownloadMessage>() {
+        @Override
+        public ExamSongDownloadMessage createFromParcel(Parcel source) {
+            return new ExamSongDownloadMessage(source);
+        }
+
+        @Override
+        public ExamSongDownloadMessage[] newArray(int size) {
+            return new ExamSongDownloadMessage[size];
+        }
+    };
+}

+ 105 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/ExamSongDownloadStatusMessage.java

@@ -0,0 +1,105 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.cooleshow.base.utils.LogUtils;
+import com.daya.live_teaching.model.DownLoadSongStatus;
+import com.daya.live_teaching.utils.log.SLog;
+import com.google.gson.JsonObject;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 伴奏下载消息
+ */
+@MessageTag(value = "DY:MSDSMsg", flag = MessageTag.NONE)
+public class ExamSongDownloadStatusMessage extends MessageContent {
+    private final static String TAG = ExamSongDownloadStatusMessage.class.getSimpleName();
+
+
+    private String studentId;
+    private List<DownLoadSongStatus> downLoadSongStatus = new ArrayList<>();
+
+    public ExamSongDownloadStatusMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+            LogUtils.e(jsonStr);
+
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            studentId = jsonObject.optString("studentId");
+            JSONArray jsonArray = jsonObject.optJSONArray("studentMusicScores");
+            downLoadSongStatus.clear();
+            for (int i = 0; i < jsonArray.length(); i++) {
+                Object object = jsonArray.get(i);
+                JSONObject json = (JSONObject) object;
+                DownLoadSongStatus student = new DownLoadSongStatus(json.optString("musicScoreAccompanimentId"), json.optInt("downStatus")
+                        , json.optInt("playStatus"), json.optInt("accompanimentPlayStatus"), json.optInt("speed"), json.optString("url")
+                        , json.optString("mp3Url"), json.optString("musicScoreName"));
+                downLoadSongStatus.add(student);
+
+
+            }
+
+        } catch (Exception e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public ExamSongDownloadStatusMessage(Parcel parcel) {
+
+        studentId = parcel.readString();
+        downLoadSongStatus = parcel.createTypedArrayList(DownLoadSongStatus.CREATOR);
+    }
+
+
+    public String getStudentId() {
+        return studentId;
+    }
+
+
+    public List<DownLoadSongStatus> getDownLoadSongStatus() {
+        return downLoadSongStatus;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+
+        dest.writeString(studentId);
+        dest.writeTypedList(downLoadSongStatus);
+
+    }
+
+    public static final Creator<ExamSongDownloadStatusMessage> CREATOR = new Creator<ExamSongDownloadStatusMessage>() {
+        @Override
+        public ExamSongDownloadStatusMessage createFromParcel(Parcel source) {
+            return new ExamSongDownloadStatusMessage(source);
+        }
+
+        @Override
+        public ExamSongDownloadStatusMessage[] newArray(int size) {
+            return new ExamSongDownloadStatusMessage[size];
+        }
+    };
+}

+ 152 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/MemberChangedMessage.java

@@ -0,0 +1,152 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.cooleshow.base.utils.LogUtils;
+import com.daya.live_teaching.model.ClassMemberChangedAction;
+import com.daya.live_teaching.model.Role;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 课堂内用户进出消息
+ */
+@MessageTag(value = "SC:RMCMsg", flag = MessageTag.ISPERSISTED)
+public class MemberChangedMessage extends MessageContent {
+    private final static String  TAG = MemberChangedMessage.class.getSimpleName();
+    private              int     action;     // 用户行为:1.加入;2.离开;3.踢出
+    private              String  userId;
+    private              String  userName;
+    private              int     role;       // 用户角色
+    private              long    timestamp;
+    private              boolean metronomeSwitch;
+    private              boolean handUpOn;
+    private              boolean examSongSwitch;
+
+    public MemberChangedMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+            LogUtils.e(jsonStr);
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            action = jsonObject.optInt("action");
+            userId = jsonObject.optString("userId");
+            userName = jsonObject.optString("userName");
+            role = jsonObject.optInt("role");
+            timestamp = jsonObject.optLong("timestamp");
+            metronomeSwitch = jsonObject.optBoolean("metronomeSwitch");
+            handUpOn = jsonObject.optBoolean("handUpOn");
+            examSongSwitch = jsonObject.optBoolean("examSongSwitch");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public MemberChangedMessage(Parcel in) {
+        action = in.readInt();
+        userId = in.readString();
+        userName = in.readString();
+        role = in.readInt();
+        timestamp = in.readLong();
+        metronomeSwitch = in.readByte() == 1;
+        handUpOn = in.readByte() == 1;
+        examSongSwitch = in.readByte() == 1;
+    }
+
+    public ClassMemberChangedAction getAction() {
+        return ClassMemberChangedAction.getAction(action);
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public Role getRole() {
+        return Role.createRole(role);
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public boolean isMetronomeSwitch() {
+        return metronomeSwitch;
+    }
+
+    public boolean isHandUpOn() {
+        return handUpOn;
+    }
+
+    public boolean isExamSongSwitch() {
+        return examSongSwitch;
+    }
+
+    @Override
+    public byte[] encode() {
+        JSONObject jsonObject = new JSONObject();
+        try {
+            jsonObject.put("action", action);
+            jsonObject.put("userId", userId);
+            jsonObject.put("userName", userName);
+            jsonObject.put("role", role);
+            jsonObject.put("timestamp", timestamp);
+            jsonObject.put("metronomeSwitch", metronomeSwitch);
+            jsonObject.put("handUpOn", handUpOn);
+            jsonObject.put("examSongSwitch", examSongSwitch);
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+
+        try {
+            return jsonObject.toString().getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(action);
+        dest.writeString(userId);
+        dest.writeString(userName);
+        dest.writeInt(role);
+        dest.writeLong(timestamp);
+        dest.writeByte((byte) (metronomeSwitch ? 1 : 0));
+        dest.writeByte((byte) (handUpOn ? 1 : 0));
+        dest.writeByte((byte) (examSongSwitch ? 1 : 0));
+
+    }
+
+    public static final Creator<MemberChangedMessage> CREATOR = new Creator<MemberChangedMessage>() {
+        @Override
+        public MemberChangedMessage createFromParcel(Parcel source) {
+            return new MemberChangedMessage(source);
+        }
+
+        @Override
+        public MemberChangedMessage[] newArray(int size) {
+            return new MemberChangedMessage[size];
+        }
+    };
+}

+ 100 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/MusicScoreDownloadMessage.java

@@ -0,0 +1,100 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.cooleshow.base.utils.LogUtils;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 助教转移消息
+ */
+@MessageTag(value = "DY:musicScoreDownloadMessage", flag = MessageTag.NONE)
+public class MusicScoreDownloadMessage extends MessageContent {
+    private final static String TAG = MusicScoreDownloadMessage.class.getSimpleName();
+
+    private String id;
+    private String examSongId;
+    private String mp3Url;
+    private String url;
+
+    public MusicScoreDownloadMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        LogUtils.e(jsonStr);
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            id = jsonObject.optString("id");
+            examSongId = jsonObject.optString("examSongId");
+            mp3Url = jsonObject.optString("mp3Url");
+            url = jsonObject.optString("url");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public MusicScoreDownloadMessage(Parcel parcel) {
+        id = parcel.readString();
+        examSongId = parcel.readString();
+        mp3Url = parcel.readString();
+        url = parcel.readString();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getExamSongId() {
+        return examSongId;
+    }
+
+    public String getMp3Url() {
+        return mp3Url;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(id);
+        dest.writeString(examSongId);
+        dest.writeString(mp3Url);
+        dest.writeString(url);
+
+    }
+
+    public static final Creator<MusicScoreDownloadMessage> CREATOR = new Creator<MusicScoreDownloadMessage>() {
+        @Override
+        public MusicScoreDownloadMessage createFromParcel(Parcel source) {
+            return new MusicScoreDownloadMessage(source);
+        }
+
+        @Override
+        public MusicScoreDownloadMessage[] newArray(int size) {
+            return new MusicScoreDownloadMessage[size];
+        }
+    };
+}

+ 108 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/PlayMidiMessage.java

@@ -0,0 +1,108 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.cooleshow.base.utils.LogUtils;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 旁听申请发言消息
+ */
+@MessageTag(value = "DY:PlayMidiMessage", flag = MessageTag.NONE)
+public class PlayMidiMessage extends MessageContent {
+    private final static String TAG = PlayMidiMessage.class.getSimpleName();
+
+    private String  userId;
+    private boolean enable;
+    private int     rate;
+    private int     customType;
+    private int     playVolume;
+
+    public PlayMidiMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+            LogUtils.e(jsonStr);
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            userId = jsonObject.optString("userId");
+            enable = jsonObject.optBoolean("enable");
+            rate = jsonObject.optInt("rate");
+            customType = jsonObject.optInt("customType");
+            playVolume = jsonObject.optInt("playVolume");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public PlayMidiMessage(Parcel parcel) {
+        userId = parcel.readString();
+        enable = parcel.readByte() == 1;
+        rate = parcel.readInt();
+        customType = parcel.readInt();
+        playVolume = parcel.readInt();
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public boolean isEnable() {
+        return enable;
+    }
+
+    public int getRate() {
+        return rate;
+    }
+
+    public int getCustomType() {
+        return customType;
+    }
+
+    public int getPlayVolume() {
+        return playVolume;
+    }
+
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(userId);
+        dest.writeByte((byte) (enable ? 1 : 0));
+        dest.writeInt(rate);
+        dest.writeInt(customType);
+        dest.writeInt(playVolume);
+    }
+
+    public static final Creator<PlayMidiMessage> CREATOR = new Creator<PlayMidiMessage>() {
+        @Override
+        public PlayMidiMessage createFromParcel(Parcel source) {
+            return new PlayMidiMessage(source);
+        }
+
+        @Override
+        public PlayMidiMessage[] newArray(int size) {
+            return new PlayMidiMessage[size];
+        }
+    };
+}

+ 133 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/RoleChangedMessage.java

@@ -0,0 +1,133 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.daya.live_teaching.model.RoleChangedUser;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 多用户角色改变消息
+ */
+@MessageTag(value = "SC:RCMsg", flag = MessageTag.NONE)
+public class RoleChangedMessage extends MessageContent {
+    private final static String TAG = RoleChangedMessage.class.getSimpleName();
+
+    private String                opUserId;
+    private List<RoleChangedUser> users;
+
+
+    public RoleChangedMessage(){
+    }
+    public RoleChangedMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            opUserId = jsonObject.optString("opUserId");
+            JSONArray usersJson = jsonObject.optJSONArray("users");
+            users = new ArrayList<>();
+            if (usersJson != null && usersJson.length() > 0) {
+                int length = usersJson.length();
+                for (int i = 0; i < length; i++) {
+                    JSONObject usersJSONObject = usersJson.getJSONObject(i);
+                    RoleChangedUser user = new RoleChangedUser();
+                    user.setUserId(usersJSONObject.optString("userId"));
+                    user.setUserName(usersJSONObject.optString("userName"));
+                    user.setRole(usersJSONObject.optInt("role"));
+                    users.add(user);
+                }
+            }
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public RoleChangedMessage(Parcel parcel) {
+        opUserId = parcel.readString();
+        users = parcel.createTypedArrayList(RoleChangedUser.CREATOR);
+    }
+
+    public String getOpUserId() {
+        return opUserId;
+    }
+
+    public List<RoleChangedUser> getUsers() {
+        return users;
+    }
+
+    public void setOpUserId(String opUserId) {
+        this.opUserId = opUserId;
+    }
+
+    public void setUsers(List<RoleChangedUser> users) {
+        this.users = users;
+    }
+
+    @Override
+    public byte[] encode() {
+        JSONObject jsonObject = new JSONObject();
+        try {
+            jsonObject.put("opUserId", opUserId);
+
+            if(users != null && users.size() > 0){
+                JSONArray jsonArray = new JSONArray();
+                for(RoleChangedUser user : users) {
+                    JSONObject userJson = new JSONObject();
+                    userJson.put("userId", user.getUserId());
+                    userJson.put("userName", user.getUserName());
+                    userJson.put("role", user.getRole().getValue());
+                    jsonArray.put(userJson);
+                }
+                jsonObject.put("users", jsonArray);
+            }
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+
+        try {
+            return jsonObject.toString().getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(opUserId);
+        dest.writeTypedList(users);
+    }
+
+    public static final Creator<RoleChangedMessage> CREATOR = new Creator<RoleChangedMessage>() {
+        @Override
+        public RoleChangedMessage createFromParcel(Parcel source) {
+            return new RoleChangedMessage(source);
+        }
+
+        @Override
+        public RoleChangedMessage[] newArray(int size) {
+            return new RoleChangedMessage[size];
+        }
+    };
+}

+ 118 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/RoleSingleChangedMessage.java

@@ -0,0 +1,118 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.daya.live_teaching.model.RoleChangedUser;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 单用户角色改变消息
+ * 此消息仅用于本地显示单条角色变化显示用
+ */
+@MessageTag(value = "SCX:RCMsg", flag = MessageTag.ISPERSISTED)
+public class RoleSingleChangedMessage extends MessageContent {
+    private final static String TAG = RoleSingleChangedMessage.class.getSimpleName();
+
+    private String          opUserId;
+    private RoleChangedUser user;
+
+    public RoleSingleChangedMessage(){
+    }
+
+    public RoleSingleChangedMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            opUserId = jsonObject.optString("opUserId");
+            JSONObject usersJson = jsonObject.optJSONObject("user");
+            RoleChangedUser user = new RoleChangedUser();
+            user.setUserId(usersJson.optString("userId"));
+            user.setUserName(usersJson.optString("userName"));
+            user.setRole(usersJson.optInt("role"));
+            this.user = user;
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public RoleSingleChangedMessage(Parcel parcel) {
+        opUserId = parcel.readString();
+        user = parcel.readParcelable(RoleChangedUser.class.getClassLoader());
+    }
+
+    public String getOpUserId() {
+        return opUserId;
+    }
+
+    public RoleChangedUser getUser() {
+        return user;
+    }
+
+    public void setOpUserId(String opUserId) {
+        this.opUserId = opUserId;
+    }
+
+    public void setUser(RoleChangedUser user) {
+        this.user = user;
+    }
+
+    @Override
+    public byte[] encode() {
+        JSONObject jsonObject = new JSONObject();
+        try {
+            jsonObject.put("opUserId", opUserId);
+
+            JSONObject userJson = new JSONObject();
+            userJson.put("userId", user.getUserId());
+            userJson.put("userName", user.getUserName());
+            userJson.put("role", user.getRole().getValue());
+            jsonObject.put("user", userJson);
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+
+        try {
+            return jsonObject.toString().getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(opUserId);
+        dest.writeParcelable(user, 0);
+    }
+
+    public static final Creator<RoleSingleChangedMessage> CREATOR = new Creator<RoleSingleChangedMessage>() {
+        @Override
+        public RoleSingleChangedMessage createFromParcel(Parcel source) {
+            return new RoleSingleChangedMessage(source);
+        }
+
+        @Override
+        public RoleSingleChangedMessage[] newArray(int size) {
+            return new RoleSingleChangedMessage[size];
+        }
+    };
+}

+ 106 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/SpeechResultMessage.java

@@ -0,0 +1,106 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 申请发言返回结果消息
+ */
+@MessageTag(value = "SC:SRMsg", flag = MessageTag.NONE)
+public class SpeechResultMessage extends MessageContent {
+    private final static String TAG = SpeechResultMessage.class.getSimpleName();
+
+    private String opUserId;    //操作用户id
+    private String opUserName;
+    private String reqUserId;   //请求用户id
+    private String reqUserName;
+    private int action;         // 1.同意;2.拒绝
+
+    public SpeechResultMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            opUserId = jsonObject.optString("opUserId");
+            opUserName = jsonObject.optString("opUserName");
+            reqUserId = jsonObject.optString("reqUserId");
+            reqUserName = jsonObject.optString("reqUserName");
+            action = jsonObject.optInt("action");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public String getOpUserId() {
+        return opUserId;
+    }
+
+    public String getOpUserName() {
+        return opUserName;
+    }
+
+    public String getReqUserId() {
+        return reqUserId;
+    }
+
+    public String getReqUserName() {
+        return reqUserName;
+    }
+
+    public boolean isAccept() {
+        // 1.同意;2.拒绝
+        return action == 1;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    public SpeechResultMessage(Parcel parcel) {
+        opUserId = parcel.readString();
+        opUserName = parcel.readString();
+        reqUserId = parcel.readString();
+        reqUserName = parcel.readString();
+        action = parcel.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(opUserId);
+        dest.writeString(opUserName);
+        dest.writeString(reqUserId);
+        dest.writeString(reqUserName);
+        dest.writeInt(action);
+    }
+
+    public static final Creator<SpeechResultMessage> CREATOR = new Creator<SpeechResultMessage>() {
+        @Override
+        public SpeechResultMessage createFromParcel(Parcel source) {
+            return new SpeechResultMessage(source);
+        }
+
+        @Override
+        public SpeechResultMessage[] newArray(int size) {
+            return new SpeechResultMessage[size];
+        }
+    };
+}

+ 93 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/TicketExpiredMessage.java

@@ -0,0 +1,93 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.cooleshow.base.utils.LogUtils;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 请求超时消息
+ */
+@MessageTag(value = "SC:TEMsg", flag = MessageTag.NONE)
+public class TicketExpiredMessage extends MessageContent {
+    private final static String TAG = ControlDeviceNotifyMessage.class.getSimpleName();
+    private String ticket;
+    private String fromUserId;//请求发言者
+    private String toUserId;//处理请求者
+
+    public TicketExpiredMessage(byte[] data) {
+        LogUtils.i("VOLEUM_TAG","TicketExpiredMessage");
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            ticket = jsonObject.optString("ticket");
+            LogUtils.i("VOLEUM_TAG","TicketExpiredMessage ==ticket " + ticket);
+            LogUtils.i("VOLEUM_TAG","TicketExpiredMessage ==jsonObject " + jsonObject.toString());
+            fromUserId = jsonObject.optString("fromUserId");
+            toUserId = jsonObject.optString("toUserId");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public TicketExpiredMessage(Parcel parcel) {
+        ticket = parcel.readString();
+        LogUtils.i("VOLEUM_TAG","TicketExpiredMessage--parcel ==ticket " + ticket);
+        fromUserId = parcel.readString();
+        toUserId = parcel.readString();
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public String getTicket() {
+        return ticket;
+    }
+
+    public String getFromUserId() {
+        return fromUserId;
+    }
+
+    public String getToUserId() {
+        return toUserId;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(ticket);
+        dest.writeString(fromUserId);
+        dest.writeString(toUserId);
+    }
+
+    public static final Creator<TicketExpiredMessage> CREATOR = new Creator<TicketExpiredMessage>() {
+        @Override
+        public TicketExpiredMessage createFromParcel(Parcel source) {
+            return new TicketExpiredMessage(source);
+        }
+
+        @Override
+        public TicketExpiredMessage[] newArray(int size) {
+            return new TicketExpiredMessage[size];
+        }
+    };
+}

+ 87 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/TurnPageMessage.java

@@ -0,0 +1,87 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 切换白板页数消息
+ */
+@MessageTag(value = "SC:WBTPMsg", flag = MessageTag.NONE)
+public class TurnPageMessage extends MessageContent {
+    private final static String TAG = TurnPageMessage.class.getSimpleName();
+
+    private String whiteboardId;  //白板 ID
+    private String userId; //操作人 ID
+    private int curPg; //当前页
+
+    public TurnPageMessage(byte[] data){
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            whiteboardId = jsonObject.optString("whiteboardId");
+            userId = jsonObject.optString("userId");
+            curPg = jsonObject.optInt("curPg");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public TurnPageMessage(Parcel parcel){
+        whiteboardId = parcel.readString();
+        userId = parcel.readString();
+        curPg = parcel.readInt();
+    }
+
+    public String getWhiteboardId() {
+        return whiteboardId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public int getCurPg() {
+        return curPg;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+
+    }
+
+    public static final Creator<TurnPageMessage> CREATOR = new Creator<TurnPageMessage>() {
+        @Override
+        public TurnPageMessage createFromParcel(Parcel source) {
+            return new TurnPageMessage(source);
+        }
+
+        @Override
+        public TurnPageMessage[] newArray(int size) {
+            return new TurnPageMessage[size];
+        }
+    };
+}

+ 109 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/UpgradeRoleMessage.java

@@ -0,0 +1,109 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.daya.live_teaching.model.InviteAction;
+import com.daya.live_teaching.model.Role;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 升级角色权限消息,被邀请人收到此消息
+ */
+@MessageTag(value = "SC:IURMsg", flag = MessageTag.NONE)
+public class UpgradeRoleMessage extends MessageContent {
+    private final static String TAG = RoleChangedMessage.class.getSimpleName();
+
+    private int action;     // 行为类型:1.邀请;2.拒绝;3.同意
+    private String opUserId;
+    private String opUserName;
+    private int role;       // 被提升到的权限
+    private String ticket;  // 凭证 id
+
+    public UpgradeRoleMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            action = jsonObject.optInt("action");
+            opUserId = jsonObject.optString("opUserId");
+            opUserName = jsonObject.optString("opUserName");
+            role = jsonObject.optInt("role");
+            ticket = jsonObject.optString("ticket");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public UpgradeRoleMessage(Parcel parcel) {
+        action = parcel.readInt();
+        opUserId = parcel.readString();
+        opUserName = parcel.readString();
+        role = parcel.readInt();
+        ticket = parcel.readString();
+    }
+
+    public InviteAction getAction() {
+        return InviteAction.getAction(action);
+    }
+
+    public String getOpUserId() {
+        return opUserId;
+    }
+
+    public String getOpUserName() {
+        return opUserName;
+    }
+
+    public Role getRole() {
+        return Role.createRole(role);
+    }
+
+    public String getTicket() {
+        return ticket;
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(action);
+        dest.writeString(opUserId);
+        dest.writeString(opUserName);
+        dest.writeInt(role);
+        dest.writeString(ticket);
+    }
+
+    public static final Creator<UpgradeRoleMessage> CREATOR = new Creator<UpgradeRoleMessage>() {
+        @Override
+        public UpgradeRoleMessage createFromParcel(Parcel source) {
+            return new UpgradeRoleMessage(source);
+        }
+
+        @Override
+        public UpgradeRoleMessage[] newArray(int size) {
+            return new UpgradeRoleMessage[size];
+        }
+    };
+
+}

+ 89 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/message/WhiteBoardMessage.java

@@ -0,0 +1,89 @@
+package com.daya.live_teaching.im.message;
+
+import android.os.Parcel;
+
+import com.daya.live_teaching.model.WhiteBoardAction;
+import com.daya.live_teaching.utils.log.SLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import io.rong.imlib.MessageTag;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 课堂白板变动消息
+ */
+@MessageTag(value = "SC:WBMsg", flag = MessageTag.NONE)
+public class WhiteBoardMessage extends MessageContent {
+    private final static String TAG = WhiteBoardMessage.class.getSimpleName();
+    private String whiteboardId;
+    private String whiteboardName;
+    private int action; // 白板变动行为:1.创建;2.删除
+
+    public WhiteBoardMessage(byte[] data) {
+        String jsonStr = null;
+        try {
+            jsonStr = new String(data, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            SLog.e(TAG, e.toString());
+        }
+        try {
+            JSONObject jsonObject = new JSONObject(jsonStr);
+            action = jsonObject.optInt("action");
+            whiteboardId = jsonObject.optString("whiteboardId");
+            whiteboardName = jsonObject.optString("whiteboardName");
+        } catch (JSONException e) {
+            SLog.e(TAG, e.toString());
+        }
+    }
+
+    public WhiteBoardMessage(Parcel in) {
+        whiteboardId = in.readString();
+        whiteboardName = in.readString();
+        action = in.readInt();
+    }
+
+    public String getWhiteboardId() {
+        return whiteboardId;
+    }
+
+    public String getWhiteboardName() {
+        return whiteboardName;
+    }
+
+    public WhiteBoardAction getAction() {
+        return WhiteBoardAction.getAction(action);
+    }
+
+    @Override
+    public byte[] encode() {
+        return new byte[0];
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(whiteboardId);
+        dest.writeString(whiteboardName);
+        dest.writeInt(action);
+    }
+
+    public static final Creator<WhiteBoardMessage> CREATOR = new Creator<WhiteBoardMessage>() {
+        @Override
+        public WhiteBoardMessage createFromParcel(Parcel source) {
+            return new WhiteBoardMessage(source);
+        }
+
+        @Override
+        public WhiteBoardMessage[] newArray(int size) {
+            return new WhiteBoardMessage[size];
+        }
+    };
+}

+ 29 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/provider/BaseNotificationProvider.java

@@ -0,0 +1,29 @@
+package com.daya.live_teaching.im.provider;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.daya.live_teaching.R;
+
+import io.rong.imkit.conversation.messgelist.provider.BaseNotificationMessageItemProvider;
+import io.rong.imkit.widget.adapter.ViewHolder;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 显示提示消息用基础消息模版
+ *
+ * @param <T>
+ */
+public abstract class BaseNotificationProvider<T extends MessageContent> extends BaseNotificationMessageItemProvider<T> {
+
+
+    @Override
+    protected ViewHolder onCreateMessageContentViewHolder(ViewGroup parent, int viewType) {
+        View contentView = LayoutInflater.from(parent.getContext()).inflate(R.layout.class_item_conversation_notify_message, parent, false);
+        return new ViewHolder(parent.getContext(), contentView);
+    }
+
+
+
+}

+ 40 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/provider/ClassFileMessageItemProvider.java

@@ -0,0 +1,40 @@
+package com.daya.live_teaching.im.provider;
+
+import android.widget.LinearLayout;
+
+import com.daya.live_teaching.R;
+
+import java.util.List;
+
+import io.rong.imkit.conversation.messgelist.provider.FileMessageItemProvider;
+import io.rong.imkit.conversation.messgelist.provider.MessageItemProviderConfig;
+import io.rong.imkit.model.UiMessage;
+import io.rong.imkit.widget.adapter.IViewProviderListener;
+import io.rong.imkit.widget.adapter.ViewHolder;
+import io.rong.message.FileMessage;
+
+
+/**
+ * 自定义文件消息样式
+ * 修改了背景图
+ */
+public class ClassFileMessageItemProvider extends FileMessageItemProvider {
+
+
+    protected MessageItemProviderConfig mConfig = new MessageItemProviderConfig();
+
+    public ClassFileMessageItemProvider() {
+        mConfig.showProgress = false;
+        mConfig.showReadState = true;
+    }
+
+    @Override
+    protected void bindMessageContentViewHolder(ViewHolder holder, ViewHolder parentHolder, FileMessage fileMessage, UiMessage uiMessage, int position, List<UiMessage> list, IViewProviderListener<UiMessage> listener) {
+        super.bindMessageContentViewHolder(holder, parentHolder, fileMessage, uiMessage, position, list, listener);
+
+        LinearLayout msgContainer = holder.getView(io.rong.imkit.R.id.rc_message);
+        msgContainer.setBackgroundResource(R.drawable.bg_conversation_item_message);
+    }
+
+
+}

+ 81 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/provider/ClassImageMessageItemProvider.java

@@ -0,0 +1,81 @@
+package com.daya.live_teaching.im.provider;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.RequestOptions;
+import com.bumptech.glide.request.target.Target;
+import io.rong.imkit.R;
+
+import java.util.List;
+
+import io.rong.imkit.IMCenter;
+import io.rong.imkit.conversation.messgelist.provider.ImageMessageItemProvider;
+import io.rong.imkit.conversation.messgelist.provider.MessageItemProviderConfig;
+import io.rong.imkit.model.UiMessage;
+import io.rong.imkit.widget.adapter.IViewProviderListener;
+import io.rong.imkit.widget.adapter.ViewHolder;
+import io.rong.imlib.model.Message;
+import io.rong.message.ImageMessage;
+
+import static io.rong.imkit.conversation.messgelist.provider.SightMessageItemProvider.dip2pix;
+
+
+/**
+ * 自定义图片消息样式
+ * 修改了背景
+ */
+public class ClassImageMessageItemProvider extends ImageMessageItemProvider {
+
+    protected MessageItemProviderConfig mConfig = new MessageItemProviderConfig();
+
+    public ClassImageMessageItemProvider() {
+        mConfig.showProgress = false;
+        mConfig.showReadState = true;
+    }
+
+    @Override
+    protected void bindMessageContentViewHolder(ViewHolder holder, ViewHolder parentHolder, ImageMessage message, UiMessage uiMessage, int position, List<UiMessage> list, IViewProviderListener<UiMessage> listener) {
+        super.bindMessageContentViewHolder(holder, parentHolder, message, uiMessage, position, list, listener);
+        ImageView imageView = holder.getView(R.id.rc_img);
+        TextView messageTv = holder.getView(R.id.rc_msg);
+        RequestOptions options = RequestOptions.bitmapTransform(new RoundedCorners(dip2pix(IMCenter.getInstance().getContext(), 6))).override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
+        Glide.with(imageView).load(message.getThumUri())
+                .apply(options)
+                .listener(new RequestListener<Drawable>() {
+                    @Override
+                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+                        return false;
+                    }
+
+                    @Override
+                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+                        measureLayoutParams(holder.getView(io.rong.imkit.R.id.rl_content), resource);
+                        return false;
+                    }
+                })
+                .into(imageView);
+
+        // 设置图片加载
+        int progress = uiMessage.getProgress();
+        Message.SentStatus status = uiMessage.getSentStatus();
+
+        if (status.equals(Message.SentStatus.SENDING) && progress < 100) {
+            messageTv.setText(progress + "%");
+            messageTv.setVisibility(View.VISIBLE);
+        } else {
+            messageTv.setVisibility(View.GONE);
+        }
+    }
+
+
+}

+ 75 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/provider/ClassMemberChangedNotificationProvider.java

@@ -0,0 +1,75 @@
+package com.daya.live_teaching.im.provider;
+
+import android.content.Context;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.widget.TextView;
+
+import com.daya.live_teaching.R;
+import com.daya.live_teaching.im.message.MemberChangedMessage;
+import com.daya.live_teaching.model.ClassMemberChangedAction;
+
+import java.util.List;
+
+import io.rong.imkit.RongIM;
+import io.rong.imkit.conversation.messgelist.provider.MessageItemProviderConfig;
+import io.rong.imkit.model.UiMessage;
+import io.rong.imkit.widget.adapter.IViewProviderListener;
+import io.rong.imkit.widget.adapter.ViewHolder;
+import io.rong.imlib.model.MessageContent;
+
+/**
+ * 用户进出课堂消息提示模版
+ */
+public class ClassMemberChangedNotificationProvider extends BaseNotificationProvider<MemberChangedMessage> {
+
+    protected MessageItemProviderConfig mConfig = new MessageItemProviderConfig();
+
+    public ClassMemberChangedNotificationProvider() {
+        mConfig.showPortrait = false;
+        mConfig.showProgress = false;
+        mConfig.showSummaryWithName = false;
+        mConfig.centerInHorizontal = true;
+    }
+    @Override
+    protected void bindMessageContentViewHolder(ViewHolder holder, ViewHolder parentHolder, MemberChangedMessage memberChangedMessage, UiMessage uiMessage, int position, List<UiMessage> list, IViewProviderListener<UiMessage> listener) {
+        TextView tvMessage = holder.getView(R.id.tv_message);
+        tvMessage.setText(getNotifyContent(holder.getContext(), memberChangedMessage));
+    }
+
+
+
+    @Override
+    protected boolean isMessageViewType(MessageContent messageContent) {
+        return messageContent instanceof MemberChangedMessage && !messageContent.isDestruct();
+    }
+    public String getNotifyContent(Context context, MemberChangedMessage memberChangedMessage) {
+        String content = "";
+
+        String userName = memberChangedMessage.getUserName();
+        String currentUserId = RongIM.getInstance().getCurrentUserId();
+        if (userName != null && userName.equals(currentUserId)) {
+            userName = context.getString(R.string.you);
+        }
+        ClassMemberChangedAction action = memberChangedMessage.getAction();
+        switch (action) {
+            case JOIN:
+                content = context.getString(R.string.class_conversation_notify_member_join_format, userName);
+                break;
+            case LEAVE:
+                content = context.getString(R.string.class_conversation_notify_member_leave_format, userName);
+                break;
+            case KICK:
+                content = context.getString(R.string.class_conversation_notify_member_kick_format, userName);
+                break;
+        }
+
+        return content;
+    }
+
+    @Override
+    public Spannable getSummarySpannable(Context context, MemberChangedMessage memberChangedMessage) {
+        return new SpannableString(getNotifyContent(context, memberChangedMessage));
+    }
+
+}

+ 32 - 0
live_teaching/src/main/java/com/daya/live_teaching/im/provider/ClassTextMessageItemProvider.java

@@ -0,0 +1,32 @@
+package com.daya.live_teaching.im.provider;
+
+import io.rong.imkit.conversation.messgelist.provider.TextMessageItemProvider;
+
+/**
+ * 自定义文本消息样式
+ * 修改了背景图
+ */
+public class ClassTextMessageItemProvider extends TextMessageItemProvider {
+//    private static int padding;
+
+
+
+//    static {
+//        padding = DisplayUtils.dip2px(BaseApplication.getApplication().getApplicationContext(), 12);
+//    }
+//
+//    @Override
+//    protected void bindMessageContentViewHolder(ViewHolder holder, ViewHolder parentHolder, TextMessage message, UiMessage uiMessage, int position, List<UiMessage> list, IViewProviderListener<UiMessage> listener) {
+//        super.bindMessageContentViewHolder(holder, parentHolder, message, uiMessage, position, list, listener);
+//
+//
+////        AutoLinkTextView message = v.findViewById(android.R.id.text1);
+////        message.setPadding(padding, padding, padding, padding);
+////        message.setBackgroundResource(R.drawable.bg_conversation_item_message);
+//    }
+//
+//    @Override
+//    public void initUserInfo(ViewHolder holder, UiMessage uiMessage, int position, IViewProviderListener<UiMessage> listener, boolean isSender) {
+//        super.initUserInfo(holder, uiMessage, position, listener, isSender);
+//    }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов