瀏覽代碼

1,奠定vr视频基线版本

lyn 5 年之前
當前提交
2181ddfa88
共有 100 個文件被更改,包括 5068 次插入0 次删除
  1. 13 0
      .gitignore
  2. 4 0
      .idea/encodings.xml
  3. 21 0
      .idea/gradle.xml
  4. 14 0
      .idea/misc.xml
  5. 12 0
      .idea/runConfigurations.xml
  6. 6 0
      .idea/vcs.xml
  7. 1 0
      app/.gitignore
  8. 75 0
      app/build.gradle
  9. 21 0
      app/proguard-rules.pro
  10. 27 0
      app/src/androidTest/java/com/haochuan/hciptvbasic/ExampleInstrumentedTest.java
  11. 57 0
      app/src/main/AndroidManifest.xml
  12. 54 0
      app/src/main/java/com/haochuan/hciptvbasic/BaseApp.java
  13. 412 0
      app/src/main/java/com/haochuan/hciptvbasic/BaseWebActivity.java
  14. 51 0
      app/src/main/java/com/haochuan/hciptvbasic/MainActivity.java
  15. 149 0
      app/src/main/java/com/haochuan/hciptvbasic/SystemPlayerWrapper.java
  16. 24 0
      app/src/main/java/com/haochuan/hciptvbasic/util/CustomProjectionFactory.java
  17. 67 0
      app/src/main/java/com/haochuan/hciptvbasic/util/VRUtil.java
  18. 26 0
      app/src/main/java/com/haochuan/hciptvbasic/webview/HCWebChromeClient.java
  19. 109 0
      app/src/main/java/com/haochuan/hciptvbasic/webview/PayToJS.java
  20. 573 0
      app/src/main/java/com/haochuan/hciptvbasic/webview/PlayerToJS.java
  21. 192 0
      app/src/main/java/com/haochuan/hciptvbasic/webview/UtilToJS.java
  22. 34 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  23. 9 0
      app/src/main/res/drawable/bg_ad_time.xml
  24. 26 0
      app/src/main/res/drawable/frame_anim.xml
  25. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  26. 二進制
      app/src/main/res/drawable/loading_anim_0.png
  27. 二進制
      app/src/main/res/drawable/loading_anim_1.png
  28. 二進制
      app/src/main/res/drawable/loading_anim_10.png
  29. 二進制
      app/src/main/res/drawable/loading_anim_11.png
  30. 二進制
      app/src/main/res/drawable/loading_anim_12.png
  31. 二進制
      app/src/main/res/drawable/loading_anim_13.png
  32. 二進制
      app/src/main/res/drawable/loading_anim_14.png
  33. 二進制
      app/src/main/res/drawable/loading_anim_15.png
  34. 二進制
      app/src/main/res/drawable/loading_anim_16.png
  35. 二進制
      app/src/main/res/drawable/loading_anim_17.png
  36. 二進制
      app/src/main/res/drawable/loading_anim_18.png
  37. 二進制
      app/src/main/res/drawable/loading_anim_19.png
  38. 二進制
      app/src/main/res/drawable/loading_anim_2.png
  39. 二進制
      app/src/main/res/drawable/loading_anim_20.png
  40. 二進制
      app/src/main/res/drawable/loading_anim_21.png
  41. 二進制
      app/src/main/res/drawable/loading_anim_3.png
  42. 二進制
      app/src/main/res/drawable/loading_anim_4.png
  43. 二進制
      app/src/main/res/drawable/loading_anim_5.png
  44. 二進制
      app/src/main/res/drawable/loading_anim_6.png
  45. 二進制
      app/src/main/res/drawable/loading_anim_7.png
  46. 二進制
      app/src/main/res/drawable/loading_anim_8.png
  47. 二進制
      app/src/main/res/drawable/loading_anim_9.png
  48. 二進制
      app/src/main/res/drawable/loading_bg.jpg
  49. 18 0
      app/src/main/res/layout/activity_main.xml
  50. 73 0
      app/src/main/res/layout/activity_test.xml
  51. 134 0
      app/src/main/res/layout/activity_test_player.xml
  52. 15 0
      app/src/main/res/layout/view_video_control_empty.xml
  53. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  54. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  55. 二進制
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  56. 二進制
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  57. 二進制
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  58. 二進制
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  59. 二進制
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  60. 二進制
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  61. 二進制
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  62. 二進制
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  63. 二進制
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  64. 二進制
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  65. 6 0
      app/src/main/res/values/colors.xml
  66. 3 0
      app/src/main/res/values/strings.xml
  67. 15 0
      app/src/main/res/values/styles.xml
  68. 17 0
      app/src/test/java/com/haochuan/hciptvbasic/ExampleUnitTest.java
  69. 62 0
      build.gradle
  70. 1 0
      core/.gitignore
  71. 39 0
      core/build.gradle
  72. 21 0
      core/proguard-rules.pro
  73. 27 0
      core/src/androidTest/java/com/haochuan/core/ExampleInstrumentedTest.java
  74. 7 0
      core/src/main/AndroidManifest.xml
  75. 109 0
      core/src/main/java/com/haochuan/core/BaseMediaPlayer.java
  76. 21 0
      core/src/main/java/com/haochuan/core/IVideoPlayer.java
  77. 166 0
      core/src/main/java/com/haochuan/core/Logger.java
  78. 100 0
      core/src/main/java/com/haochuan/core/http/DownloadServer.java
  79. 51 0
      core/src/main/java/com/haochuan/core/http/MyRequest.java
  80. 230 0
      core/src/main/java/com/haochuan/core/http/RequestServer.java
  81. 11 0
      core/src/main/java/com/haochuan/core/http/ResponseListener.java
  82. 211 0
      core/src/main/java/com/haochuan/core/http/UpgradeDialog.java
  83. 147 0
      core/src/main/java/com/haochuan/core/http/bean/ApkSettingBean.java
  84. 41 0
      core/src/main/java/com/haochuan/core/http/bean/ResponseBean.java
  85. 196 0
      core/src/main/java/com/haochuan/core/http/bean/UpdateResponseBean.java
  86. 108 0
      core/src/main/java/com/haochuan/core/util/ELS.java
  87. 124 0
      core/src/main/java/com/haochuan/core/util/FileUtil.java
  88. 47 0
      core/src/main/java/com/haochuan/core/util/HandlerUtil.java
  89. 59 0
      core/src/main/java/com/haochuan/core/util/JSONUtil.java
  90. 28 0
      core/src/main/java/com/haochuan/core/util/JsUtil.java
  91. 9 0
      core/src/main/java/com/haochuan/core/util/Judge.java
  92. 348 0
      core/src/main/java/com/haochuan/core/util/MacUtil.java
  93. 28 0
      core/src/main/java/com/haochuan/core/util/MathUtil.java
  94. 45 0
      core/src/main/java/com/haochuan/core/util/Md5Util.java
  95. 10 0
      core/src/main/java/com/haochuan/core/util/MediaStatusCode.java
  96. 9 0
      core/src/main/java/com/haochuan/core/util/MessageCode.java
  97. 31 0
      core/src/main/java/com/haochuan/core/util/RegexUtil.java
  98. 23 0
      core/src/main/java/com/haochuan/core/util/ScreenSnap.java
  99. 321 0
      core/src/main/java/com/haochuan/core/util/ToolsUtil.java
  100. 0 0
      core/src/main/res/drawable-xxhdpi/icon_close.png

+ 13 - 0
.gitignore

@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild

+ 4 - 0
.idea/encodings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" addBOMForNewFiles="with NO BOM" />
+</project>

+ 21 - 0
.idea/gradle.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+            <option value="$PROJECT_DIR$/core" />
+            <option value="$PROJECT_DIR$/systemvideo" />
+            <option value="$PROJECT_DIR$/vrlib" />
+          </set>
+        </option>
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 14 - 0
.idea/misc.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CMakeSettings">
+    <configurations>
+      <configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
+    </configurations>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 12 - 0
.idea/runConfigurations.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 1 - 0
app/.gitignore

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

+ 75 - 0
app/build.gradle

@@ -0,0 +1,75 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion versions.compileSdk
+    defaultConfig {
+        applicationId "com.hc.jsyd.vr"
+        minSdkVersion versions.minSdk
+        targetSdkVersion versions.targetSdk
+        versionCode versions.versionCode
+        versionName versions.versionName
+        testInstrumentationRunner lib.test.test_runner
+        ndk {
+            //APP的build.gradle设置支持的SO库架构
+            abiFilters 'armeabi', 'armeabi-v7a', 'x86'
+        }
+
+        buildConfigField "int", "player_type", "1"; //播放器类型;1,原生;2,未来;3,GsyPlayer
+    }
+
+
+    compileOptions {
+        sourceCompatibility versions.Java
+        targetCompatibility versions.Java
+    }
+
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+
+    buildTypes {
+        release {
+            buildConfigField "Boolean", "isDebug", "false"     //app全局是否调试,发布版本不要开启
+        }
+
+        debug {
+            buildConfigField "Boolean", "isDebug", "true"     //app全局是否调试,调试版本开启
+        }
+    }
+
+    android.applicationVariants.all{ variant ->
+        variant.outputs.all { output ->
+            def outputFile = output.outputFile
+            if (outputFile != null && outputFile.name.endsWith('.apk')) {
+                def fileName
+                if (variant.buildType.name == "release") {
+                    // 输出apk名称为
+                    fileName = "hc_vr_jsyd_${variant.versionName}_release_${releaseTime()}.apk"
+                } else {
+                    fileName = "hc_vr_jsyd_${variant.versionName}_debug_${releaseTime()}.apk"
+                }
+                outputFileName = fileName
+            }
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
+    implementation lib.support.v7
+    implementation lib.support.constraint_layout
+    implementation lib.support.appcompat
+    implementation lib.support.constraintlayout
+    testImplementation lib.test.junit
+    androidTestImplementation lib.test.runner
+    androidTestImplementation lib.test.espresso
+    implementation project(path: ':core')
+    implementation project(path: ':systemvideo')
+    implementation project(path: ':vrlib')
+}
+
+def releaseTime() {
+    return new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC"))
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# 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

+ 27 - 0
app/src/androidTest/java/com/haochuan/hciptvbasic/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package com.haochuan.hciptvbasic;
+
+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.haochuan.hciptvbasic", appContext.getPackageName());
+    }
+}

+ 57 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,57 @@
+<?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.haochuan.hciptvbasic">
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <!-- 未来电视所需权限 -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.GET_TASKS" />
+    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.REORDER_TASKS" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+        tools:ignore="ProtectedPermissions" />
+
+    <uses-permission android:name="android.permission.REAL_GET_TASKS"
+        tools:ignore="ProtectedPermissions" />
+
+    <application
+        android:name=".BaseApp"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme"
+        tools:ignore="GoogleAppIndexingWarning">
+        <activity
+            android:screenOrientation="landscape"
+            android:exported="true"
+            android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <uses-library
+            android:name="android.test.runner"
+            android:required="false" />
+    </application>
+
+</manifest>

+ 54 - 0
app/src/main/java/com/haochuan/hciptvbasic/BaseApp.java

@@ -0,0 +1,54 @@
+package com.haochuan.hciptvbasic;
+
+import android.app.Application;
+import android.content.Context;
+
+import com.yanzhenjie.nohttp.InitializationConfig;
+import com.yanzhenjie.nohttp.Logger;
+import com.yanzhenjie.nohttp.NoHttp;
+import com.yanzhenjie.nohttp.URLConnectionNetworkExecutor;
+import com.yanzhenjie.nohttp.cache.DBCacheStore;
+import com.yanzhenjie.nohttp.cookie.DBCookieStore;
+
+
+public class BaseApp extends Application {
+    public static Context mContext;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mContext = getApplicationContext();
+        initHttp(this);
+    }
+
+    //初始化网络请求库配置
+    public void initHttp(Context context) {
+        InitializationConfig config = InitializationConfig.newBuilder(context)
+                // 全局连接服务器超时时间,单位毫秒,默认10s。
+                .connectionTimeout(60 * 1000)
+                // 全局等待服务器响应超时时间,单位毫秒,默认10s。
+                .readTimeout(60 * 1000)
+                // 配置缓存,默认保存数据库DBCacheStore,保存到SD卡使用DiskCacheStore。
+                .cacheStore(
+                        // 如果不使用缓存,setEnable(false)禁用。
+                        new DBCacheStore(context).setEnable(true)
+                )
+                // 配置Cookie,默认保存数据库DBCookieStore,开发者可以自己实现CookieStore接口。
+                .cookieStore(
+                        // 如果不维护cookie,setEnable(false)禁用。
+                        new DBCookieStore(context).setEnable(true)
+                )
+                // 配置网络层,默认URLConnectionNetworkExecutor,如果想用OkHttp:OkHttpNetworkExecutor。
+                .networkExecutor(new URLConnectionNetworkExecutor())
+                // 全局重试次数,配置后每个请求失败都会重试x次。
+                .retry(2)
+                .build();
+        NoHttp.initialize(config);
+        Logger.setDebug(true);
+        Logger.setTag("NoHttp");
+    }
+
+    public static Context getAppContext() {
+        return mContext;
+    }
+}

+ 412 - 0
app/src/main/java/com/haochuan/hciptvbasic/BaseWebActivity.java

@@ -0,0 +1,412 @@
+package com.haochuan.hciptvbasic;
+
+/*
+* 这是主页面的基类,负责webview的初始化工作
+*
+* */
+
+
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
+
+import com.asha.vrlib.MDVRLibrary;
+import com.haochuan.core.BaseMediaPlayer;
+import com.haochuan.core.IVideoPlayer;
+import com.haochuan.core.Logger;
+import com.haochuan.core.http.RequestServer;
+import com.haochuan.core.http.ResponseListener;
+import com.haochuan.core.http.bean.ApkSettingBean;
+import com.haochuan.core.http.bean.ResponseBean;
+import com.haochuan.core.util.ELS;
+import com.haochuan.hciptvbasic.util.VRUtil;
+import com.haochuan.hciptvbasic.webview.PayToJS;
+import com.haochuan.hciptvbasic.webview.PlayerToJS;
+import com.haochuan.hciptvbasic.webview.HCWebChromeClient;
+import com.haochuan.hciptvbasic.webview.UtilToJS;
+import com.haochuan.systemvideo.SystemVideoPlayer;
+
+import java.util.List;
+
+public abstract class BaseWebActivity extends AppCompatActivity {
+    private WebView webView;                                    //整个应用唯一的webview
+    private PlayerToJS playerToJS;                              //PlayerToJS类实例
+    private PayToJS payToJS;                                    // PayToJS类实例
+    private UtilToJS utilToJS;                                  //ToolToJS实例
+    String playerToJSName = PlayerToJS.class.getSimpleName();    //playerToJS类名
+    String payToJSName = PayToJS.class.getSimpleName();         //payToJS类名
+    String toolToJSName = UtilToJS.class.getSimpleName();       //toolToJS类名
+
+    //播放器
+    private BaseMediaPlayer mHCPlayer = null;
+    protected ELS els;
+
+    //vr模块
+    MDVRLibrary mVRLibrary;
+
+    /**-----------------------虚函数-----------------------*/
+
+    //获取启动页web地址
+    protected abstract String getIndexURL();
+
+    /*--------------------生命周期---------------------*/
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        els = ELS.getInstance(this);
+        String uid = "";
+        //访问接口确定是否需要上传日志
+        if (!TextUtils.isEmpty(uid)) {
+            RequestServer.getInstance().getApkSetting(uid, new ResponseListener<ApkSettingBean>() {
+                @Override
+                public void onSuccess(ApkSettingBean response) {
+                    Logger.d("getApkSetting:" + response.toString());
+                    List<ApkSettingBean.DataBean> dataList = response.getData();
+                    for (ApkSettingBean.DataBean data : dataList) {
+                        //遍历寻找对应配置
+                        if ("open_apk_log_status".equals(data.getSetting_name())) {
+                            if ("1".equals(data.getSetting_value())) {
+                                Logger.setLogNeedWriteToFile(true);
+                                els.saveBoolData(ELS.LAST_LOG_SWITCH, true);
+                            } else {
+                                els.saveBoolData(ELS.LAST_LOG_SWITCH, false);
+                            }
+                        }
+                    }
+                }
+
+                @Override
+                public void onFailure(String code, String message) {
+                    els.saveBoolData(ELS.LAST_LOG_SWITCH, false);
+                }
+            });
+        }
+
+        //每次启动应用时,检查一下上一次使用是否要上传日志,
+        // 因为在应用关闭时上传文件可能导致内存内泄漏或上传失败,所以放在应用启动时上传
+        if (els.getBoolData(ELS.LAST_LOG_SWITCH)) {
+            String lasLogFileName = els.getStringData(ELS.LOG_FILE_NAME);
+            Logger.d("uploadLogFile lasLogFileName:" + lasLogFileName);
+            if (!TextUtils.isEmpty(lasLogFileName)) {
+                RequestServer.getInstance().uploadLogFile(lasLogFileName,
+                        new ResponseListener<ResponseBean>() {
+                            @Override
+                            public void onSuccess(ResponseBean response) {
+                                Logger.d("uploadLogFile:" + response.toString());
+                            }
+
+                            @Override
+                            public void onFailure(String code, String message) {
+                            }
+                        });
+            }
+        }
+
+        //初始化日志
+        Logger.init(this,getWebView());
+
+
+
+        //初始化播放器
+        initPlayer();
+
+        //初始化vr模块
+        mVRLibrary = new VRUtil().createVRLibrary(this,mHCPlayer);
+
+        webView = new WebView(this);
+        webView.setBackgroundColor(ContextCompat.getColor(this, android.R.color.transparent));
+        setContentView(webView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+        initWebSetting(webView);
+
+        runH5();
+    }
+
+    private void runH5(){
+        webView.loadUrl(getIndexURL());
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        //关闭软键盘
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (webView != null) {
+            webView.onResume();
+        }
+        if(mHCPlayer !=null){
+            mHCPlayer.resume();
+        }
+
+        if(mVRLibrary != null){
+            mVRLibrary.onResume(this);
+        }
+
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (webView != null) {
+            webView.onPause();
+        }
+        if(mHCPlayer !=null){
+            mHCPlayer.pause();
+        }
+
+        if(mVRLibrary != null){
+            mVRLibrary.onPause(this);
+        }
+    }
+
+    @Override
+    protected void onStop(){
+        super.onStop();
+        boolean isForeground = isRunningForeground(this);
+        if (!isForeground) {
+            Handler handler = new Handler(getMainLooper());
+            handler.postDelayed(this::AppExit, 500);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (webView != null) {
+            webView.removeJavascriptInterface(playerToJSName);
+            webView.clearCache(true);
+            webView.clearFormData();
+            webView.clearMatches();
+            webView.clearSslPreferences();
+            webView.clearDisappearingChildren();
+            webView.clearHistory();
+            webView.clearAnimation();
+            webView.loadUrl("about:blank");
+            webView.removeAllViews();
+
+            webView = null;
+        }
+        if(mHCPlayer !=null){
+            mHCPlayer.release();
+        }
+        if(mVRLibrary != null){
+            mVRLibrary.onDestroy();
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mVRLibrary.onOrientationChanged(this);
+    }
+
+    /**
+     * webView对象获取"返回"按键事件
+     */
+    @Override
+    public void onBackPressed() {
+        //如果是除未来其他版本,请用这段代码
+        utilToJS.onBackPressed();
+    }
+
+    /*--------------------------初始化函数---------------------------*/
+    /*
+    * 初始化播放器
+    * */
+    private void initPlayer(){
+        Logger.d("BaseWebActivity,initPlayer()");
+        switch (BuildConfig.player_type){
+            case 1:
+                mHCPlayer = new SystemVideoPlayer(this);
+                break;
+            case 2:
+                break;
+            case 3:
+                break;
+            default:
+                break;
+        }
+        mHCPlayer.setVideoPlayerListener(new IVideoPlayer() {
+            @Override
+            public void onPreparing() {
+                playerToJS.onPlayerPreparing();
+            }
+
+            @Override
+            public void onPlaying() {
+                playerToJS.onPlayerPlaying();
+            }
+
+            @Override
+            public void onResume() {
+                playerToJS.onPlayerResume();
+            }
+
+            @Override
+            public void onPause() {
+                playerToJS.onPlayerPause();
+            }
+
+            @Override
+            public void onDestroy() {
+            }
+
+            @Override
+            public void onPlayingBuffering() {
+                playerToJS.onPlayingBuffer();
+            }
+
+            @Override
+            public void onCompletion() {
+                playerToJS.onPlayerComplete();
+            }
+
+            @Override
+            public void onError(int what, int extra) {
+                playerToJS.onPlayerError(what,extra);
+                //取消提示框显示,改由前端页面实现
+               /* if(BuildConfig.player_type == 2){
+                    CNTVPlayerErrorAlert();
+                }*/
+            }
+
+        });
+    }
+
+
+
+
+    @SuppressLint({"SetJavaScriptEnabled", "JavascriptInterface", "AddJavascriptInterface"})
+    private void initWebSetting(WebView webView) {
+        Logger.d("BaseWebActivity,initWebSetting()");
+        try{
+            WebSettings webSettings = webView.getSettings();
+            // 由H5端适配屏幕,具体参考文档:https://developer.chrome.com/multidevice/webview/pixelperfect
+            webSettings.setUseWideViewPort(true);
+            webSettings.setLoadWithOverviewMode(true);
+            webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
+            // 设置JS交互
+            webSettings.setJavaScriptEnabled(true);
+
+            HCWebChromeClient hcWebChromeClient = new HCWebChromeClient();
+            setPlayerToJS();
+            setPayToJS();
+            setToolToJS();
+
+            webView.addJavascriptInterface(playerToJS,playerToJSName);
+            webView.addJavascriptInterface(payToJS,payToJSName);
+            webView.addJavascriptInterface(utilToJS,toolToJSName);
+            // 设置WebClient
+            webView.setWebViewClient(new WebViewClient());
+            webView.setWebChromeClient(hcWebChromeClient);
+            // 设置是否开启web内容调试,具体调试方式查看:https://developers.google.com/web/tools/chrome-devtools/remote-debugging/?utm_source=dcc&utm_medium=redirect&utm_campaign=2016q3
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+                WebView.setWebContentsDebuggingEnabled(BuildConfig.isDebug);
+            }
+
+            webView.requestFocus();
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    private void setPlayerToJS(){
+        playerToJS = new PlayerToJS(this,webView,mHCPlayer);
+        //添加VrRotate接口
+        playerToJS.setVrRotate((int x, int y)->{
+            mVRLibrary.rotateScreen(Float.parseFloat(String.valueOf(x)),Float.parseFloat(String.valueOf(y)));
+        });
+    }
+    private void setPayToJS(){ payToJS = new PayToJS(this,webView); }
+    private void setToolToJS(){ utilToJS = new UtilToJS(this,webView); }
+
+    /*-----------------------------------功能函数 start----------------------------------*/
+
+    /**
+     * 判断应用是否处于前台
+     *
+     * @return <code>true</code>为前台,反之为后台
+     */
+    public boolean isRunningForeground(Context context) {
+        Logger.d("BaseWebActivity,isRunningForeground()");
+        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        if (activityManager == null) return false;
+        List<ActivityManager.RunningAppProcessInfo> appProcessInfos = activityManager.getRunningAppProcesses();
+        // 枚举进程
+        for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessInfos) {
+            if (appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                if (appProcessInfo.processName.equals(context.getApplicationInfo().processName)) {
+                    Logger.d("应用处于前台状态");
+                    return true;
+                }
+            }
+        }
+        Logger.d("应用退到后台");
+        return false;
+    }
+
+    /*
+     * 退出应用
+     * */
+    public void AppExit() {
+        Logger.d("BaseWebActivity,AppExit()");
+        android.os.Process.killProcess(android.os.Process.myPid());   //获取PID
+        System.exit(0);
+    }
+
+    /*------------------------子类获取实例接口------------------------------*/
+
+    /**
+     * 获取当前WebView对象
+     */
+    protected WebView getWebView(){return this.webView;}
+
+    /*
+     * 获取播放器实例
+     * */
+    protected BaseMediaPlayer getMediaPlayer(){return this.mHCPlayer;}
+
+
+
+    /*
+    * 获取PlayerToJs实例
+    * */
+    protected PlayerToJS getPlayerToJS(){return playerToJS;}
+
+    /*
+     * 获取PayToJS实例
+     * */
+    protected PayToJS getPayToJS(){return payToJS;}
+
+
+    /*
+     * 获取ToolToJS实例
+     * */
+    protected UtilToJS getUtilToJS(){return utilToJS;}
+
+
+    public interface VrRotate{
+        void rotate(int x, int y);
+    }
+}

+ 51 - 0
app/src/main/java/com/haochuan/hciptvbasic/MainActivity.java

@@ -0,0 +1,51 @@
+package com.haochuan.hciptvbasic;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.haochuan.core.Logger;
+import com.haochuan.core.http.DownloadServer;
+import com.haochuan.core.http.RequestServer;
+
+
+public class MainActivity extends BaseWebActivity  {
+    private String mBasicUrl = "http://221.130.29.211:8095/ivod/index.html";    //入口地址
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+    }
+
+    /**-------------------BaseWebActivity重载函数 start--------------------------*/
+
+    /*
+    * 获取WebView入口地址
+    * */
+    @Override
+    protected String getIndexURL() {
+        Logger.d("getIndexURL(),mBasicUrl:"+mBasicUrl);
+        return mBasicUrl;
+    }
+
+
+    //因为重写了onBackPress方法,所以在onDestroy中复写方法不会回调,相应操作都放在该方法中进行
+    @Override
+    public void AppExit() {
+        //关掉网络请求队列
+        RequestServer.getInstance().stop();
+        DownloadServer.getInstance().stop();
+        super.AppExit();
+    }
+
+
+}

+ 149 - 0
app/src/main/java/com/haochuan/hciptvbasic/SystemPlayerWrapper.java

@@ -0,0 +1,149 @@
+package com.haochuan.hciptvbasic;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.view.Surface;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.VideoView;
+
+import androidx.annotation.RawRes;
+
+import com.haochuan.core.Logger;
+
+
+
+/**
+ * Created by hzqiujiadi on 16/4/5.
+ * hzqiujiadi ashqalcn@gmail.com
+ *
+ * http://developer.android.com/intl/zh-cn/reference/android/media/MediaPlayer.html
+ * status
+ */
+public class SystemPlayerWrapper implements MediaPlayer.OnPreparedListener {
+    protected VideoView mPlayer;
+    private MediaPlayer.OnPreparedListener mPreparedListener;
+    private static final int STATUS_IDLE = 0;
+    private static final int STATUS_PREPARING = 1;
+    private static final int STATUS_PREPARED = 2;
+    private static final int STATUS_STARTED = 3;
+    private static final int STATUS_PAUSED = 4;
+    private static final int STATUS_STOPPED = 5;
+    private int mStatus = STATUS_IDLE;
+
+    private Surface surface;
+
+    public void init(Context context){
+        mStatus = STATUS_IDLE;
+        mPlayer = new VideoView(context);
+        mPlayer.setOnPreparedListener(this);
+        mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
+            @Override
+            public boolean onInfo(MediaPlayer mp, int what, int extra) {
+                return false;
+            }
+        });
+
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        mPlayer.setLayoutParams(params);
+
+
+        if (mPlayer.getParent() == null) {
+            Activity activity = (Activity)context;
+            ViewGroup viewGroup = (ViewGroup)activity.getWindow().getDecorView();
+            viewGroup.addView(mPlayer, 0);
+        }
+    }
+
+
+
+    public void setSurface(Surface surface){
+        this.surface = surface;
+    }
+
+
+    public void openRemoteFile(String path){
+        try {
+            mPlayer.setVideoPath(path);
+            mPlayer.start();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void openRawFile(Context context, @RawRes int videoId) {
+        String rawPath = "android.resource://" + context.getPackageName() + "/" + videoId;
+        openRemoteFile(rawPath);
+    }
+
+
+    public VideoView getPlayer() {
+        return mPlayer;
+    }
+
+
+    public void stop(){
+        if (mPlayer == null) return;
+        if (mStatus == STATUS_STARTED || mStatus ==  STATUS_PAUSED){
+            mPlayer.stopPlayback();
+            mStatus = STATUS_STOPPED;
+        }
+    }
+
+    public void pause(){
+        if (mPlayer == null) return;
+        if (mPlayer.isPlaying() && mStatus == STATUS_STARTED) {
+            mPlayer.pause();
+            mStatus = STATUS_PAUSED;
+        }
+    }
+
+    private void start(){
+        if (mPlayer == null) return;
+        if (mStatus == STATUS_PREPARED || mStatus == STATUS_PAUSED){
+            mPlayer.start();
+            mStatus = STATUS_STARTED;
+        }
+
+    }
+
+    public void setPreparedListener(MediaPlayer.OnPreparedListener mPreparedListener) {
+        this.mPreparedListener = mPreparedListener;
+    }
+
+
+    public void resume() {
+        start();
+    }
+
+    public void destroy() {
+        stop();
+        if (mPlayer != null) {
+            mPlayer.stopPlayback();
+        }
+        mPlayer = null;
+    }
+
+    @Override
+    public void onPrepared(MediaPlayer mp) {
+        mStatus = STATUS_PREPARED;
+        if(this.surface != null){
+            Logger.d("准备好,设置surface");
+            mp.setSurface(this.surface);
+        }
+        if (mPreparedListener != null) mPreparedListener.onPrepared(mp);
+    }
+
+    public void seek(boolean isBack){
+        final int currentPosition = mPlayer.getCurrentPosition();
+        if(isBack){
+            Logger.d("后退");
+            mPlayer.seekTo(currentPosition - 10000 >=0?currentPosition-10000:0);
+        }else{
+            Logger.d("前进");
+            mPlayer.seekTo(currentPosition + 10000 < mPlayer.getDuration()?mPlayer.getDuration() -1000:currentPosition + 10000);
+        }
+    }
+}

+ 24 - 0
app/src/main/java/com/haochuan/hciptvbasic/util/CustomProjectionFactory.java

@@ -0,0 +1,24 @@
+package com.haochuan.hciptvbasic.util;
+
+import com.asha.vrlib.common.MDDirection;
+import com.asha.vrlib.strategy.projection.AbsProjectionStrategy;
+import com.asha.vrlib.strategy.projection.IMDProjectionFactory;
+import com.asha.vrlib.strategy.projection.MultiFishEyeProjection;
+
+/**
+ * Created by hzqiujiadi on 16/8/20.
+ * hzqiujiadi ashqalcn@gmail.com
+ */
+public class CustomProjectionFactory implements IMDProjectionFactory {
+
+    public static final int CUSTOM_PROJECTION_FISH_EYE_RADIUS_VERTICAL = 9611;
+
+    @Override
+    public AbsProjectionStrategy createStrategy(int mode) {
+        switch (mode){
+            case CUSTOM_PROJECTION_FISH_EYE_RADIUS_VERTICAL:
+                return new MultiFishEyeProjection(0.745f, MDDirection.VERTICAL);
+            default:return null;
+        }
+    }
+}

+ 67 - 0
app/src/main/java/com/haochuan/hciptvbasic/util/VRUtil.java

@@ -0,0 +1,67 @@
+package com.haochuan.hciptvbasic.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.Log;
+import android.view.Surface;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import com.asha.vrlib.MD360Director;
+import com.asha.vrlib.MD360DirectorFactory;
+import com.asha.vrlib.MDVRLibrary;
+import com.asha.vrlib.model.BarrelDistortionConfig;
+import com.asha.vrlib.model.MDPinchConfig;
+import com.haochuan.core.BaseMediaPlayer;
+import com.haochuan.core.Logger;
+
+/**
+ * @author: xulin
+ * @projectName:HcIPTVBasic
+ * @createTime:2020/6/5 16:23
+ */
+public class VRUtil {
+    public MDVRLibrary createVRLibrary(Context context, BaseMediaPlayer mMediaPlayerWrapper) {
+        //初始化GLSurfaceView,并将它添加到view树中
+        GLSurfaceView glSurfaceView = new GLSurfaceView(context);
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        glSurfaceView.setLayoutParams(params);
+
+        if (glSurfaceView.getParent() == null) {
+            Activity activity = (Activity)context;
+            ViewGroup viewGroup = (ViewGroup)activity.getWindow().getDecorView();
+            viewGroup.addView(glSurfaceView, 0);
+        }
+        return MDVRLibrary.with(context)
+                .displayMode(MDVRLibrary.DISPLAY_MODE_NORMAL)
+                .interactiveMode(MDVRLibrary.INTERACTIVE_MODE_KEYBOARD)
+                .asVideo(new MDVRLibrary.IOnSurfaceReadyCallback() {
+                    @Override
+                    public void onSurfaceReady(Surface surface) {
+                        Logger.d("onSurfaceReady");
+                        mMediaPlayerWrapper.setSurface(surface);
+                    }
+                })
+                .ifNotSupport(new MDVRLibrary.INotSupportCallback() {
+                    @Override
+                    public void onNotSupport(int mode) {
+                        String tip = mode == MDVRLibrary.INTERACTIVE_MODE_MOTION
+                                ? "onNotSupport:MOTION" : "onNotSupport:" + String.valueOf(mode);
+                        Logger.d("onNotSupport:MOTION,onNotSupport:"+ String.valueOf(mode));
+                    }
+                })
+                .pinchConfig(new MDPinchConfig().setMin(1.0f).setMax(8.0f).setDefaultValue(0.1f))
+                .pinchEnabled(true)
+                .directorFactory(new MD360DirectorFactory() {
+                    @Override
+                    public MD360Director createDirector(int index) {
+                        return MD360Director.builder().setPitch(90).build();
+                    }
+                })
+                .projectionFactory(new CustomProjectionFactory())
+                .barrelDistortionConfig(new BarrelDistortionConfig().setDefaultEnabled(false).setScale(0.95f))
+                .build(glSurfaceView);
+    }
+}

+ 26 - 0
app/src/main/java/com/haochuan/hciptvbasic/webview/HCWebChromeClient.java

@@ -0,0 +1,26 @@
+package com.haochuan.hciptvbasic.webview;
+
+import android.util.Log;
+import android.webkit.ConsoleMessage;
+import android.webkit.WebChromeClient;
+
+
+public class HCWebChromeClient extends WebChromeClient {
+
+    private String errorMsg;
+    private String TAG = "HCWebApp";
+
+    @Override
+    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+        errorMsg = consoleMessage.message() + " -- From line "
+                + consoleMessage.lineNumber() + " of "
+                + consoleMessage.sourceId();
+        Log.d(TAG,  errorMsg);
+        return true;
+    }
+
+    public String getErrorMsg(){
+        return errorMsg;
+    }
+
+}

+ 109 - 0
app/src/main/java/com/haochuan/hciptvbasic/webview/PayToJS.java

@@ -0,0 +1,109 @@
+package com.haochuan.hciptvbasic.webview;
+
+import android.content.Context;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+
+import com.haochuan.core.Logger;
+import com.haochuan.core.util.JsUtil;
+
+public class PayToJS {
+    private Context context;                        //MainActivity 句柄
+    private WebView webView;
+
+    /**
+     * 将SDK初始化结果传递给js
+     * 参数:0,成功;-1,失败
+     */
+    private String JS_EVENT_SDK_INIT_RESULT = "javascript:onSDKInitResult(%s)";
+
+    /**
+     * 将SDK鉴权结果传递给js
+     * 参数:json字符串
+     */
+    private String JS_EVENT_AUTH_RESULT = "javascript:onAuthResult('%s')";
+
+
+    /*
+     * 将计费结果传递给js
+     * 参数:json字符串
+     * */
+    private String JS_EVENT_PAY_RESULT = "javascript:onPayResult('%s')";
+
+
+    public PayToJS(Context context, WebView webView) {
+        this.context = context;
+        this.webView = webView;
+    }
+
+
+    /*---------------------------功能性事件--------------------------------*/
+
+    /*
+    * SDK初始化
+    * 已去除
+    * */
+    @JavascriptInterface
+    public void sdkInit(String paramsJson){
+        Logger.d("PayToJS,sdkInit(),paramsJson:" + paramsJson);
+        //在这里添加SDK初始化逻辑
+    }
+
+    /*
+    * 鉴权
+    * */
+    @JavascriptInterface
+    public void auth(String paramsJson){
+        Logger.d("PayToJS,auth(),paramsJson:" + paramsJson);
+        //在这里添加鉴权逻辑
+    }
+
+
+    /*
+    * 计费
+    * */
+    @JavascriptInterface
+    public void pay(String paramsJson){
+        Logger.d("PayToJS,pay(),paramsJson:" + paramsJson);
+        //在这里添加支付逻辑
+    }
+
+    /*
+    * 获取用户ID
+    * */
+    @JavascriptInterface
+    public String getUserId(){
+        //这这里添加获取用户ID逻辑
+        Logger.d("PayToJS,getUserId()");
+        return "";
+    }
+
+    /*---------------------------事件函数--------------------------------*/
+
+    /**
+     * 将SDK初始化结果传递给js
+     * 参数:0,成功;-1,失败
+     */
+    public void onSDKInitResult(int code){
+        Logger.d(String.format("PayToJS,onSDKInitResult(%s)",code));
+        JsUtil.evaluateJavascript(context,webView, String.format(JS_EVENT_SDK_INIT_RESULT,code));
+    }
+
+    /**
+     * 将SDK鉴权结果传递给js
+     * 参数:json字符串
+     */
+    public void onAuthResult(String paramJson){
+        Logger.d("PayToJS,onAuthResult(),paramJson:" + paramJson);
+        JsUtil.evaluateJavascript(context,webView, String.format(JS_EVENT_AUTH_RESULT,paramJson));
+    }
+
+    /*
+     * 将计费结果传递给js
+     * 参数:json字符串
+     * */
+    public void onPayResult(String paramJson){
+        Logger.d("PayToJS,onPayResult(),paramJson:" + paramJson);
+        JsUtil.evaluateJavascript(context,webView, String.format(JS_EVENT_PAY_RESULT,paramJson));
+    }
+}

+ 573 - 0
app/src/main/java/com/haochuan/hciptvbasic/webview/PlayerToJS.java

@@ -0,0 +1,573 @@
+package com.haochuan.hciptvbasic.webview;
+
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.animation.LinearInterpolator;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+import android.widget.FrameLayout;
+
+import com.haochuan.core.BaseMediaPlayer;
+import com.haochuan.core.Logger;
+import com.haochuan.core.util.JSONUtil;
+import com.haochuan.core.util.JsUtil;
+import com.haochuan.core.util.MathUtil;
+import com.haochuan.core.util.MediaStatusCode;
+import com.haochuan.core.util.ScreenSnap;
+import com.haochuan.hciptvbasic.BaseWebActivity;
+
+import org.json.JSONObject;
+
+import static com.haochuan.core.util.MessageCode.EXCEPTION_ERROR;
+import static com.haochuan.core.util.MessageCode.PARAM_ERROR;
+import static com.haochuan.core.util.MessageCode.PLAYER_NO_INIT;
+import static com.haochuan.core.util.MessageCode.PLAYER_OBJ_NULL;
+import static com.haochuan.core.util.MessageCode.SUCCESS;
+
+
+public class PlayerToJS {
+    private Context context;                        //MainActivity 句柄
+    private BaseMediaPlayer baseMediaPlayer;        //播放器
+    private WebView webView;
+
+    //vr视角旋转结果
+    private BaseWebActivity.VrRotate vrRotateListener;
+
+
+    /*--------------------传给前端的播放器事件--------------------------*/
+    private String JS_EVENT_PLAYERROR="javascript:onPlayerError(%s,%s)";
+    private String JS_PLAYER_STATUS = "javascript:onPlayerStatus(%s)";
+
+
+    public PlayerToJS(Context context, WebView webView, BaseMediaPlayer mediaPlayer) {
+        this.context = context;
+        this.baseMediaPlayer = mediaPlayer;
+        this.webView = webView;
+    }
+
+    public void setVrRotate(BaseWebActivity.VrRotate rotateListener){
+        this.vrRotateListener = rotateListener;
+    }
+
+
+
+    /*---------------------------事件函数---------------------------*/
+    /*
+     * 播放器准备事件
+     * */
+    public void onPlayerPreparing(){
+        Logger.d("PlayerToJS,onPlayerPreparing()");
+        executePlayStatusEvent(MediaStatusCode.PREPARE);
+    }
+
+    /*
+     * 播放器开始播放事件
+     * */
+    public void onPlayerPlaying(){
+        Logger.d("PlayerToJS,onPlayerPlaying()");
+        executePlayStatusEvent(MediaStatusCode.PLAY);
+    }
+
+    /*
+     * 播放器继续播放事件
+     * */
+    public void onPlayerResume(){
+        Logger.d("PlayerToJS,onPlayerResume()");
+        executePlayStatusEvent(MediaStatusCode.PLAY);
+    }
+
+    /*
+     * 播放器暂停播放事件
+     * */
+    public void onPlayerPause(){
+        Logger.d("PlayerToJS,onPlayerPause()");
+        executePlayStatusEvent(MediaStatusCode.PAUSE);
+    }
+
+    /*
+     * 播放器缓冲事件
+     * */
+    public void onPlayingBuffer(){
+        Logger.d("PlayerToJS,onPlayerBuffer()");
+        executePlayStatusEvent(MediaStatusCode.BUFFER);
+    }
+
+    /*
+     * 播放器播放完毕事件
+     * */
+    public void onPlayerComplete(){
+        Logger.d("PlayerToJS,onPlayerComplete()");
+        executePlayStatusEvent(MediaStatusCode.COMPLETE);
+    }
+
+    /*
+     * 播放器播放错误事件
+     * */
+    public void onPlayerError(int what, int extra){
+        Logger.d(String.format("PlayerToJS,onPlayerError(%s,%s)",what,extra));
+        JsUtil.evaluateJavascript(context,webView,String.format(JS_EVENT_PLAYERROR,what,extra));
+    }
+
+    /*----------------------------------播放器功能函数----------------------------------------*/
+
+    /*
+    * 播放函数
+    *  @param url                 播放链接,type参数为1时必填
+    *  @param code                播放代码,,type参数为2时必填,该版本没用到
+    *  @param type                播放类型,改版本固定用url播放,1,url链接播放;2,传递code值播放;
+     * @param x                  播放器x坐标
+     * @param y                  播放器y坐标
+     * @param width              播放器宽度
+     * @param height             播放器高度
+     * @param seekTime           播放初始位置,单位 s
+    * */
+    @JavascriptInterface
+    public int play(String playParamJson){
+        Logger.d("PlayerToJS,play(),playParamJson:" + playParamJson);
+        try{
+            JSONObject playParam = new JSONObject(playParamJson);
+            String url = "";
+            String typeStr = JSONUtil.getString(playParam,"type","1");
+            int type = 1;
+            if(TextUtils.isEmpty(typeStr)){
+                type = 1;
+            }
+            if(MathUtil.isDigitsOnly(typeStr)){
+                type = Integer.parseInt(typeStr);
+            }else{
+                Logger.e(PARAM_ERROR,"调用play函数,type参数错误,type:" + type);
+            }
+            switch (type){
+                case 1:
+                    url = JSONUtil.getString(playParam,"url","");
+                    break;
+                case 2:
+                    //该版本没有code获取url的功能,暂缺,请根据实际情况添加
+                    break;
+                default:
+                    url = JSONUtil.getString(playParam,"url","");
+                    break;
+            }
+            if(TextUtils.isEmpty(url)){
+                Logger.e(PARAM_ERROR,"调用play函数,url为空,不能执行播放");
+                return PARAM_ERROR;
+            }
+            String seekTime = JSONUtil.getString(playParam,"seek_time","0");
+            String x = JSONUtil.getString(playParam,"x","0");
+            String y = JSONUtil.getString(playParam,"y","0");
+            String width = JSONUtil.getString(playParam,"width","1280");
+            String height = JSONUtil.getString(playParam,"height","720");
+            String examineId = JSONUtil.getString(playParam,"examine_id","");
+            String examineType = JSONUtil.getString(playParam,"examine_type","program");
+            return videoPlay(url,seekTime,x,y,width,height,examineId,examineType);
+        }catch (Exception e){
+            e.printStackTrace();
+            Logger.e(EXCEPTION_ERROR,"异常抛出:" + e.getMessage());
+            return EXCEPTION_ERROR;
+        }
+    }
+
+    /*
+    * 改变播放器尺寸
+    * 参数定义同play函数
+    * */
+    @JavascriptInterface
+    public int change(String changeParamJson){
+        Logger.d("PlayerToJS,change(),changeParamJson:" + changeParamJson);
+        try{
+            JSONObject changeParam = new JSONObject(changeParamJson);
+            String x = JSONUtil.getString(changeParam,"x","0");
+            String y = JSONUtil.getString(changeParam,"y","0");
+            String width = JSONUtil.getString(changeParam,"width","1280");
+            String height = JSONUtil.getString(changeParam,"height","720");
+            return videoChange(x,y,width,height);
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+    }
+
+
+    /*
+     * 暂停
+     * */
+    @JavascriptInterface
+    public int pause(){
+        Logger.d("PlayerToJS,pause()");
+        if(baseMediaPlayer == null){
+            Logger.e("播放器为空,不能暂停");
+            return PLAYER_OBJ_NULL;
+        }
+        try{
+            Activity activity = (Activity)context;
+            activity.runOnUiThread(()->baseMediaPlayer.pause());
+            return SUCCESS;
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+    }
+
+    /*
+     * 恢复
+     * */
+    @JavascriptInterface
+    public int resume(){
+        Logger.d("PlayerToJS,resume()");
+        if(baseMediaPlayer == null){
+            Logger.e("播放器为空,不能恢复");
+            return PLAYER_OBJ_NULL;
+        }
+        try{
+            Activity activity = (Activity)context;
+            activity.runOnUiThread(()->baseMediaPlayer.resume());
+            return SUCCESS;
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+    }
+
+    /*
+     * 快进到指定位置
+     * */
+    @JavascriptInterface
+    public int seek(String paramJson){
+        Logger.d("PlayerToJS,seek(),paramJson:" + paramJson);
+        if(baseMediaPlayer == null){
+            Logger.e("播放器为空,不能拖动");
+            return PLAYER_OBJ_NULL;
+        }
+        try{
+            JSONObject jsonObject = new JSONObject(paramJson);
+            int position = JSONUtil.getInt(jsonObject,"time",-1);
+            if(position == -1){
+                return PARAM_ERROR;
+            }
+            Activity activity = (Activity)context;
+            activity.runOnUiThread(()->baseMediaPlayer.seek(position));
+            return SUCCESS;
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+    }
+
+    /**
+     *
+     * @param paramJson
+     * @return
+     */
+    @JavascriptInterface
+    public int rotate(String paramJson){
+        Logger.d("PlayerToJS,rotation(),paramJson:" + paramJson);
+        if(baseMediaPlayer == null){
+            Logger.e("播放器为空,不能拖动");
+            return PLAYER_OBJ_NULL;
+        }
+        try{
+            JSONObject jsonObject = new JSONObject(paramJson);
+            int x = JSONUtil.getInt(jsonObject,"x",0);
+            int y = JSONUtil.getInt(jsonObject,"y",0);
+            vrRotateListener.rotate(x,y);
+            return SUCCESS;
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+    }
+
+    /*
+    * 退出播放
+    * */
+    @JavascriptInterface
+    public int stop(){
+        Logger.d("PlayerToJS,stop()");
+        if(baseMediaPlayer == null){
+            Logger.e("播放器为空,不能退出");
+            return PLAYER_OBJ_NULL;
+        }
+
+        try{
+            Activity activity = (Activity)context;
+            activity.runOnUiThread(this::destroyVideo);
+            return SUCCESS;
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+    }
+
+    /*
+    * 获取当前播放状态
+    * 状态说明:1,视频准备中;2,播放;3,暂停;4,缓冲;5,播放完成停止;
+    * */
+    @JavascriptInterface
+    public int getPlayerStatus(){
+        Logger.d("PlayerToJS,getPlayerStatus()");
+        if(baseMediaPlayer == null){
+            Logger.e("播放器为空,不能退出");
+            return PLAYER_OBJ_NULL;
+        }
+        return baseMediaPlayer.getCurrentStatus();
+    }
+
+    /*
+    * 获得视频时长
+    * */
+    @JavascriptInterface
+    public int getDuration(){
+        Logger.d("PlayerToJS,getDuration()");
+        if(baseMediaPlayer == null){
+            Logger.e("播放器为空,不能退出");
+            return PLAYER_OBJ_NULL;
+        }
+        return baseMediaPlayer.getDuration();
+    }
+
+    /*
+     * 获得当前播放时间
+     * */
+    @JavascriptInterface
+    public int getCurrentPlayTime(){
+        Logger.d("PlayerToJS,getCurrentPlayTime()");
+        if(baseMediaPlayer == null){
+            Logger.e("播放器为空,不能退出");
+            return PLAYER_OBJ_NULL;
+        }
+        return baseMediaPlayer.getCurrentPlayPosition();
+    }
+
+
+    /**-------------------------------------------功能函数-----------------------------------------------*/
+
+    /*
+    * 播放
+    * @param x                  播放器x坐标
+    * @param y                  播放器y坐标
+    * @param width              播放器宽度
+    * @param height             播放器高度
+    * @param seekTime           播放初始位置,单位 s
+    * */
+    private int videoPlay(String url,String seekTime, String x,String y,String width, String height,
+                          String examineId,String examineType){
+        Logger.d(String.format("PlayerToJS,videoPlay(%s,%s,%s,%s,%s,%s,%s,%s)",url,seekTime,x,y,
+                width,height,examineId,examineType));
+        try{
+            if(baseMediaPlayer == null){
+                Logger.e(PLAYER_OBJ_NULL,"播放器对象为空,不能播放");
+                return PLAYER_OBJ_NULL;
+            }
+           /* if(!RegexUtil.isUrl(url)){
+                Logger.e(PARAM_ERROR,"调用play函数,url格式不正确,不能执行播放,请检查;url:" + url);
+                return PARAM_ERROR;
+            }*/
+
+
+            if(MathUtil.isDigitsOnly(x) && MathUtil.isDigitsOnly(y) && MathUtil.isDigitsOnly(width) && MathUtil.isDigitsOnly(height) && MathUtil.isDigitsOnly(seekTime)){
+
+                //判断x,y是否小于零
+                int Dx = Integer.parseInt(x);
+                int Dy = Integer.parseInt(y);
+                x = (Dx < 0?"0":x);
+                y = (Dy < 0?"0":y);
+
+
+
+                int screenWidth = ScreenSnap.getScreenWidth(context);
+                int screenHeight = ScreenSnap.getScreenHeight(context);
+                int transformX = (int) (Float.parseFloat(x) * screenWidth / 1280);
+                int transformY = (int) (Float.parseFloat(y) * screenHeight / 720);
+                int transformWidth = (int) (Float.parseFloat(String.valueOf(width)) * screenWidth / 1280);
+                int transformHeight = (int) (Float.parseFloat(String.valueOf(height)) * screenHeight / 720);
+
+                int transformSeekTime = Integer.parseInt(seekTime);
+                transformSeekTime = transformSeekTime < 0 ? 0 :transformSeekTime;
+                if(transformSeekTime < 0){
+                    transformSeekTime = 0;
+                }else{
+                    transformSeekTime *= 1000;
+                }
+
+                final int realSeekTime = transformSeekTime;
+
+                Logger.d(String.format("调用播放函数。转换坐标(%s, %s),宽高(%s, %s)", transformX, transformY, transformWidth, transformHeight));
+
+                Activity activity = (Activity)context;
+                activity.runOnUiThread(()->{
+                    initVideoParamsIfNoInit(baseMediaPlayer,transformX, transformY, transformWidth, transformHeight);
+                    baseMediaPlayer.play(url,examineId,examineType);
+                    baseMediaPlayer.setStartTime(realSeekTime);
+                    webView.requestFocus();
+                });
+                return SUCCESS;
+            }else{
+                Logger.e(PARAM_ERROR,String.format("请正确传递play函数参数:x:%s;y:%s;width:%s;height:%s;seekTime:%s",
+                        x,y,width,height,seekTime));
+                return PARAM_ERROR;
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+
+    }
+
+
+    /*
+     * 在视频未初始化的情况下,调用该函数初始化
+     * @param x                  播放器x坐标
+     * @param y                  播放器y坐标
+     * @param width              播放器宽度
+     * @param height             播放器高度
+     * */
+    private void initVideoParamsIfNoInit(BaseMediaPlayer baseMediaPlayer,int x, int y, int width, int height){
+        Logger.d(String.format("PlayerToJS,initVideoParamsIfNoInit(%s,%s,%s,%s)",x,y,
+                width,height));
+        if(baseMediaPlayer == null){
+            Logger.w("播放器为空,不能执行initVideoParamsIfNoInit函数");
+            return;
+        }
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        params.width = width;
+        params.height = height;
+        baseMediaPlayer.setLayoutParams(params);
+
+        baseMediaPlayer.setX(x);
+        baseMediaPlayer.setY(y);
+
+        if (baseMediaPlayer.getParent() == null) {
+            Activity activity = (Activity)context;
+            ViewGroup viewGroup = (ViewGroup)activity.getWindow().getDecorView();
+            viewGroup.addView(baseMediaPlayer, 1);
+        }
+    }
+
+    /*
+     * @param x                  待改变播放器x坐标
+     * @param y                  待改变播放器y坐标
+     * @param width              待改变播放器宽度
+     * @param height             待改变播放器高度
+    * */
+    private int videoChange(String x,String y,String width, String height){
+        Logger.d(String.format("PlayerToJS,videoChange(%s,%s,%s,%s)",x,y,
+                width,height));
+        if(baseMediaPlayer == null){
+            Logger.e(PLAYER_OBJ_NULL,"baseMediaPlayer is null,不能调用play函数");
+            return PLAYER_OBJ_NULL;
+        }
+
+        if(baseMediaPlayer.getParent() == null){
+            Logger.w("当前播放器没有启动,请先调用play函数启动");
+            return PLAYER_NO_INIT;
+        }
+
+        if(MathUtil.isDigitsOnly(x) && MathUtil.isDigitsOnly(y) && MathUtil.isDigitsOnly(width) && MathUtil.isDigitsOnly(height)){
+            int screenWidth = ScreenSnap.getScreenWidth(context);
+            int screenHeight = ScreenSnap.getScreenHeight(context);
+            int toX = (int) (Float.parseFloat(x) * screenWidth / 1280);
+            int toY = (int) (Float.parseFloat(y) * screenHeight / 720);
+            int toWidth = (int) (Float.parseFloat(width) * screenWidth / 1280);
+            int toHeight = (int) (Float.parseFloat(height) * screenHeight / 720);
+
+            int fromX = (int)baseMediaPlayer.getX();
+            int fromY = (int)baseMediaPlayer.getY();
+            int fromWidth = baseMediaPlayer.getWidth();
+            int fromHeight = baseMediaPlayer.getHeight();
+            Activity activity = (Activity)context;
+            activity.runOnUiThread(()->{
+                animChanged(fromX,toX,fromY,toY,fromWidth,toWidth,fromHeight,toHeight);
+            });
+            return SUCCESS;
+        }else{
+            Logger.e(PARAM_ERROR,String.format("请正确传递change函数参数:x:%s;y:%s;width:%s;height:%s",
+                    x,y,width,height));
+            return PARAM_ERROR;
+        }
+    }
+
+    /*
+    * 以动画形式改变播放器尺寸
+    * */
+    private int animChanged(int fromX, int toX, int fromY, int toY, int fromWidth, int toWidth, int fromHeight, int toHeight) {
+        try{
+            Logger.d(String.format("PlayerToJS,animChanged(%s,%s,%s,%s,%s,%s,%s,%s)",fromX,toX,fromY,toY,
+                    fromWidth,toWidth,fromHeight,toHeight));
+            ValueAnimator animator = new ValueAnimator();
+            animator.setValues(
+                    PropertyValuesHolder.ofFloat("x", fromX, toX),
+                    PropertyValuesHolder.ofFloat("y", fromY, toY),
+                    PropertyValuesHolder.ofInt("width", fromWidth, toWidth),
+                    PropertyValuesHolder.ofInt("height", fromHeight, toHeight)
+            );
+            animator.setDuration(200);
+            animator.setInterpolator(new LinearInterpolator());
+            animator.addUpdateListener(valueAnimator -> {
+                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+                params.width = (int) valueAnimator.getAnimatedValue("width");
+                params.height = (int) valueAnimator.getAnimatedValue("height");
+                if(baseMediaPlayer != null){
+                    Logger.w("baseMediaPlayer is null,不能调用play函数");
+                }
+
+                baseMediaPlayer.setLayoutParams(params);
+
+                baseMediaPlayer.setX((Float) valueAnimator.getAnimatedValue("x"));
+                baseMediaPlayer.setY((Float) valueAnimator.getAnimatedValue("y"));
+            });
+            animator.start();
+            return SUCCESS;
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+    }
+
+
+    private void destroyVideo(){
+        try{
+            Logger.d("PlayerToJS,destroyVideo()");
+            if(baseMediaPlayer != null && baseMediaPlayer.getParent() != null){
+                Activity activity = (Activity)context;
+                activity.runOnUiThread(()->baseMediaPlayer.release());
+                ViewGroup viewGroup = (ViewGroup) activity.getWindow().getDecorView();
+                viewGroup.removeView(baseMediaPlayer);
+            }else{
+                Logger.e(PLAYER_OBJ_NULL,"播放器对象为null,不能调用destroyVideo函数");
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    private void executePlayStatusEvent(int status){
+        Logger.d(String.format("PlayerToJS,executePlayStatusEvent()",status));
+        JsUtil.evaluateJavascript(context,webView,String.format(JS_PLAYER_STATUS,status));
+    }
+
+    //模拟事件点击,解决play方法调用之后前端无法响应事件的问题,山东会出现这个问题,暂不清楚原因
+    public void pretendClick() {
+        if (webView == null) return;
+        long downTime = SystemClock.uptimeMillis();
+
+        //按下事件
+        MotionEvent downEvent = MotionEvent.obtain(downTime, downTime,
+                MotionEvent.ACTION_DOWN, 10, 10, 0);
+        downTime += 1000;
+
+        //松开事件
+        MotionEvent upEvent = MotionEvent.obtain(downTime, downTime,
+                MotionEvent.ACTION_UP, 10, 10, 0);
+
+        webView.onTouchEvent(downEvent);
+        webView.onTouchEvent(upEvent);
+        downEvent.recycle();
+        upEvent.recycle();
+    }
+}

+ 192 - 0
app/src/main/java/com/haochuan/hciptvbasic/webview/UtilToJS.java

@@ -0,0 +1,192 @@
+package com.haochuan.hciptvbasic.webview;
+
+import android.app.Activity;
+import android.content.Context;
+import android.text.TextUtils;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+
+
+import com.haochuan.core.Logger;
+import com.haochuan.core.util.HandlerUtil;
+import com.haochuan.hciptvbasic.BaseWebActivity;
+import com.haochuan.hciptvbasic.BuildConfig;
+import com.haochuan.core.util.JSONUtil;
+import com.haochuan.core.util.JsUtil;
+import com.haochuan.core.util.MacUtil;
+import com.haochuan.core.util.MathUtil;
+import com.haochuan.core.util.ToolsUtil;
+
+import org.json.JSONObject;
+
+
+import static com.haochuan.core.util.MessageCode.EXCEPTION_ERROR;
+import static com.haochuan.core.util.MessageCode.SUCCESS;
+
+
+public class UtilToJS {
+    private Context context;                        //MainActivity 句柄
+    private WebView webView;
+    private ToolsUtil toolsUtil;
+
+    //将遥控返回按键事件传递给前端
+    private String JS_EVENT_BACK = "javascript:onBackEvent()";
+
+    //将日志传递给js
+    private String JS_EVENT_LOG = "javascript:onLog('%s')";
+
+    //将response传递给js
+    private String JS_EVENT_RESPONSE ="javascript:onWebRequestResponse('%s','%s')";
+
+    //语音控制事件
+    public static final String JS_EVENT_PLAY_EVENT = "javascript:onVoicePlayEvent(%s)";
+
+
+    /**
+     * 兼容旧智慧平台接口用
+     * 前端调用apk http请求回调事件
+     * <br>
+     * 形参1:表示前端调用apk方法时传入的code,String字符串
+     * <br>
+     * 形参2:表示apk请求过程--(0:开始请求;1:请求成功;-1:请求失败;-2:发生异常;2:请求结束),String字符串
+     * <br>
+     * 形参3:表示apk请求过程中的结果,String字符串
+     */
+    public static final String JS_EVENT_HTTP_CONNECT = "javascript:onHttpConnect(\"%s\", \"%s\", \"%s\")";
+
+    public UtilToJS(Context context, WebView webView){
+        this.context = context;
+        this.webView = webView;
+        toolsUtil = new ToolsUtil();
+    }
+
+    /*------------------------------------功能性函数-----------------------------------------*/
+
+    /*
+     * 将log传递给前端
+     * */
+    public void logToJs(String log){
+        Logger.d("UtilToJS,logToJs(),log:" + log);
+        JsUtil.evaluateJavascript(context,webView,
+                String.format(JS_EVENT_LOG,log));
+    }
+
+    /*
+     * webView对象获取"返回"按键事件
+     * */
+    public void onBackPressed(){
+        Logger.d("UtilToJS,onBackPressed()");
+        JsUtil.evaluateJavascript(context,webView, JS_EVENT_BACK);
+    }
+
+    public void onVoiceEvent(String action){
+        Logger.d("UtilToJS,onVoiceEvent()");
+        JsUtil.evaluateJavascript(context,webView, String.format(JS_EVENT_PLAY_EVENT,action));
+    }
+
+
+    /*---------------------------------获取本地参数--------------------------*/
+
+    /*
+    * 获取intent启动参数
+    * */
+    @JavascriptInterface
+    public String getIntentJson(){
+        Logger.d("UtilToJS,getIntentJson()");
+        return toolsUtil.getIntentJson(context);
+    }
+
+    @JavascriptInterface
+    public String getLocalParamsByJson(){
+        try{
+            Logger.d("UtilToJS,getLocalParamsByJson()");
+            JSONObject localParamsJson = new JSONObject();
+            localParamsJson.put("version_code",BuildConfig.VERSION_CODE);
+            localParamsJson.put("version_name",BuildConfig.VERSION_NAME);
+            localParamsJson.put("mac",MacUtil.getMac(context));
+            String paramJsonStr = localParamsJson.toString();
+            Logger.d("UtilToJS,getLocalParamsByJson(),paramJsonStr:" + paramJsonStr);
+            return paramJsonStr;
+        }catch (Exception e){
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+
+
+    /*-----------------------------操作APK-------------------------------------*/
+
+    /*
+     * 退出app
+     * */
+    @JavascriptInterface
+    public void appExit(){
+        try{
+            HandlerUtil.runOnUiThread(() -> {
+                Logger.d("UtilToJS,appExit()");
+               Activity activity =(Activity)context;
+               if(activity instanceof BaseWebActivity){
+                   BaseWebActivity baseWebActivity = (BaseWebActivity)activity;
+                   baseWebActivity.AppExit();
+               }else{
+                   android.os.Process.killProcess(android.os.Process.myPid());   //获取PID
+                   System.exit(0);
+               }
+            });
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    /*---------------------------通过客户端请求接口------------------------*/
+
+
+
+    /*
+     *js 通过apk客户端访问网络接口
+     *@param contentType 网络类型,详情参考网络请求的content-type
+     *@param paramJson 请求参数集,格式为json字符串
+     *@param headJson 请求头部集,格式为json字符串
+     *@param method 请求方法,1,get;2,post
+     *@param ignoreResult 是否忽略结果,true,忽略;false,不忽略.
+     *@param tag 透传参数,将在结果回调中一并返回,主要区别多个并发请求
+     * */
+    @JavascriptInterface
+    public int clientWebRequest(String paramsJson){
+        try{
+            Logger.d("clientWebRequest(),paramsJson" + paramsJson);
+            JSONObject requestParams = new JSONObject(paramsJson);
+            String url = JSONUtil.getString(requestParams,"url","");
+            String methodStr = JSONUtil.getString(requestParams,"method","1");
+            int method = 0;
+            if(MathUtil.isDigitsOnly(methodStr)){
+                method = Integer.parseInt(methodStr);
+                if( method <0  || method > 1 ){
+                    Logger.w("clientWebRequest 请求参数method必须为0或者1,目前重置为0");
+                    method = 0;
+                }
+            }else{
+                Logger.w("clientWebRequest 请求参数method必须为数字,目前重置为0");
+            }
+            String contentType = JSONUtil.getString(requestParams,"content_type","application/json");
+            String headJson = JSONUtil.getString(requestParams,"head_json","{}");
+            String paramJson = JSONUtil.getString(requestParams,"param_json","{}");
+            String ignore = JSONUtil.getString(requestParams,"ignore_result","0");
+            boolean ignoreResult = TextUtils.equals(ignore,"1");
+            String tag = JSONUtil.getString(requestParams,"tag","");
+            toolsUtil.clientWebRequest(url, method, contentType, headJson,paramJson, ignoreResult, tag,
+                    (int what,String response,String tag1)->{
+                        Logger.d(String.format("response:%s;tag:%s",response,tag1));
+                        JsUtil.evaluateJavascript(context,webView,
+                                String.format(JS_EVENT_RESPONSE,response,tag1));
+                    });
+            return SUCCESS;
+        }catch (Exception e){
+            e.printStackTrace();
+            return EXCEPTION_ERROR;
+        }
+    }
+
+
+}

文件差異過大導致無法顯示
+ 34 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 9 - 0
app/src/main/res/drawable/bg_ad_time.xml

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

+ 26 - 0
app/src/main/res/drawable/frame_anim.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:oneshot="false">
+    <item android:drawable="@drawable/loading_anim_0" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_1" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_2" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_3" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_4" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_5" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_6" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_7" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_8" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_9" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_10" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_11" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_12" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_13" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_14" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_15" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_16" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_17" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_18" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_19" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_20" android:duration="100"/>
+    <item android:drawable="@drawable/loading_anim_21" android:duration="100"/>
+</animation-list>

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#008577"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

二進制
app/src/main/res/drawable/loading_anim_0.png


二進制
app/src/main/res/drawable/loading_anim_1.png


二進制
app/src/main/res/drawable/loading_anim_10.png


二進制
app/src/main/res/drawable/loading_anim_11.png


二進制
app/src/main/res/drawable/loading_anim_12.png


二進制
app/src/main/res/drawable/loading_anim_13.png


二進制
app/src/main/res/drawable/loading_anim_14.png


二進制
app/src/main/res/drawable/loading_anim_15.png


二進制
app/src/main/res/drawable/loading_anim_16.png


二進制
app/src/main/res/drawable/loading_anim_17.png


二進制
app/src/main/res/drawable/loading_anim_18.png


二進制
app/src/main/res/drawable/loading_anim_19.png


二進制
app/src/main/res/drawable/loading_anim_2.png


二進制
app/src/main/res/drawable/loading_anim_20.png


二進制
app/src/main/res/drawable/loading_anim_21.png


二進制
app/src/main/res/drawable/loading_anim_3.png


二進制
app/src/main/res/drawable/loading_anim_4.png


二進制
app/src/main/res/drawable/loading_anim_5.png


二進制
app/src/main/res/drawable/loading_anim_6.png


二進制
app/src/main/res/drawable/loading_anim_7.png


二進制
app/src/main/res/drawable/loading_anim_8.png


二進制
app/src/main/res/drawable/loading_anim_9.png


二進制
app/src/main/res/drawable/loading_bg.jpg


+ 18 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout 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"
+    tools:context=".MainActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hello World!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>

+ 73 - 0
app/src/main/res/layout/activity_test.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout 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:id="@+id/player_container"
+    tools:context=".test.TestActivity">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_gravity="center"
+        android:gravity="center_horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        >
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:text="播放"
+            android:id="@+id/play_btn"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:text="改变尺寸"
+            android:id="@+id/change_btn"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="8dp"
+            android:text="暂停"
+            android:id="@+id/pause_btn"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="8dp"
+            android:text="恢复"
+            android:id="@+id/resume_btn"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="8dp"
+            android:text="快进5秒"
+            android:id="@+id/forward_btn"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="8dp"
+            android:text="快退5秒"
+            android:id="@+id/back_btn"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="8dp"
+            android:text="退出"
+            android:id="@+id/exit_btn"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="8dp"
+            android:text="退出app"
+            android:id="@+id/exit_app"/>
+    </LinearLayout>
+
+</FrameLayout>

+ 134 - 0
app/src/main/res/layout/activity_test_player.xml

@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout 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"
+    tools:context=".test.TestPlayerActivity">
+    <com.haochuan.hciptvbasic.video.HCPlayer
+        android:id="@+id/hc_player"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+    <!--loading-->
+    <ProgressBar
+        android:id="@+id/loading"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:visibility="visible"
+        android:layout_centerInParent="true" />
+
+    <!--底部栏-->
+    <LinearLayout
+        android:id="@+id/layout_bottom"
+        android:layout_width="match_parent"
+        android:visibility="gone"
+        android:layout_height="80dp"
+        android:paddingLeft="50dp"
+        android:paddingRight="50dp"
+        android:layout_marginLeft="11.3dp"
+        android:layout_marginBottom="11.3dp"
+        android:layout_marginRight="11.3dp"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentStart="true"
+        android:background="#b3000000"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <!--左边播放/暂停按钮-->
+        <ImageView
+            android:id="@+id/start"
+            android:layout_width="57.3dp"
+            android:layout_height="57.3dp"
+            android:layout_marginStart="11.3dp"
+            />
+
+        <!--进度条-->
+        <SeekBar
+            android:id="@+id/progress_bar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:focusable="true"
+            android:gravity="center"
+            android:max="100"
+            android:maxHeight="4.0dp"
+            android:minHeight="4.0dp"
+            android:layout_marginLeft="8dp"
+            android:paddingBottom="8.0dp"
+            android:paddingLeft="10.6dp"
+            android:paddingRight="10.6dp"
+            android:paddingTop="8.0dp"
+            />
+
+        <!--播放进度/播放总时长显示-->
+        <TextView
+            android:id="@+id/current_total"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="11.3dp"
+            android:text="00:00/00:00"
+            android:textColor="#eaebec"
+            android:textSize="18.6dp"
+            />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/seek_container"
+        android:layout_width="match_parent"
+        android:visibility="gone"
+        android:layout_height="80dp"
+        android:paddingLeft="50dp"
+        android:paddingRight="50dp"
+        android:layout_marginLeft="11.3dp"
+        android:layout_marginBottom="11.3dp"
+        android:layout_marginRight="11.3dp"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentStart="true"
+        android:background="#b3000000"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <!--左边播放/暂停按钮-->
+        <TextView
+            android:id="@+id/seek_percent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="11.3dp"
+            android:text="1%"
+            android:textColor="#eaebec"
+            />
+
+        <!--进度条-->
+        <SeekBar
+            android:id="@+id/seek_bar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:focusable="true"
+            android:gravity="center"
+            android:max="100"
+            android:maxHeight="4.0dp"
+            android:minHeight="4.0dp"
+            android:layout_marginLeft="8dp"
+            android:paddingBottom="8.0dp"
+            android:paddingLeft="10.6dp"
+            android:paddingRight="10.6dp"
+            android:paddingTop="8.0dp"
+            />
+
+        <!--播放进度/播放总时长显示-->
+        <TextView
+            android:id="@+id/seek_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="11.3dp"
+            android:text="00:00"
+            android:textColor="#eaebec"
+            android:textSize="18.6dp"
+            />
+
+    </LinearLayout>
+
+</RelativeLayout>

+ 15 - 0
app/src/main/res/layout/view_video_control_empty.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/black">
+
+    <FrameLayout
+        android:id="@+id/surface_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center">
+
+    </FrameLayout>
+
+</RelativeLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

二進制
app/src/main/res/mipmap-hdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


二進制
app/src/main/res/mipmap-mdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


二進制
app/src/main/res/mipmap-xhdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


二進制
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


二進制
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 6 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">广州浩传VR大厅</string>
+</resources>

+ 15 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,15 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+
+        <item name="android:windowBackground">@android:color/black</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+</resources>

+ 17 - 0
app/src/test/java/com/haochuan/hciptvbasic/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.haochuan.hciptvbasic;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 62 - 0
build.gradle

@@ -0,0 +1,62 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+        
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.4.1'
+        
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+
+    ext.versions = [
+            'minSdk'                : 19,
+            'targetSdk'             : 28,
+            'compileSdk'            : 28,
+            'Java'                  : JavaVersion.VERSION_1_8,
+            'supportLibrary'        : '28.0.0',
+            'versionCode'           : 1,
+            'versionName'           : "1.0.1"
+    ]
+
+    // 引用的lib
+    ext.lib = [
+            /**  常备底层库*/
+            support                      : [
+                    v7          : "com.android.support:appcompat-v7:${versions.supportLibrary}",
+                    constraint_layout  : 'com.android.support.constraint:constraint-layout:1.1.3',
+                    appcompat   : 'androidx.appcompat:appcompat:1.0.2',
+                    constraintlayout : 'androidx.constraintlayout:constraintlayout:1.1.3',
+            ],
+            test                         : [
+                    junit   : 'junit:junit:4.12',
+                    runner  : 'com.android.support.test:runner:1.0.2',
+                    espresso: 'com.android.support.test.espresso:espresso-core:3.0.2',
+                    test_runner  : 'androidx.test.runner.AndroidJUnitRunner',
+            ],
+
+            //其他依赖组件
+            dependency_library            :[
+                    nohttp  : 'com.yanzhenjie.nohttp:nohttp:1.1.11',
+                    gson :'com.google.code.gson:gson:2.8.6',
+                    GSYVideoPlayer  : 'com.shuyu:GSYVideoPlayer:7.0.2'
+            ],
+    ]
+
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 1 - 0
core/.gitignore

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

+ 39 - 0
core/build.gradle

@@ -0,0 +1,39 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion versions.compileSdk
+    defaultConfig {
+        minSdkVersion versions.minSdk
+        targetSdkVersion versions.targetSdk
+        versionCode versions.versionCode
+        versionName versions.versionName
+        testInstrumentationRunner lib.test.test_runner
+    }
+
+    buildTypes {
+        release {
+            buildConfigField "Boolean", "isDebug", "false"     //app全局是否调试,发布版本不要开启
+        }
+
+        debug {
+            buildConfigField "Boolean", "isDebug", "true"     //app全局是否调试,调试版本开启
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility versions.Java
+        targetCompatibility versions.Java
+    }
+
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    implementation lib.support.appcompat
+    testImplementation lib.test.junit
+    androidTestImplementation lib.test.runner
+    androidTestImplementation lib.test.espresso
+    api lib.dependency_library.nohttp
+    api lib.dependency_library.gson
+}

+ 21 - 0
core/proguard-rules.pro

@@ -0,0 +1,21 @@
+# 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

+ 27 - 0
core/src/androidTest/java/com/haochuan/core/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package com.haochuan.core;
+
+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.haochuan.core", appContext.getPackageName());
+    }
+}

+ 7 - 0
core/src/main/AndroidManifest.xml

@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.haochuan.core">
+
+    <application
+        android:allowBackup="true"
+        android:supportsRtl="true" />
+</manifest>

+ 109 - 0
core/src/main/java/com/haochuan/core/BaseMediaPlayer.java

@@ -0,0 +1,109 @@
+package com.haochuan.core;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public abstract class BaseMediaPlayer extends FrameLayout {
+
+
+    public BaseMediaPlayer(@NonNull Context context) {
+        super(context);
+    }
+
+    public BaseMediaPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public BaseMediaPlayer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    /*---------------------------操作函数------------------------*/
+
+
+    /*
+     * 播放视频
+     *@param jsonParam
+     * */
+    public abstract void play(String url,String examineId,String examineType);
+
+
+    /*
+     * 设置开始时间
+     *@param url
+     * */
+    public abstract void setStartTime(int time);
+
+    /*
+     * 恢复
+     *@param url
+     * */
+    public abstract void resume();
+
+    /*
+     * 暂停
+     * */
+    public abstract void pause();
+
+    /*
+     * 拖动
+     * @param 目标位置,单位毫秒
+     * */
+    public abstract void seek(int position);
+
+
+
+    /*
+     * 资源释放
+     * */
+    public abstract void release();
+
+    /*---------------------------获取播放器状态和参数函数------------------------*/
+
+
+    /*
+     * 当前是否正在播放
+     * */
+    public abstract boolean isPlaying();
+
+
+    /*
+     * 视频是否准备完毕
+     * */
+    public abstract boolean isPrePared();
+
+
+    /*
+     * 获得视频时长
+     * */
+    public abstract int getDuration();
+
+
+    /*
+     * 获得当前播放时长
+     * */
+    public abstract int getCurrentPlayPosition();
+
+
+    /*
+     * 获得当前播放状态
+     * */
+    public abstract int getCurrentStatus();
+
+    /*
+    * 设置事件监听
+    * */
+    public  abstract void setVideoPlayerListener(@NonNull IVideoPlayer iVideoPlayer);
+
+    /**
+     * vr项目特别添加,设置播放器的surface
+     * @param surface
+     */
+    public  abstract void setSurface(@NonNull Surface surface);
+}
+

+ 21 - 0
core/src/main/java/com/haochuan/core/IVideoPlayer.java

@@ -0,0 +1,21 @@
+package com.haochuan.core;
+
+public interface IVideoPlayer {
+    /*---------------事件接口----------------*/
+    void onPreparing();
+
+    void onPlaying();
+
+    void onResume();
+
+    void onPause();
+
+    void onDestroy();
+
+    void onPlayingBuffering();
+
+    void onCompletion();
+
+    void onError(int what, int extra);
+
+}

+ 166 - 0
core/src/main/java/com/haochuan/core/Logger.java

@@ -0,0 +1,166 @@
+package com.haochuan.core;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+import android.webkit.WebView;
+
+import androidx.annotation.NonNull;
+
+import com.haochuan.core.util.ELS;
+import com.haochuan.core.util.ToolsUtil;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+public class Logger {
+    private static String TAG = "HcIPTV";
+    private static Context context;
+    private static WebView webView;
+
+    private static Boolean LOG_NEED_WRITE_TO_FILE = false;// 日志写入文件开关
+    private static char LOG_TYPE = 'v';// 输入日志类型,w代表只输出告警信息等,v代表输出所有信息
+
+    private static ELS els;
+
+    public static void init(Context appContext, WebView mWebView) {
+        context = appContext;
+        webView = mWebView;
+        els = ELS.getInstance(context);
+        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            return;
+        }
+        try {
+            //日志根目录文件夹
+            String pathName = Environment.getExternalStorageDirectory().getCanonicalPath() + "/"
+                    + ToolsUtil.getAppProcessName(context);
+            File dir = new File(pathName);
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+            //保证每次使用只生成一个Log文件,利用sp存储文件名,方便后面写入时复用
+            String fileName = pathName + "/log_" + getCurrentDate() + ".txt";
+            els.saveStringData(ELS.LOG_FILE_NAME, fileName);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void e(@NonNull int code, @NonNull String message) {
+        message = String.format("错误代码: %s,错误信息:%s", code, message);
+        log(message, 'e');
+    }
+
+    public static void e(@NonNull String message) {
+        message = "错误! " + message;
+        log(message, 'e');
+    }
+
+    public static void w(@NonNull String message) {
+        message = "警告! " + message;
+        log(message, 'w');
+    }
+
+    public static void d(@NonNull String message) {
+        message = "调试 " + message;
+        log(message, 'd');
+    }
+
+    public static void i(@NonNull String message) {
+        log(message, 'i');
+    }
+
+    public static void v(@NonNull String message) {
+        log(message, 'v');
+    }
+
+    //根据tag, msg和等级,输出日志
+    private static void log(String message, char level) {
+        if (BuildConfig.isDebug) {//日志文件总开关
+            if ('e' == level && ('e' == LOG_TYPE || 'v' == LOG_TYPE)) {
+                Log.e(TAG, message);
+            } else if ('w' == level && ('w' == LOG_TYPE || 'v' == LOG_TYPE)) {
+                Log.w(TAG, message);
+            } else if ('i' == level && ('i' == LOG_TYPE || 'v' == LOG_TYPE)) {
+                Log.i(TAG, message);
+            } else if ('d' == level && ('d' == LOG_TYPE || 'v' == LOG_TYPE)) {
+                Log.d(TAG, message);
+            } else {
+                Log.v(TAG, message);
+            }
+            if (LOG_NEED_WRITE_TO_FILE)
+                writeLogToFile(String.valueOf(level), TAG, message);
+        }
+        messageToJs(message);
+    }
+
+    private static void messageToJs(@NonNull String message) {
+        //将日志传给MainActivity,然后传给js
+    }
+
+    public static void evaluateJavascript(String script) {
+        if (context == null) {
+            Log.e(TAG, "context is null,can`t execute evaluateJavascript");
+            return;
+        }
+        if (webView == null) {
+            Log.e(TAG, "webView is null,can`t execute evaluateJavascript");
+            return;
+        }
+        Activity activity = (Activity) context;
+        if (webView == null) {
+            return;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            activity.runOnUiThread(() -> webView.evaluateJavascript(script, value -> {
+                //此处为 js 返回的结果
+            }));
+        } else {
+            activity.runOnUiThread(() -> webView.loadUrl(script));
+        }
+    }
+
+    //打开日志文件并写入日志
+    private static void writeLogToFile(String logType, String tag, String text) {
+        FileOutputStream fos = null;
+        try {
+            //拼接需要写入的文本
+            String logStr = getCurrentDate() + " /" + logType + " /" + tag + ": " + text + "\n";
+            //第二个参数表示接着之前的文本写入,不会覆盖
+            fos = new FileOutputStream(els.getStringData(ELS.LOG_FILE_NAME), true);
+            fos.write(logStr.getBytes());
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (fos != null) {
+                    fos.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static String getCurrentDate() {
+        //获取当前格式化的日期
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return sdf.format(System.currentTimeMillis());
+    }
+
+    public static void setLogNeedWriteToFile(Boolean logNeedWriteToFile) {
+        LOG_NEED_WRITE_TO_FILE = logNeedWriteToFile;
+    }
+
+    public static Boolean getLogNeedWriteToFile() {
+        return LOG_NEED_WRITE_TO_FILE;
+    }
+}

+ 100 - 0
core/src/main/java/com/haochuan/core/http/DownloadServer.java

@@ -0,0 +1,100 @@
+package com.haochuan.core.http;
+
+import android.content.Context;
+
+import com.yanzhenjie.nohttp.Headers;
+import com.yanzhenjie.nohttp.NoHttp;
+import com.yanzhenjie.nohttp.RequestMethod;
+import com.yanzhenjie.nohttp.download.DownloadListener;
+import com.yanzhenjie.nohttp.download.DownloadQueue;
+import com.yanzhenjie.nohttp.download.DownloadRequest;
+
+/**
+ * 下载文件单例
+ * @since 2019/1/10
+ */
+public class DownloadServer {
+
+    private static DownloadServer instance;
+    private static DownloadRequest downloadRequest;
+
+    public static DownloadServer getInstance() {
+        if (instance == null) {
+            synchronized (DownloadServer.class) {
+                if (instance == null) {
+                    instance = new DownloadServer();
+                }
+            }
+        }
+        return instance;
+    }
+
+    private DownloadQueue mDownloadQueue;
+
+    private DownloadServer() {
+        mDownloadQueue = NoHttp.newDownloadQueue();
+    }
+
+    public void download(int what, DownloadRequest mRequest, DownloadListener mListener) {
+        mDownloadQueue.add(what, mRequest, mListener);
+    }
+
+    public void download(Context context, String downloadUrl, String fileFolder, String fileName, DownloadServerListener listener) {
+        downloadRequest = new DownloadRequest(downloadUrl, RequestMethod.GET, fileFolder,
+                fileName, true, true);
+        downloadRequest.setCancelSign(context);
+        download(100, downloadRequest, new DownloadListener() {
+            @Override
+            public void onDownloadError(int what, Exception exception) {
+                listener.onError(what, exception);
+                downloadRequest = null;
+            }
+
+            @Override
+            public void onStart(int what, boolean isResume, long rangeSize, Headers responseHeaders, long allCount) {
+
+            }
+
+            @Override
+            public void onProgress(int what, int progress, long fileCount, long speed) {
+
+            }
+
+            @Override
+            public void onFinish(int what, String filePath) {
+                listener.onFinish(filePath);
+                downloadRequest = null;
+            }
+
+            @Override
+            public void onCancel(int what) {
+
+            }
+        });
+    }
+
+    public void cancelBySign(Object sign) {
+        if (mDownloadQueue != null) {
+            mDownloadQueue.cancelBySign(sign);
+        }
+    }
+
+    public void cancelAll() {
+        if (mDownloadQueue != null) {
+            mDownloadQueue.cancelAll();
+        }
+    }
+
+    //关闭队列,在使用的地方记得及时调用该方法关闭,以防内存泄漏
+    public void stop() {
+        if (mDownloadQueue != null) {
+            mDownloadQueue.stop();
+        }
+    }
+
+    public interface DownloadServerListener {
+        public void onFinish(String filePath);
+
+        public void onError(int what, Exception exception);
+    }
+}

+ 51 - 0
core/src/main/java/com/haochuan/core/http/MyRequest.java

@@ -0,0 +1,51 @@
+package com.haochuan.core.http;
+
+import android.text.TextUtils;
+
+import com.yanzhenjie.nohttp.Headers;
+import com.yanzhenjie.nohttp.RequestMethod;
+import com.yanzhenjie.nohttp.rest.Request;
+import com.yanzhenjie.nohttp.tools.HeaderUtils;
+import com.yanzhenjie.nohttp.tools.IOUtils;
+
+public class MyRequest extends Request<String> {
+    public MyRequest(String url) {
+        super(url, RequestMethod.GET);
+    }
+
+    public MyRequest(String url, RequestMethod requestMethod) {
+        super(url, requestMethod);
+    }
+
+
+    @Override
+    public String parseResponse(Headers responseHeaders, byte[] responseBody) throws Exception {
+        if (responseBody == null || responseBody.length == 0)
+            return "";
+        String charset = HeaderUtils.parseHeadValue(responseHeaders.getContentType(), "charset", "");
+        return IOUtils.toString(responseBody, charset);
+    }
+
+    @Override
+    public MyRequest addHeader(String key, String value) {
+        //先查找Headers中是否已经包含key
+        if(headsHasKey(key)){
+            setHeader(key,value);
+        }else{
+            addHeader(key,value);
+        }
+        return this;
+    }
+
+    private Boolean headsHasKey(String keyStr){
+        Boolean result = false;
+        final Headers headers = getHeaders();
+        for(String key : headers.keySet()){
+            if(TextUtils.equals(key,keyStr)){
+                result = true;
+                break;
+            }
+        }
+        return result;
+    }
+}

+ 230 - 0
core/src/main/java/com/haochuan/core/http/RequestServer.java

@@ -0,0 +1,230 @@
+package com.haochuan.core.http;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.google.gson.Gson;
+import com.haochuan.core.Logger;
+import com.haochuan.core.http.bean.ApkSettingBean;
+import com.haochuan.core.http.bean.ResponseBean;
+import com.haochuan.core.http.bean.UpdateResponseBean;
+import com.haochuan.core.util.ELS;
+import com.haochuan.core.util.ToolsUtil;
+import com.yanzhenjie.nohttp.FileBinary;
+import com.yanzhenjie.nohttp.Headers;
+import com.yanzhenjie.nohttp.NoHttp;
+import com.yanzhenjie.nohttp.RequestMethod;
+import com.yanzhenjie.nohttp.error.NetworkError;
+import com.yanzhenjie.nohttp.error.TimeoutError;
+import com.yanzhenjie.nohttp.error.URLError;
+import com.yanzhenjie.nohttp.error.UnKnownHostError;
+import com.yanzhenjie.nohttp.rest.RequestQueue;
+import com.yanzhenjie.nohttp.rest.Response;
+import com.yanzhenjie.nohttp.rest.SimpleResponseListener;
+import com.yanzhenjie.nohttp.rest.StringRequest;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+/**
+ * Created by ncx on 2020/3/10
+ * 接口统一请求类
+ */
+public class RequestServer {
+    public static final String UNKNOW_ERROR = "0x001";
+    public static final String EXCEPTION_NETWORK = "0x002";
+    public static final String EXCEPTION_CONNECT_TIMEOUT = "0x003";
+    public static final String EXCEPTION_HOST = "0x004";
+    public static final String EXCEPTION_URL = "0x005";
+    public static final String OTHER_MESSAGE = "其它错误";
+    public static final String OTHER_MESSAGE_CODE = "0x999";
+
+
+    //    private static final String HOST = "http://150.138.11.180:6401/";
+    private static final String HOST = "http://sxsj.reading.sdteleiptv.com:6401/";
+
+    private static final String UPDATE_VERSION = HOST + "apk/up_version";
+
+    private static final String UPLOAD_LOG_FILE = HOST + "apk/save_apk_log";
+
+    private static final String GET_APK_SETTING = HOST + "apk/get_apk_setting";
+
+    private static RequestServer instance;
+
+    public static RequestServer getInstance() {
+        if (instance == null)
+            synchronized (RequestServer.class) {
+                if (instance == null)
+                    instance = new RequestServer();
+            }
+        return instance;
+    }
+
+    private RequestQueue queue;
+    private Gson gson;
+
+    private RequestServer() {
+        queue = NoHttp.newRequestQueue();
+        gson = new Gson();
+    }
+
+    // 完全退出app时,调用这个方法释放CPU。
+    public void stop() {
+        if (queue != null) {
+            queue.stop();
+        }
+    }
+
+    //请求失败错误码处理
+    private String requestFailedCode(Exception exception) {
+        String stringRes = UNKNOW_ERROR;
+        if (exception instanceof NetworkError) {
+            stringRes = EXCEPTION_NETWORK;
+        } else if (exception instanceof TimeoutError) {
+            stringRes = EXCEPTION_CONNECT_TIMEOUT;
+        } else if (exception instanceof UnKnownHostError) {
+            stringRes = EXCEPTION_HOST;
+        } else if (exception instanceof URLError) {
+            stringRes = EXCEPTION_URL;
+        }
+        return stringRes;
+    }
+
+    //请求失败信息处理
+    private String requestFailedMessage(Exception exception) {
+        String stringRes = "未知错误";
+        if (exception instanceof NetworkError) {
+            stringRes = "网络不可用,请检查网络";
+        } else if (exception instanceof TimeoutError) {
+            stringRes = "链接服务器超时";
+        } else if (exception instanceof UnKnownHostError) {
+            stringRes = "没有找到Url指定的服务器";
+        } else if (exception instanceof URLError) {
+            stringRes = "Url格式错误";
+        }
+        return stringRes;
+    }
+
+    //获取版本更新
+    public void updateVersion(Context mContext, int versionCode, String userId,
+                              ResponseListener<UpdateResponseBean> listener) {
+        StringRequest request = new StringRequest(UPDATE_VERSION, RequestMethod.GET);
+        //当前版本号
+        request.add("versionCode", versionCode);
+        //userId用于指定用户升级
+        request.add("userId", userId);
+        queue.add(1, request, new SimpleResponseListener<String>() {
+            @Override
+            public void onSucceed(int what, Response<String> response) {
+                super.onSucceed(what, response);
+                String responseStr = response.get();
+                if (!TextUtils.isEmpty(responseStr)) {
+                    UpdateResponseBean bean = UpdateResponseBean.objectFromData(responseStr);
+                    if (bean.getCode() == 0) {
+                        if (bean.getData() != null) {
+                            if (bean.getData().getUpload_type() != 1) {
+                                //upload_type为1才更新
+                                listener.onFailure(OTHER_MESSAGE_CODE, "upload_type不为1,无需更新");
+                                return;
+                            }
+                            if (bean.getData().getVersion_code() <= ToolsUtil.getVersionCode(mContext)) {
+                                //接口返回版本比本地版本高才更新
+                                listener.onFailure(OTHER_MESSAGE_CODE, "当前版本和后台版本一直,无需更新");
+                                return;
+                            }
+                            listener.onSuccess(bean);
+                        } else {
+                            listener.onFailure(OTHER_MESSAGE_CODE, OTHER_MESSAGE);
+                        }
+                    } else {
+                        listener.onFailure(OTHER_MESSAGE_CODE, OTHER_MESSAGE);
+                    }
+                } else {
+                    listener.onFailure(OTHER_MESSAGE_CODE, OTHER_MESSAGE);
+                }
+            }
+
+            @Override
+            public void onFailed(int what, Response<String> response) {
+                super.onFailed(what, response);
+                if (response != null) {
+                    String code = requestFailedCode(response.getException());
+                    String message = requestFailedMessage(response.getException());
+                    Logger.d("updateVersion,onFailed:" + code + "," + message);
+                    listener.onFailure(code, message);
+                }
+            }
+        });
+    }
+
+    //查询是否需要上传日志文件
+    public void getApkSetting(String uid, ResponseListener<ApkSettingBean> listener) {
+        StringRequest request = new StringRequest(GET_APK_SETTING, RequestMethod.GET);
+        request.add("uid", uid);
+        queue.add(2, request, new SimpleResponseListener<String>() {
+            @Override
+            public void onSucceed(int what, Response<String> response) {
+                super.onSucceed(what, response);
+                String responseStr = response.get();
+                if (!TextUtils.isEmpty(responseStr)) {
+                    ApkSettingBean bean = ApkSettingBean.objectFromData(responseStr);
+                    if (bean.getCode() == 0) {
+                        listener.onSuccess(bean);
+                    } else {
+                        listener.onFailure(OTHER_MESSAGE_CODE, OTHER_MESSAGE);
+                    }
+                } else {
+                    listener.onFailure(OTHER_MESSAGE_CODE, OTHER_MESSAGE);
+                }
+            }
+
+            @Override
+            public void onFailed(int what, Response<String> response) {
+                super.onFailed(what, response);
+                if (response != null) {
+                    String code = requestFailedCode(response.getException());
+                    String message = requestFailedMessage(response.getException());
+                    Logger.d("queryUploadType,onFailed:" + code + "," + message);
+                    listener.onFailure(code, message);
+                }
+            }
+        });
+    }
+
+    //上传日志文件
+    public void uploadLogFile(String fileName, ResponseListener<ResponseBean> listener) {
+        try {
+            File file = new File(fileName);
+            if (!file.exists()) return;
+            StringRequest request = new StringRequest(UPLOAD_LOG_FILE, RequestMethod.POST);
+            request.add("file", new FileBinary(file));
+            queue.add(3, request, new SimpleResponseListener<String>() {
+                @Override
+                public void onSucceed(int what, Response<String> response) {
+                    super.onSucceed(what, response);
+                    String responseStr = response.get();
+                    if (!TextUtils.isEmpty(responseStr)) {
+                        Logger.d("uploadLogFile responseStr:" + responseStr);
+                    } else {
+                        listener.onFailure(OTHER_MESSAGE_CODE, OTHER_MESSAGE);
+                    }
+                }
+
+                @Override
+                public void onFailed(int what, Response<String> response) {
+                    super.onFailed(what, response);
+                    if (response != null) {
+                        String code = requestFailedCode(response.getException());
+                        String message = requestFailedMessage(response.getException());
+                        Logger.d("uploadLogFile,onFailed:" + code + "," + message);
+                        listener.onFailure(code, message);
+                    }
+                }
+
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 11 - 0
core/src/main/java/com/haochuan/core/http/ResponseListener.java

@@ -0,0 +1,11 @@
+package com.haochuan.core.http;
+
+/**
+ * Created by ncx on 2020/3/10
+ * 网络请求响应接口
+ */
+public interface ResponseListener<T> {
+    void onSuccess(T response);
+
+    void onFailure(String code, String message);
+}

+ 211 - 0
core/src/main/java/com/haochuan/core/http/UpgradeDialog.java

@@ -0,0 +1,211 @@
+package com.haochuan.core.http;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+
+import com.haochuan.core.Logger;
+import com.haochuan.core.R;
+import com.haochuan.core.http.bean.UpdateResponseBean;
+import com.haochuan.core.util.ToolsUtil;
+import com.yanzhenjie.nohttp.Headers;
+import com.yanzhenjie.nohttp.RequestMethod;
+import com.yanzhenjie.nohttp.download.DownloadRequest;
+import com.yanzhenjie.nohttp.download.SimpleDownloadListener;
+
+import java.io.File;
+
+
+public class UpgradeDialog extends DialogFragment implements View.OnClickListener {
+
+    public static final String TAG = "UpgradeDialog";
+    public static final String RESPONSE_DATA_KEY = "response_data_key";
+    private ImageButton mClose;
+    private TextView tvTitle;
+    private ProgressBar mProgress;
+    private Button btnOk;
+    private UpdateResponseBean responseBean;
+
+    private static final int UPDATE_PROGRESS = 0x120;
+    private static final int UPDATE_TITLE = 0x121;
+
+    private Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            super.handleMessage(msg);
+            switch (msg.what) {
+                case UPDATE_PROGRESS:
+                    if (mProgress != null) {
+                        mProgress.setVisibility(View.VISIBLE);
+                        if (mProgress.getProgress() >= 100) {
+                            mProgress.setProgress(100);
+                        } else {
+                            mProgress.setProgress(msg.arg1);
+                        }
+                    }
+                    break;
+                case UPDATE_TITLE:
+                    downLoad((String)msg.obj);
+                    break;
+            }
+        }
+    };
+
+    public UpgradeDialog() {
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        Bundle bundle = getArguments();
+        if (bundle != null) {
+            responseBean = (UpdateResponseBean) bundle.getSerializable(RESPONSE_DATA_KEY);
+        }
+        return super.onCreateView(inflater, container, savedInstanceState);
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        super.onDismiss(dialog);
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        if (getActivity() == null) {
+            dismiss();
+        }
+        Dialog dialog = new Dialog(getActivity(), R.style.MsgDialogStyle);
+        View view = View.inflate(getActivity(), R.layout.upgrade_dialog, null);
+        tvTitle = view.findViewById(R.id.update_title);
+        mProgress = view.findViewById(R.id.progress);
+        mClose = view.findViewById(R.id.close);
+        btnOk = view.findViewById(R.id.btn_ok);
+        mClose.setOnClickListener(this);
+        btnOk.setOnClickListener(this);
+        dialog.setContentView(view);
+        //关闭按钮获取焦点
+        btnOk.setFocusable(true);
+        return dialog;
+    }
+
+    private void changeProgress(int progressValue) {
+        Message message = Message.obtain();
+        message.what = UPDATE_PROGRESS;
+        message.arg1 = progressValue;
+        mHandler.sendMessage(message);
+    }
+
+    private void downLoad(String title) {
+        if (mProgress == null) {
+            return;
+        }
+        if (btnOk == null) {
+            return;
+        }
+        if (tvTitle == null) {
+            return;
+        }
+        if (getActivity() == null) {
+            showToast("出意外了,请重启试试");
+            return;
+        }
+        if (responseBean == null) {
+            showToast("下载失败,请重启试试");
+        }
+        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            //判断内存是否可用
+            showToast("当前内存不可用,请检查");
+            return;
+        }
+        mProgress.setVisibility(View.VISIBLE);
+        btnOk.setVisibility(View.GONE);
+        tvTitle.setText(title);
+        //剩余内存和安装包大小作比较,+1是为了防止单位换算的误差
+        if ((ToolsUtil.getAvailableMemory(getActivity()) / 1024d / 1024d)
+                < responseBean.getData().getApk_size() + 1) {
+            showToast("剩余内存不足");
+            return;
+        }
+        String folder = Environment.getExternalStorageDirectory().getAbsolutePath()
+                + File.separator + "apk";
+        String url = responseBean.getData().getApk_url();
+        String fileName = getFileNameFromUrl(url);
+        File downFile = new File(folder + File.separator + fileName);
+        Logger.d("downFile:" + folder + File.separator + fileName);
+        //删掉旧包,保证每次都下最新的包
+        if (downFile.exists()) {
+            downFile.delete();
+        }
+        DownloadRequest downloadRequest = new DownloadRequest(url, RequestMethod.GET, folder,
+                fileName, true, true);
+        DownloadServer.getInstance().download(120, downloadRequest, new SimpleDownloadListener() {
+
+            @Override
+            public void onProgress(int what, int progress, long fileCount, long speed) {
+                super.onProgress(what, progress, fileCount, speed);
+                Logger.d("onProgress:" + progress);
+                changeProgress(progress);
+            }
+
+            @Override
+            public void onFinish(int what, String filePath) {
+                super.onFinish(what, filePath);
+                Logger.d("onFinish:" + filePath);
+                //下载好后安装APK
+                if (getActivity() != null) {
+                    ToolsUtil.installApk(getActivity(), filePath);
+                } else {
+                    showToast("出意外了,请重启试试");
+                }
+                dismiss();
+            }
+        });
+    }
+
+    private String getFileNameFromUrl(String url) {
+        String[] splitArray = url.split("/");
+        return splitArray[splitArray.length - 1];
+    }
+
+    private void showToast(String text) {
+        if (getActivity() != null) {
+            Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getId() == R.id.close) {
+            dismiss();
+        } else if (v.getId() == R.id.btn_ok) {
+            Message message = Message.obtain();
+            message.what = UPDATE_TITLE;
+            message.obj = "检测到更新,下载中...";
+            mHandler.sendMessage(message);
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        DownloadServer.getInstance().stop();
+        super.onDestroyView();
+    }
+}

+ 147 - 0
core/src/main/java/com/haochuan/core/http/bean/ApkSettingBean.java

@@ -0,0 +1,147 @@
+package com.haochuan.core.http.bean;
+
+import com.google.gson.Gson;
+
+import java.util.List;
+
+/**
+ * Created by ncx on 2020/3/17
+ * 获取APK配置状态实体类
+ */
+public class ApkSettingBean {
+    //code为0表示请求成功
+    private int code;
+    private String message;
+    private List<DataBean> data;
+
+    public static ApkSettingBean objectFromData(String str) {
+        return new Gson().fromJson(str, ApkSettingBean.class);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public List<DataBean> getData() {
+        return data;
+    }
+
+    public void setData(List<DataBean> data) {
+        this.data = data;
+    }
+
+    public static class DataBean {
+        //自增id,不需要处理
+        private int id;
+        //配置标识名称
+        private String setting_name;
+        //配置描述,解释
+        private Object setting_describe;
+        //配置值,如果是1代表该项配置已打开
+        private String setting_value;
+        //配置类型【1:apk】
+        private int setting_type;
+        private Object created_at;
+        private Object updated_at;
+        private Object deleted_at;
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public String getSetting_name() {
+            return setting_name;
+        }
+
+        public void setSetting_name(String setting_name) {
+            this.setting_name = setting_name;
+        }
+
+        public Object getSetting_describe() {
+            return setting_describe;
+        }
+
+        public void setSetting_describe(Object setting_describe) {
+            this.setting_describe = setting_describe;
+        }
+
+        public String getSetting_value() {
+            return setting_value;
+        }
+
+        public void setSetting_value(String setting_value) {
+            this.setting_value = setting_value;
+        }
+
+        public int getSetting_type() {
+            return setting_type;
+        }
+
+        public void setSetting_type(int setting_type) {
+            this.setting_type = setting_type;
+        }
+
+        public Object getCreated_at() {
+            return created_at;
+        }
+
+        public void setCreated_at(Object created_at) {
+            this.created_at = created_at;
+        }
+
+        public Object getUpdated_at() {
+            return updated_at;
+        }
+
+        public void setUpdated_at(Object updated_at) {
+            this.updated_at = updated_at;
+        }
+
+        public Object getDeleted_at() {
+            return deleted_at;
+        }
+
+        public void setDeleted_at(Object deleted_at) {
+            this.deleted_at = deleted_at;
+        }
+
+        @Override
+        public String toString() {
+            return "DataBean{" +
+                    "id=" + id +
+                    ", setting_name='" + setting_name + '\'' +
+                    ", setting_describe=" + setting_describe +
+                    ", setting_value='" + setting_value + '\'' +
+                    ", setting_type=" + setting_type +
+                    ", created_at=" + created_at +
+                    ", updated_at=" + updated_at +
+                    ", deleted_at=" + deleted_at +
+                    '}';
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ApkSettingBean{" +
+                "code=" + code +
+                ", message='" + message + '\'' +
+                ", data=" + data +
+                '}';
+    }
+}

+ 41 - 0
core/src/main/java/com/haochuan/core/http/bean/ResponseBean.java

@@ -0,0 +1,41 @@
+package com.haochuan.core.http.bean;
+
+import com.google.gson.Gson;
+
+/**
+ * Created by ncx on 2019/12/25
+ * 接口请求通用参数类
+ */
+public class ResponseBean {
+    //code为0表示请求成功
+    private int code;
+    private String message;
+
+    public static ResponseBean objectFromData(String str) {
+        return new Gson().fromJson(str, ResponseBean.class);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String toString() {
+        return "ResponseBean{" +
+                "code=" + code +
+                ", message='" + message + '\'' +
+                '}';
+    }
+}

+ 196 - 0
core/src/main/java/com/haochuan/core/http/bean/UpdateResponseBean.java

@@ -0,0 +1,196 @@
+package com.haochuan.core.http.bean;
+
+import com.google.gson.Gson;
+
+import java.io.Serializable;
+
+/**
+ * Created by ncx on 2020/3/11
+ * 更新接口返回数据实体类
+ */
+public class UpdateResponseBean implements Serializable {
+
+    //code为0表示请求成功
+    private int code;
+    private DataBean data;
+    private String msg;
+
+    public static UpdateResponseBean objectFromData(String str) {
+        return new Gson().fromJson(str, UpdateResponseBean.class);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public DataBean getData() {
+        return data;
+    }
+
+    public void setData(DataBean data) {
+        this.data = data;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public static class DataBean {
+        private int id;
+        private String name;
+        //APK包大小,用于对比机器剩余大小
+        private double apk_size;
+        //APK下载链接
+        private String apk_url;
+        //此参数为1才更新
+        private int upload_type;
+        //APK版本号
+        private int version_code;
+        //APK版本名
+        private String version_name;
+        //md5校验值,未来电视用
+        private String apk_md5;
+        //版本描述
+        private Object version_content;
+        //下面三个参数是对应操作时间
+        private String created_at;
+        private String updated_at;
+        private Object deleted_at;
+
+        public static DataBean objectFromData(String str) {
+
+            return new Gson().fromJson(str, DataBean.class);
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public double getApk_size() {
+            return apk_size;
+        }
+
+        public void setApk_size(double apk_size) {
+            this.apk_size = apk_size;
+        }
+
+        public String getApk_url() {
+            return apk_url;
+        }
+
+        public void setApk_url(String apk_url) {
+            this.apk_url = apk_url;
+        }
+
+        public int getUpload_type() {
+            return upload_type;
+        }
+
+        public void setUpload_type(int upload_type) {
+            this.upload_type = upload_type;
+        }
+
+        public int getVersion_code() {
+            return version_code;
+        }
+
+        public void setVersion_code(int version_code) {
+            this.version_code = version_code;
+        }
+
+        public String getVersion_name() {
+            return version_name;
+        }
+
+        public void setVersion_name(String version_name) {
+            this.version_name = version_name;
+        }
+
+        public String getApk_md5() {
+            return apk_md5;
+        }
+
+        public void setApk_md5(String apk_md5) {
+            this.apk_md5 = apk_md5;
+        }
+
+        public Object getVersion_content() {
+            return version_content;
+        }
+
+        public void setVersion_content(Object version_content) {
+            this.version_content = version_content;
+        }
+
+        public String getCreated_at() {
+            return created_at;
+        }
+
+        public void setCreated_at(String created_at) {
+            this.created_at = created_at;
+        }
+
+        public String getUpdated_at() {
+            return updated_at;
+        }
+
+        public void setUpdated_at(String updated_at) {
+            this.updated_at = updated_at;
+        }
+
+        public Object getDeleted_at() {
+            return deleted_at;
+        }
+
+        public void setDeleted_at(Object deleted_at) {
+            this.deleted_at = deleted_at;
+        }
+
+        @Override
+        public String toString() {
+            return "DataBean{" +
+                    "id=" + id +
+                    ", name='" + name + '\'' +
+                    ", apk_size=" + apk_size +
+                    ", apk_url='" + apk_url + '\'' +
+                    ", upload_type=" + upload_type +
+                    ", version_code=" + version_code +
+                    ", version_name='" + version_name + '\'' +
+                    ", apk_md5='" + apk_md5 + '\'' +
+                    ", version_content=" + version_content +
+                    ", created_at='" + created_at + '\'' +
+                    ", updated_at='" + updated_at + '\'' +
+                    ", deleted_at=" + deleted_at +
+                    '}';
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "UpdateResponseBean{" +
+                "code=" + code +
+                ", data=" + data +
+                ", msg='" + msg + '\'' +
+                '}';
+    }
+}

+ 108 - 0
core/src/main/java/com/haochuan/core/util/ELS.java

@@ -0,0 +1,108 @@
+package com.haochuan.core.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+//sharedPreference封装类
+public class ELS {
+    private static final String ELS = "EL_SharePreference";
+    private static ELS mPref = null;
+    private SharedPreferences mSharePrefer = null;
+    private Editor mEditor = null;
+
+    //日志文件名称
+    public static final String LOG_FILE_NAME = "log_file_name";
+    //上一次使用应用的日志文件上传开关
+    public static final String LAST_LOG_SWITCH = "last_log_switch";
+    //上一次日志文件的名称
+
+    public static ELS getInstance(Context mContext) {
+        if (mPref == null)
+            synchronized (ELS.class) {
+                if (mPref == null)
+                    mPref = new ELS(mContext);
+            }
+        return mPref;
+    }
+
+    private ELS(Context context) {
+        mSharePrefer = context.getSharedPreferences(ELS, Context.MODE_PRIVATE);
+        mEditor = mSharePrefer.edit();
+    }
+
+    public void saveLogInfo() {
+        mEditor.putBoolean(LAST_LOG_SWITCH, true);
+        mEditor.apply();
+    }
+
+    public void saveLongDate(String key, long value) {
+        mEditor.putLong(key, value);
+        mEditor.apply();
+    }
+
+    public long getLongDate(String key) {
+        return mSharePrefer.getLong(key, 0);
+    }
+
+    public void saveBoolData(String key, boolean value) {
+        mEditor.putBoolean(key, value);
+        mEditor.apply();
+    }
+
+    public boolean getBoolData(String key) {
+        return mSharePrefer.getBoolean(key, false);
+    }
+
+    public void saveCookieSet(String key, HashSet<String> set) {
+        mEditor.putStringSet(key, set);
+        mEditor.apply();
+    }
+
+    public Set<String> getCookieSet(String key) {
+        return mSharePrefer.getStringSet(key, new HashSet<String>());
+    }
+
+    public void saveStringData(String key, String value) {
+        mEditor.putString(key, value);
+        mEditor.apply();
+    }
+
+    public String getStringData(String key) {
+        return mSharePrefer.getString(key, "");
+    }
+
+    public void saveIntData(String key, int value) {
+        mEditor.putInt(key, value);
+        mEditor.apply();
+    }
+
+    public int getIntData(String key, int defaultValue) {
+        return mSharePrefer.getInt(key, defaultValue);
+    }
+
+    public int getIntData(String key) {
+        return mSharePrefer.getInt(key, 0);
+    }
+
+    public void saveFloatData(String key, float value) {
+        mEditor.putFloat(key, value);
+        mEditor.apply();
+    }
+
+    public float getFloatData(String key) {
+        return mSharePrefer.getFloat(key, 0);
+    }
+
+    /**
+     * 清空 SharedPreferences
+     */
+    public void clear() {
+        mEditor.clear();
+        mEditor.apply();
+    }
+
+}

+ 124 - 0
core/src/main/java/com/haochuan/core/util/FileUtil.java

@@ -0,0 +1,124 @@
+package com.haochuan.core.util;
+
+import android.util.Log;
+
+import com.haochuan.core.Logger;
+
+import java.io.File;
+import java.io.IOException;
+
+public final class FileUtil {
+    /**
+     * Create a file, if the directory of the file does not exist, create a superior directory
+     *
+     * @param filePath file path
+     * @return Returning true indicates that the file was created successfully or already exists.
+     */
+    private static String TAG = "FileUtil";
+    public static boolean createFile(String filePath) {
+        Logger.d("FileUtil,createFile(),filePath:" + filePath);
+        File file = new File(filePath);
+        if (!file.exists()) {
+            if (!file.getParentFile().exists()) {
+                boolean mkDirsResult = makeDirs(file.getParent());
+                if (!mkDirsResult) {
+                    Logger.d( "create File[" + filePath + "] fail because it's parent dir created failed");
+                    return false;
+                }
+            }
+            try {
+                boolean createFileResult = file.createNewFile();
+                Logger.d("create File[" + filePath + "] result " + createFileResult);
+                return createFileResult;
+            } catch (IOException e) {
+                Logger.d("create File[" + filePath + "]cause exception : " + e.getLocalizedMessage());
+                e.printStackTrace();
+                return false;
+            }
+        } else {
+            Logger.d("File[" + filePath + "] had exists!");
+            return true;
+        }
+    }
+
+    /**
+     * Create a directory, if the directory has a parent directory, create the same
+     *
+     * @param dirPath Directory path
+     * @return Return true to create a successful one.
+     */
+    public static boolean makeDirs(String dirPath) {
+        Logger.d("FileUtil,makeDirs(),dirPath:" + dirPath);
+        File folder = new File(dirPath);
+        if (!folder.exists()) {
+            boolean mkDirsResult = folder.mkdirs();
+            Logger.d( "makeDirs[" + dirPath + "] result " + mkDirsResult);
+            return mkDirsResult;
+        }
+        Logger.d("Dirs[" + dirPath + "] had exists!");
+        return true;
+    }
+
+    /**
+     * Delete the file, if the file is a directory and there is a subordinate directory or file, the deletion will fail
+     *
+     * @param filePath file path
+     * @return Returning true indicates that the file was deleted successfully or the file does not exist.
+     */
+    public static boolean delete(String filePath) {
+        Logger.d("FileUtil,delete(),filePath:" + filePath);
+        File file = new File(filePath);
+        if (file.exists()) {
+            boolean deleteFileResult = file.delete();
+            Logger.d("deleted File[" + filePath + "] result " + deleteFileResult);
+            return deleteFileResult;
+        }
+        Logger.d("File[" + filePath + "] is not exists");
+        return true;
+    }
+
+
+    /**
+     * Be cautious! Force the file to be deleted. If the file is a directory and there is a subordinate directory or file, it will be deleted together.
+     *
+     * @param filePath file path
+     * @return Return true to indicate that the file or all files in the file directory are deleted successfully.
+     */
+    public static boolean deleteForce(String filePath) {
+        Logger.d("FileUtil,deleteForce(),filePath:" + filePath);
+        File file = new File(filePath);
+        if (file.exists()) {
+            File[] childFiles = file.listFiles();
+            if (childFiles == null || childFiles.length == 0) {
+                boolean deleteDirsResult = file.delete();
+                Log.d(TAG, "deleted file[" + filePath + "] result " + deleteDirsResult);
+                return deleteDirsResult;
+            }
+            for (File childFile : childFiles) {
+                deleteForce(childFile.getPath());
+            }
+            boolean deleteDirsResult = file.delete();
+            Log.d(TAG, "deleted file[" + filePath + "] result " + deleteDirsResult);
+            return deleteDirsResult;
+        }
+        Log.d(TAG, "file[" + filePath + "] is not exists");
+        return true;
+    }
+
+    /**
+     * Rename file
+     *
+     * @param filePath file path
+     * @param newName  New file name
+     * @return Returning true indicates that the file was named successfully.
+     */
+    public static boolean rename(String filePath, String newName) {
+        Logger.d(String.format("FileUtil,rename('%s','%s')",filePath,newName));
+        File file = new File(filePath);
+        File newFile = new File(file.getParent(), newName);
+        boolean renameFileResult = file.renameTo(newFile);
+        Log.d(TAG, "rename File[" + filePath + "] result " + renameFileResult);
+        Log.d(TAG, "newFile Name[" + file.getName() + "] ");
+        return renameFileResult;
+    }
+}

+ 47 - 0
core/src/main/java/com/haochuan/core/util/HandlerUtil.java

@@ -0,0 +1,47 @@
+package com.haochuan.core.util;
+
+import android.os.Handler;
+import android.os.Looper;
+
+public class HandlerUtil {
+    private static final Handler HANDLER = new Handler(Looper.getMainLooper());
+
+    /**
+     * Causes the Runnable to be added to the message queue. The runnable will be run on the thread to which this handler is attached
+     *
+     * @return Returns true if the Runnable was successfully placed in to the message queue. Returns false on failure, usually because the looper processing the message queue is exiting
+     */
+    public static boolean runOnUiThread(Runnable runnable) {
+        return HANDLER.post(runnable);
+    }
+
+    /**
+     * Causes the Runnable r to be added to the message queue, to be run after the specified amount of time elapses. The runnable will be run on the thread to which this handler is attached.
+     *
+     * @return Returns true if the Runnable was successfully placed in to the message queue. Returns false on failure, usually because the looper processing the message queue is exiting. Note that a result of true does not mean the Runnable will be processed -- if the looper is quit before the delivery time of the message occurs then the message will be dropped.
+     */
+    public static boolean runOnUiThreadDelay(Runnable runnable, long delayMillis) {
+        return HANDLER.postDelayed(runnable, delayMillis);
+    }
+
+    /**
+     * Remove any pending posts of Runnable r that are in the message queue.
+     */
+    public static void removeRunable(Runnable runnable) {
+        HANDLER.removeCallbacks(runnable);
+    }
+
+    /**
+     * Remove any pending posts of messages with code 'what' that are in the message queue.
+     */
+    public static void removeMessages(int what) {
+        HANDLER.removeMessages(what);
+    }
+
+    /**
+     * Remove all callbacks and messages
+     */
+    public static void removeAll() {
+        HANDLER.removeCallbacksAndMessages(null);
+    }
+}

+ 59 - 0
core/src/main/java/com/haochuan/core/util/JSONUtil.java

@@ -0,0 +1,59 @@
+package com.haochuan.core.util;
+
+import com.haochuan.core.Logger;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class JSONUtil {
+
+    //获取Json对象String类型值
+    public static String getString(JSONObject json,String key,String defaultValue){
+        try{
+            Logger.d(String.format("JSONUtil,getString('%s','%s','%s')",
+                    json,key,defaultValue));
+            return json.has(key)?json.get(key).toString():defaultValue;
+        }catch (Exception e){
+            e.printStackTrace();
+            return defaultValue;
+        }
+    }
+
+    //获取Json对象int类型值
+    public static int getInt(JSONObject json,String key,int defaultValue){
+        try{
+            Logger.d(String.format("JSONUtil,getInt('%s','%s','%s')",
+                    json,key,defaultValue));
+            return json.has(key)?Integer.parseInt(json.get(key).toString()):defaultValue;
+        }catch (Exception e){
+            e.printStackTrace();
+            return defaultValue;
+        }
+    }
+
+    //获取Json对象
+    public static JSONObject getJsonObject(JSONObject json,String key){
+        try{
+            Logger.d(String.format("JSONUtil,getJsonObject('%s','%s')",
+                    json,key));
+            return json.has(key)?json.getJSONObject(key):null;
+        }catch (Exception e){
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    //获取Json对象
+    public static JSONArray getJsonArray(JSONObject json, String key){
+        try{
+            Logger.d(String.format("JSONUtil,getJsonArray('%s','%s')",
+                    json,key));
+            return json.has(key)?json.getJSONArray(key):null;
+        }catch (Exception e){
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+}

+ 28 - 0
core/src/main/java/com/haochuan/core/util/JsUtil.java

@@ -0,0 +1,28 @@
+package com.haochuan.core.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Build;
+import android.webkit.WebView;
+
+import com.haochuan.core.Logger;
+
+public class JsUtil {
+    /**
+     * 调用js事件
+     */
+    public static void evaluateJavascript(Context context, WebView webView, String script) {
+        Logger.d(String.format("JsUtil,evaluateJavascript('%s')", script));
+        Activity activity = (Activity)context;
+        if (webView == null) {
+            return;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            activity.runOnUiThread(()-> webView.evaluateJavascript(script, value -> {
+                //此处为 js 返回的结果
+            }));
+        } else {
+            activity.runOnUiThread(()-> webView.loadUrl(script));
+        }
+    }
+}

+ 9 - 0
core/src/main/java/com/haochuan/core/util/Judge.java

@@ -0,0 +1,9 @@
+package com.haochuan.core.util;
+
+public class Judge {
+    //判断当前是不是纯IP地址
+    public static boolean isPureIp(String url){
+        url = url.replace("http://","");
+        return !url.contains("/");
+    }
+}

+ 348 - 0
core/src/main/java/com/haochuan/core/util/MacUtil.java

@@ -0,0 +1,348 @@
+package com.haochuan.core.util;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+public final class MacUtil {
+
+    public static String getMac(Context context) {
+        String strMac;
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            Log.d("=====", "6.0以下");
+            strMac = getLocalMacAddressFromWifiInfo(context);
+        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            Log.d("=====", "6.0以上7.0以下");
+            strMac = getMacAddress(context);
+        } else {
+            Log.d("=====", "7.0以上");
+            if (!TextUtils.isEmpty(getMacAddress())) {
+                Log.d("=====", "7.0以上1");
+                strMac = getMacAddress();
+            } else if (!TextUtils.isEmpty(getMachineHardwareAddress())) {
+                Log.d("=====", "7.0以上2");
+                strMac = getMachineHardwareAddress();
+            } else {
+                Log.d("=====", "7.0以上3");
+                strMac = getLocalMacAddressFromBusybox();
+            }
+        }
+
+        if (TextUtils.isEmpty(strMac)) {
+            strMac = "02:00:00:00:00:00";
+        }
+
+        return strMac;
+    }
+
+
+    /**
+     * 根据wifi信息获取本地mac
+     */
+    @SuppressLint("HardwareIds")
+    private static String getLocalMacAddressFromWifiInfo(Context context) {
+        try{
+            WifiManager wifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+            WifiInfo info = null;
+            if (wifi != null) {
+                info = wifi.getConnectionInfo();
+            }
+            String mac = null;
+            if (info != null) {
+                mac = info.getMacAddress();
+            }
+            return mac;
+        }catch (Exception e){
+            e.printStackTrace();
+            return "";
+        }
+
+    }
+
+    /**
+     * android 6.0及以上、7.0以下 获取mac地址
+     */
+    private static String getMacAddress(Context context) {
+        // 如果是6.0以下,直接通过wifimanager获取
+        try{
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+                String macAddress0 = getMacAddress0(context);
+                if (!TextUtils.isEmpty(macAddress0)) {
+                    return macAddress0;
+                }
+            }
+            return getMacAddress2();
+        }catch (Exception e){
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    private static String getMacAddress2() {
+        String str = "";
+        String macSerial = "";
+        try {
+            Process pp = Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address");
+            InputStreamReader ir = new InputStreamReader(pp.getInputStream());
+            LineNumberReader input = new LineNumberReader(ir);
+            for (; null != str; ) {
+                str = input.readLine();
+                if (str != null) {
+                    macSerial = str.trim();// 去空格
+                    break;
+                }
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            Log.e("----->" + "NetInfoManager", "getMacAddress:" + ex.toString());
+        }
+        if (TextUtils.isEmpty(macSerial)) {
+            try {
+                return loadFileAsString("/sys/class/net/eth0/address").toUpperCase().substring(0, 17);
+            } catch (Exception e) {
+                e.printStackTrace();
+                Log.e("----->" + "NetInfoManager", "getMacAddress:" + e.toString());
+            }
+
+        }
+        return macSerial;
+    }
+
+    @SuppressLint("HardwareIds")
+    private static String getMacAddress0(Context context) {
+        if (isAccessWifiStateAuthorized(context)) {
+            WifiManager wifiMgr = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+            WifiInfo wifiInfo = null;
+            try {
+                if (wifiMgr != null) {
+                    wifiInfo = wifiMgr.getConnectionInfo();
+                }
+                if (wifiInfo != null) {
+                    return wifiInfo.getMacAddress();
+                }
+            } catch (Exception e) {
+                Log.e("----->" + "NetInfoManager", "getMacAddress0:" + e.toString());
+            }
+        }
+        return "";
+
+    }
+
+    /**
+     * Check whether accessing wifi state is permitted
+     */
+    private static boolean isAccessWifiStateAuthorized(Context context) {
+        if (PackageManager.PERMISSION_GRANTED == context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_WIFI_STATE)) {
+            Log.d("----->" + "NetInfoManager", "isAccessWifiStateAuthorized:" + "access wifi state is enabled");
+            return true;
+        } else
+            return false;
+    }
+
+    private static String loadFileAsString(String fileName) throws Exception {
+        FileReader reader = new FileReader(fileName);
+        String text = loadReaderAsString(reader);
+        reader.close();
+        return text;
+    }
+
+    private static String loadReaderAsString(Reader reader) throws Exception {
+        StringBuilder builder = new StringBuilder();
+        char[] buffer = new char[4096];
+        int readLength = reader.read(buffer);
+        while (readLength >= 0) {
+            builder.append(buffer, 0, readLength);
+            readLength = reader.read(buffer);
+        }
+        return builder.toString();
+    }
+
+    /**
+     * 根据IP地址获取MAC地址
+     */
+    private static String getMacAddress() {
+        String strMacAddr = null;
+        try {
+            // 获得IpD地址
+            InetAddress ip = getLocalInetAddress();
+            byte[] b = NetworkInterface.getByInetAddress(ip).getHardwareAddress();
+            StringBuilder buffer = new StringBuilder();
+            for (int i = 0; i < b.length; i++) {
+                if (i != 0) {
+                    buffer.append(':');
+                }
+                String str = Integer.toHexString(b[i] & 0xFF);
+                buffer.append(str.length() == 1 ? 0 + str : str);
+            }
+            strMacAddr = buffer.toString().toUpperCase();
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.e("====", e.getMessage());
+        }
+        return strMacAddr;
+    }
+
+    /**
+     * 获取移动设备本地IP
+     */
+    private static InetAddress getLocalInetAddress() {
+        InetAddress ip = null;
+        try {
+            // 列举
+            Enumeration<NetworkInterface> en_netInterface = NetworkInterface
+                    .getNetworkInterfaces();
+            while (en_netInterface.hasMoreElements()) {// 是否还有元素
+                NetworkInterface ni = en_netInterface.nextElement();// 得到下一个元素
+                Enumeration<InetAddress> en_ip = ni.getInetAddresses();// 得到一个ip地址的列举
+                while (en_ip.hasMoreElements()) {
+                    ip = en_ip.nextElement();
+                    if (!ip.isLoopbackAddress() && !ip.getHostAddress().contains(":")) {
+                        break;
+                    } else {
+                        ip = null;
+                    }
+                }
+
+                if (ip != null) {
+                    break;
+                }
+            }
+        } catch (SocketException e) {
+
+            e.printStackTrace();
+        }
+        return ip;
+    }
+
+    /**
+     * 获取本地IP
+     */
+    private static String getLocalIpAddress() {
+        try {
+            for (Enumeration<NetworkInterface> en = NetworkInterface
+                    .getNetworkInterfaces(); en.hasMoreElements(); ) {
+                NetworkInterface intf = en.nextElement();
+                for (Enumeration<InetAddress> enumIpAddr = intf
+                        .getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+                    InetAddress inetAddress = enumIpAddr.nextElement();
+                    if (!inetAddress.isLoopbackAddress()) {
+                        return inetAddress.getHostAddress();
+                    }
+                }
+            }
+        } catch (SocketException ex) {
+            ex.printStackTrace();
+        }
+        return null;
+    }
+
+    // android 7.0及以上 (2)扫描各个网络接口获取mac地址
+
+    /**
+     * 获取设备HardwareAddress地址
+     */
+    public static String getMachineHardwareAddress() {
+        Enumeration<NetworkInterface> interfaces = null;
+        try {
+            interfaces = NetworkInterface.getNetworkInterfaces();
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        String hardWareAddress = null;
+        NetworkInterface iF = null;
+        if (interfaces == null) {
+            return null;
+        }
+        while (interfaces.hasMoreElements()) {
+            iF = interfaces.nextElement();
+            try {
+                hardWareAddress = bytesToString(iF.getHardwareAddress());
+                if (hardWareAddress != null)
+                    break;
+            } catch (SocketException e) {
+                e.printStackTrace();
+            }
+        }
+        return hardWareAddress;
+    }
+
+    /***
+     * byte转为String
+     */
+    private static String bytesToString(byte[] bytes) {
+        if (bytes == null || bytes.length == 0) {
+            return null;
+        }
+        StringBuilder buf = new StringBuilder();
+        for (byte b : bytes) {
+            buf.append(String.format("%02X:", b));
+        }
+        if (buf.length() > 0) {
+            buf.deleteCharAt(buf.length() - 1);
+        }
+        return buf.toString();
+    }
+
+    // android 7.0及以上 (3)通过busybox获取本地存储的mac地址
+
+    /**
+     * 根据busybox获取本地Mac
+     *
+     * @return
+     */
+    private static String getLocalMacAddressFromBusybox() {
+        String result = "";
+        String Mac = "";
+        result = callCmd("busybox ifconfig", "HWaddr");
+        // 如果返回的result == null,则说明网络不可取
+        if (result == null) {
+            return "网络异常";
+        }
+        // 对该行数据进行解析
+        // 例如:eth0 Link encap:Ethernet HWaddr 00:16:E8:3E:DF:67
+        if (result.length() > 0 && result.contains("HWaddr")) {
+            Mac = result.substring(result.indexOf("HWaddr") + 6,
+                    result.length() - 1);
+            result = Mac;
+        }
+        return result;
+    }
+
+    private static String callCmd(String cmd, String filter) {
+        String result = "";
+        String line = "";
+        try {
+            Process proc = Runtime.getRuntime().exec(cmd);
+            InputStreamReader is = new InputStreamReader(proc.getInputStream());
+            BufferedReader br = new BufferedReader(is);
+
+            while ((line = br.readLine()) != null
+                    && !line.contains(filter)) {
+                result += line;
+            }
+
+            result = line;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+}

+ 28 - 0
core/src/main/java/com/haochuan/core/util/MathUtil.java

@@ -0,0 +1,28 @@
+package com.haochuan.core.util;
+
+import java.util.regex.Pattern;
+
+public class MathUtil {
+
+    public static boolean isDigitsOnly(String str) {
+        return isInteger(str) || isDouble(str);
+    }
+
+    // 判断整数(int)
+    private static boolean isInteger(String str) {
+        if (null == str || "".equals(str)) {
+            return false;
+        }
+        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
+        return pattern.matcher(str).matches();
+    }
+
+    //判断浮点数(double和float)
+    private static boolean isDouble(String str) {
+        if (null == str || "".equals(str)) {
+            return false;
+        }
+        Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
+        return pattern.matcher(str).matches();
+    }
+}

+ 45 - 0
core/src/main/java/com/haochuan/core/util/Md5Util.java

@@ -0,0 +1,45 @@
+package com.haochuan.core.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.MessageDigest;
+
+public class Md5Util {
+    public static String getFileMD5(File file) {
+        if (!file.isFile()) {
+            return null;
+        }
+        MessageDigest digest = null;
+        FileInputStream in = null;
+        byte buffer[] = new byte[1024];
+        int len;
+        try {
+            digest = MessageDigest.getInstance("MD5");
+            in = new FileInputStream(file);
+            while ((len = in.read(buffer, 0, 1024)) != -1) {
+                digest.update(buffer, 0, len);
+            }
+            in.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+        return bytesToHexString(digest.digest());
+    }
+
+    private static String bytesToHexString(byte[] src) {
+        StringBuilder stringBuilder = new StringBuilder("");
+        if (src == null || src.length <= 0) {
+            return null;
+        }
+        for (int i = 0; i < src.length; i++) {
+            int v = src[i] & 0xFF;
+            String hv = Integer.toHexString(v);
+            if (hv.length() < 2) {
+                stringBuilder.append(0);
+            }
+            stringBuilder.append(hv);
+        }
+        return stringBuilder.toString();
+    }
+}

+ 10 - 0
core/src/main/java/com/haochuan/core/util/MediaStatusCode.java

@@ -0,0 +1,10 @@
+package com.haochuan.core.util;
+
+public class MediaStatusCode {
+    public final static int PREPARE = 1;                    //准备
+    public final static int PLAY = 2;                       //播放
+    public final static int PAUSE = 3;                      //暂停
+    public final static int BUFFER = 4;                    //缓冲
+    public final static int COMPLETE = 5;                 //播放完成
+    public final static int STOP = 6;                     //停止未播放
+}

+ 9 - 0
core/src/main/java/com/haochuan/core/util/MessageCode.java

@@ -0,0 +1,9 @@
+package com.haochuan.core.util;
+
+public class MessageCode {
+    public final static int EXCEPTION_ERROR = -1;       //异常抛出
+    public final static int SUCCESS = 6;                //成功
+    public final static int PARAM_ERROR = 7;            //参数错误
+    public final static int PLAYER_OBJ_NULL = 8;        //播放器对象为空
+    public final static int PLAYER_NO_INIT = 9;        //播放器对象未初始化
+}

+ 31 - 0
core/src/main/java/com/haochuan/core/util/RegexUtil.java

@@ -0,0 +1,31 @@
+package com.haochuan.core.util;
+
+import com.haochuan.core.Logger;
+
+import java.util.regex.Pattern;
+
+public class RegexUtil {
+    /*
+     * 判断字符串是否为正确的url
+     * */
+    public static boolean isUrl(String url){
+        Logger.d("RegexUtil isUrl(),url:" + url);
+        if (null == url || "".equals(url)) {
+            return false;
+        }
+        Pattern pattern = Pattern.compile("(http?|https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]");
+        return pattern.matcher(url).matches();
+    }
+
+    /*
+     * 判断字符串是否为正确的包名或者类名
+     * */
+    public static boolean isPackageName(String name){
+        Logger.d("RegexUtil isPackageName(),name:" + name);
+        if (null == name || "".equals(name)) {
+            return false;
+        }
+        Pattern pattern = Pattern.compile("^([a-zA-Z_][a-zA-Z0-9_]*)+([.][a-zA-Z_][a-zA-Z0-9_]*)+$");
+        return pattern.matcher(name).matches();
+    }
+}

+ 23 - 0
core/src/main/java/com/haochuan/core/util/ScreenSnap.java

@@ -0,0 +1,23 @@
+package com.haochuan.core.util;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.view.WindowManager;
+
+public class ScreenSnap {
+
+    public static int getScreenWidth(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Point point = new Point();
+        wm.getDefaultDisplay().getRealSize(point);
+        return point.x;
+    }
+
+
+    public static int getScreenHeight(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Point point = new Point();
+        wm.getDefaultDisplay().getRealSize(point);
+        return point.y;
+    }
+}

+ 321 - 0
core/src/main/java/com/haochuan/core/util/ToolsUtil.java

@@ -0,0 +1,321 @@
+package com.haochuan.core.util;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Base64;
+
+import com.haochuan.core.Logger;
+import com.haochuan.core.http.MyRequest;
+import com.yanzhenjie.nohttp.NoHttp;
+import com.yanzhenjie.nohttp.RequestMethod;
+import com.yanzhenjie.nohttp.rest.OnResponseListener;
+import com.yanzhenjie.nohttp.rest.Request;
+import com.yanzhenjie.nohttp.rest.StringRequest;
+
+import org.json.JSONObject;
+
+import java.io.File;
+import java.net.URLEncoder;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+public class ToolsUtil {
+
+    public boolean checkSubAppInstalled(Context context, String pkgName) {
+        if (context == null) {
+            Logger.e("checkSubAppInstalled() context is null,不能执行");
+            return false;
+        }
+        if (pkgName == null || pkgName.isEmpty()) {
+            return false;
+        }
+        PackageInfo packageInfo;
+        try {
+            packageInfo = context.getPackageManager().getPackageInfo(pkgName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            packageInfo = null;
+            e.printStackTrace();
+        }
+        return packageInfo != null;
+    }
+
+    public static void installApk(Context context, String filePath) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//4.0以上系统弹出安装成功打开界面
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
+        context.startActivity(intent);
+    }
+
+    public void uninstall(Context context, String pkgName) {
+        Uri uri = Uri.fromParts("package", pkgName, null);
+        Intent intent = new Intent(Intent.ACTION_DELETE, uri);
+        context.startActivity(intent);
+    }
+
+    /*
+     * 获得intent参数集
+     * */
+    public String getIntentJson(Context context) {
+        try {
+            Activity activity = (Activity) context;
+            Intent intent = activity.getIntent();
+            if (intent != null) {
+                Bundle bundle = intent.getExtras();
+                if (bundle != null) {
+                    Set<String> keySet = bundle.keySet();
+                    JSONObject intentJson = new JSONObject();
+                    for (String key : keySet) {
+                        Object bundleValue = bundle.get(key);
+                        intentJson.put(key, bundleValue);
+                    }
+                    String getIntentJson = intentJson.toString();
+                    Logger.d("getIntentJson:" + getIntentJson);
+                    return getIntentJson;
+                } else {
+                    Logger.d("getIntentJson, bundle is null");
+                    return "{}";
+                }
+            } else {
+                Logger.d("getIntentJson, intent is null");
+                return "{}";
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return "{}";
+        }
+    }
+
+    /*
+     *js 通过apk客户端访问网络接口
+     *@param paramJson 请求参数集,格式为json字符串
+     *@param headJson 请求头部集,格式为json字符串
+     *@param method 请求方法,1,get;2,post
+     *@param ignoreResult 是否忽略结果,true,忽略;false,不忽略.
+     *@param tag 透传参数,将在结果回调中一并返回,主要区别多个并发请求
+     * */
+    public void clientWebRequest(String url, int method, String paramJson,
+                                 String headJson, boolean ignoreResult,
+                                 String tag, IResponseListener listener) {
+        if (url == null || paramJson == null || headJson == null || tag == null) {
+            Logger.w(String.format("参数不能为null,url:%s;paramJson:%s;headJson:%s;" +
+                    "method:%s;ignoreResult:%s;tag:%s", url, paramJson, headJson, method, ignoreResult ? "忽略结果" : "不忽略结果", tag));
+            return;
+        }
+        try {
+            RequestMethod requestMethod;
+            switch (method) {
+                case 1:
+                    requestMethod = RequestMethod.GET;
+                    break;
+                case 2:
+                    requestMethod = RequestMethod.POST;
+                    break;
+                default:
+                    requestMethod = RequestMethod.GET;
+                    break;
+            }
+            if (url.isEmpty()) {
+                Logger.w("clientWebRequest,url 不能为空");
+                return;
+            }
+            final StringRequest request = new StringRequest(url, requestMethod);
+            if (!paramJson.isEmpty()) {
+                JSONObject jsonParams = new JSONObject(paramJson);
+                Iterator<String> paramIterators = jsonParams.keys();
+                while (paramIterators.hasNext()) {
+                    String paramKey = paramIterators.next();
+                    String paramValue = jsonParams.getString(paramKey);
+                    request.add(paramKey, paramValue);
+                }
+            }
+            if (!headJson.isEmpty()) {
+                JSONObject headParams = new JSONObject(headJson);
+                Iterator<String> headIterators = headParams.keys();
+                while (headIterators.hasNext()) {
+                    String paramKey = headIterators.next();
+                    String paramValue = headParams.getString(paramKey);
+                    request.addHeader(paramKey, paramValue);
+                }
+            }
+
+            NoHttp.newRequestQueue().add(100, request, new OnResponseListener<String>() {
+                @Override
+                public void onStart(int what) {
+                    Logger.d("clientWebRequest,开始请求");
+                }
+
+                @Override
+                public void onSucceed(int what, com.yanzhenjie.nohttp.rest.Response<String> response) {
+                    String data = response.get();
+                    Logger.d("clientWebRequest,请求认证成功!");
+                    String base64Response = Base64.encodeToString(data.getBytes(), Base64.NO_WRAP);
+                    base64Response = base64Response.replace("\n", "");
+                    if (ignoreResult) {
+                        listener.OnResponse(0, "{}", tag);
+                    } else {
+                        listener.OnResponse(0, base64Response, tag);
+                    }
+                }
+
+                @Override
+                public void onFailed(int what, com.yanzhenjie.nohttp.rest.Response<String> response) {
+                    Logger.w("clientWebRequest,请求认证失败:" + what);
+                    listener.OnResponse(-1, "{}", tag);
+                }
+
+                @Override
+                public void onFinish(int what) {
+                    Logger.d("clientWebRequest,请求认证结束");
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    public void clientWebRequest(String url, int method,
+                                 String contentType, String headJson,
+                                 String paramsBody, boolean ignoreResult,
+                                 String tag, IResponseListener listener) {
+        try {
+            Logger.d(String.format("webRequest,url:%s,method:%s,contentType:%s," +
+                            "headJson:%s,paramsBody:%s,tag:%s,ignoreResult%s,",
+                    url, method, contentType, headJson, paramsBody, tag, ignoreResult ? "忽略" : "不忽略"));
+            RequestMethod requestMethod;
+            switch (method) {
+                case 1:
+                    requestMethod = RequestMethod.GET;
+                    break;
+                case 2:
+                    requestMethod = RequestMethod.POST;
+                    break;
+                default:
+                    requestMethod = RequestMethod.GET;
+                    break;
+            }
+            Request<String> request = new MyRequest(url, requestMethod);
+
+            //添加头部
+            if (!TextUtils.isEmpty(headJson)) {
+                JSONObject headParams = new JSONObject(headJson);
+                Iterator<String> headIterators = headParams.keys();
+                while (headIterators.hasNext()) {
+                    String paramKey = headIterators.next();
+                    String paramValue = headParams.getString(paramKey);
+                    request.addHeader(paramKey, paramValue);
+                }
+            }
+
+            if (TextUtils.equals(contentType, "application/x-www-form-urlencoded")) {
+                JSONObject jsonObject = new JSONObject(paramsBody);
+                Iterator iterator = jsonObject.keys();
+                StringBuilder sb = new StringBuilder();
+                int i = 0;
+                while (iterator.hasNext()) {
+                    if (i > 0) {
+                        sb.append("&");
+                    }
+                    String key = (String) iterator.next();
+                    String value = jsonObject.getString(key);
+                    value = URLEncoder.encode(value, "UTF-8");
+                    sb.append(String.format("%s=%s", key, value));
+                    i++;
+                }
+                paramsBody = sb.toString();
+            }
+
+            if (method == 2 && !TextUtils.isEmpty(paramsBody)) {
+                request.setDefineRequestBody(paramsBody, contentType);
+            }
+            NoHttp.newRequestQueue().add(100, request, new OnResponseListener<String>() {
+                @Override
+                public void onStart(int what) {
+                    Logger.d("clientWebRequest,开始请求");
+                }
+
+                @Override
+                public void onSucceed(int what, com.yanzhenjie.nohttp.rest.Response<String> response) {
+                    String data = response.get();
+                    String result = ignoreResult ? "{}" : data.replace("\"", "\\\"");
+                    result = result.replace("\n", "");
+                    String base64Response = Base64.encodeToString(data.getBytes(), Base64.NO_WRAP);
+                    base64Response = base64Response.replace("\n", "");
+                    if (ignoreResult) {
+                        listener.OnResponse(0, "{}", tag);
+                    } else {
+                        listener.OnResponse(0, base64Response, tag);
+                    }
+                }
+
+                @Override
+                public void onFailed(int what, com.yanzhenjie.nohttp.rest.Response<String> response) {
+                    Logger.w("clientWebRequest,请求认证失败:" + what);
+                }
+
+                @Override
+                public void onFinish(int what) {
+                    Logger.d("clientWebRequest,请求认证结束");
+                }
+            });
+        } catch (Exception e) {
+            Logger.e("JS传递的请求发生未知异常");
+            e.printStackTrace();
+        }
+    }
+
+    public static int getVersionCode(Context mContext) {
+        int versionName = 0;
+        PackageManager manager = mContext.getPackageManager();
+        try {
+            PackageInfo info = manager.getPackageInfo(mContext.getPackageName(), 0);
+            versionName = info.versionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return versionName;
+    }
+
+    //获取剩余可用内存
+    public static long getAvailableMemory(Context mContext) {
+        ActivityManager mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+        mActivityManager.getMemoryInfo(mi);
+        //返回的内存单位为字节
+        return mi.availMem;
+    }
+
+    //获取当前应用程序包名
+    public static String getAppProcessName(Context context) {
+        //因为派生出去的项目都会修改对应的ApplicationId,所以用BuildConfig获取包名是不准的
+        //当前应用pid
+        int pid = android.os.Process.myPid();
+        //任务管理类
+        ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        //遍历所有应用
+        List<ActivityManager.RunningAppProcessInfo> infos = manager.getRunningAppProcesses();
+        for (ActivityManager.RunningAppProcessInfo info : infos) {
+            if (info.pid == pid)//得到当前应用
+                return info.processName;//返回包名
+        }
+        return "";
+    }
+
+
+    /*
+     * clientWebRequest 结果response接口
+     * */
+    public interface IResponseListener {
+        public void OnResponse(int code, String response, String tag);
+    }
+}

+ 0 - 0
core/src/main/res/drawable-xxhdpi/icon_close.png


部分文件因文件數量過多而無法顯示