소스 검색

first commit

JaycePC 1 주 전
커밋
194760df83
100개의 변경된 파일2118개의 추가작업 그리고 0개의 파일을 삭제
  1. 77 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 77 0
      app/build.gradle
  4. BIN
      app/lib/sdkapi.jar
  5. 21 0
      app/proguard-rules.pro
  6. 26 0
      app/src/androidTest/java/xn/huaxue/update/ExampleInstrumentedTest.java
  7. 94 0
      app/src/main/AndroidManifest.xml
  8. 369 0
      app/src/main/java/xn/huaxue/update/MainActivity.java
  9. 76 0
      app/src/main/java/xn/huaxue/update/PingResult.java
  10. 81 0
      app/src/main/java/xn/huaxue/update/SettingActivity.java
  11. 113 0
      app/src/main/java/xn/huaxue/update/Tool.java
  12. 207 0
      app/src/main/java/xn/huaxue/update/UpdateTool.java
  13. 64 0
      app/src/main/java/xn/huaxue/update/app/App.java
  14. 33 0
      app/src/main/java/xn/huaxue/update/constant/AppConstant.java
  15. 17 0
      app/src/main/java/xn/huaxue/update/evnet/UpdateUiEvent.java
  16. 77 0
      app/src/main/java/xn/huaxue/update/http/HttpTool.java
  17. 61 0
      app/src/main/java/xn/huaxue/update/http/LogInterceptor.java
  18. 42 0
      app/src/main/java/xn/huaxue/update/http/OkHttpUtils.java
  19. 86 0
      app/src/main/java/xn/huaxue/update/http/bean/AllLogInterceptor.java
  20. 145 0
      app/src/main/java/xn/huaxue/update/http/bean/request/DeviceInfo.java
  21. 126 0
      app/src/main/java/xn/huaxue/update/http/bean/response/UpdateTask.java
  22. 19 0
      app/src/main/java/xn/huaxue/update/receiver/BootReceiver.java
  23. 26 0
      app/src/main/java/xn/huaxue/update/receiver/ProcessReceiver.java
  24. 68 0
      app/src/main/java/xn/huaxue/update/receiver/TimeTickReceiver.java
  25. BIN
      app/src/main/res/drawable-hdpi/ic_admin.png
  26. BIN
      app/src/main/res/drawable-hdpi/ic_back.png
  27. BIN
      app/src/main/res/drawable-hdpi/ic_celsius.png
  28. BIN
      app/src/main/res/drawable-hdpi/ic_cpu.png
  29. BIN
      app/src/main/res/drawable-hdpi/ic_eixt.png
  30. BIN
      app/src/main/res/drawable-hdpi/ic_et_err.png
  31. BIN
      app/src/main/res/drawable-hdpi/ic_folder.png
  32. BIN
      app/src/main/res/drawable-hdpi/ic_http.png
  33. BIN
      app/src/main/res/drawable-hdpi/ic_ip.png
  34. BIN
      app/src/main/res/drawable-hdpi/ic_mqtt.png
  35. BIN
      app/src/main/res/drawable-hdpi/ic_mqtt_account.png
  36. BIN
      app/src/main/res/drawable-hdpi/ic_mqtt_password.png
  37. BIN
      app/src/main/res/drawable-hdpi/ic_reboot.png
  38. BIN
      app/src/main/res/drawable-hdpi/ic_save.png
  39. BIN
      app/src/main/res/drawable-hdpi/ic_settings.png
  40. BIN
      app/src/main/res/drawable-hdpi/ic_sn.png
  41. BIN
      app/src/main/res/drawable-hdpi/ic_time.png
  42. BIN
      app/src/main/res/drawable-hdpi/ic_ver.png
  43. BIN
      app/src/main/res/drawable-mdpi/ic_admin.png
  44. BIN
      app/src/main/res/drawable-mdpi/ic_back.png
  45. BIN
      app/src/main/res/drawable-mdpi/ic_celsius.png
  46. BIN
      app/src/main/res/drawable-mdpi/ic_cpu.png
  47. BIN
      app/src/main/res/drawable-mdpi/ic_eixt.png
  48. BIN
      app/src/main/res/drawable-mdpi/ic_et_err.png
  49. BIN
      app/src/main/res/drawable-mdpi/ic_folder.png
  50. BIN
      app/src/main/res/drawable-mdpi/ic_http.png
  51. BIN
      app/src/main/res/drawable-mdpi/ic_ip.png
  52. BIN
      app/src/main/res/drawable-mdpi/ic_mqtt.png
  53. BIN
      app/src/main/res/drawable-mdpi/ic_mqtt_account.png
  54. BIN
      app/src/main/res/drawable-mdpi/ic_mqtt_password.png
  55. BIN
      app/src/main/res/drawable-mdpi/ic_reboot.png
  56. BIN
      app/src/main/res/drawable-mdpi/ic_save.png
  57. BIN
      app/src/main/res/drawable-mdpi/ic_settings.png
  58. BIN
      app/src/main/res/drawable-mdpi/ic_sn.png
  59. BIN
      app/src/main/res/drawable-mdpi/ic_time.png
  60. BIN
      app/src/main/res/drawable-mdpi/ic_ver.png
  61. BIN
      app/src/main/res/drawable-xhdpi/ic_admin.png
  62. BIN
      app/src/main/res/drawable-xhdpi/ic_back.png
  63. BIN
      app/src/main/res/drawable-xhdpi/ic_celsius.png
  64. BIN
      app/src/main/res/drawable-xhdpi/ic_cpu.png
  65. BIN
      app/src/main/res/drawable-xhdpi/ic_eixt.png
  66. BIN
      app/src/main/res/drawable-xhdpi/ic_et_err.png
  67. BIN
      app/src/main/res/drawable-xhdpi/ic_folder.png
  68. BIN
      app/src/main/res/drawable-xhdpi/ic_http.png
  69. BIN
      app/src/main/res/drawable-xhdpi/ic_ip.png
  70. BIN
      app/src/main/res/drawable-xhdpi/ic_mqtt.png
  71. BIN
      app/src/main/res/drawable-xhdpi/ic_mqtt_account.png
  72. BIN
      app/src/main/res/drawable-xhdpi/ic_mqtt_password.png
  73. BIN
      app/src/main/res/drawable-xhdpi/ic_reboot.png
  74. BIN
      app/src/main/res/drawable-xhdpi/ic_save.png
  75. BIN
      app/src/main/res/drawable-xhdpi/ic_settings.png
  76. BIN
      app/src/main/res/drawable-xhdpi/ic_sn.png
  77. BIN
      app/src/main/res/drawable-xhdpi/ic_time.png
  78. BIN
      app/src/main/res/drawable-xhdpi/ic_ver.png
  79. BIN
      app/src/main/res/drawable-xxhdpi/ic_admin.png
  80. BIN
      app/src/main/res/drawable-xxhdpi/ic_back.png
  81. BIN
      app/src/main/res/drawable-xxhdpi/ic_celsius.png
  82. BIN
      app/src/main/res/drawable-xxhdpi/ic_cpu.png
  83. BIN
      app/src/main/res/drawable-xxhdpi/ic_eixt.png
  84. BIN
      app/src/main/res/drawable-xxhdpi/ic_et_err.png
  85. BIN
      app/src/main/res/drawable-xxhdpi/ic_folder.png
  86. BIN
      app/src/main/res/drawable-xxhdpi/ic_http.png
  87. BIN
      app/src/main/res/drawable-xxhdpi/ic_ip.png
  88. BIN
      app/src/main/res/drawable-xxhdpi/ic_mqtt.png
  89. BIN
      app/src/main/res/drawable-xxhdpi/ic_mqtt_account.png
  90. BIN
      app/src/main/res/drawable-xxhdpi/ic_mqtt_password.png
  91. BIN
      app/src/main/res/drawable-xxhdpi/ic_reboot.png
  92. BIN
      app/src/main/res/drawable-xxhdpi/ic_save.png
  93. BIN
      app/src/main/res/drawable-xxhdpi/ic_settings.png
  94. BIN
      app/src/main/res/drawable-xxhdpi/ic_sn.png
  95. BIN
      app/src/main/res/drawable-xxhdpi/ic_time.png
  96. BIN
      app/src/main/res/drawable-xxhdpi/ic_ver.png
  97. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  98. 30 0
      app/src/main/res/drawable/ic_launcher_foreground.xml
  99. 12 0
      app/src/main/res/drawable/shape_rect.xml
  100. 0 0
      app/src/main/res/layout/activity_main.xml

+ 77 - 0
.gitignore

@@ -0,0 +1,77 @@
+# Built application files
+*.apk
+*.ap_
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+build/
+
+# Gradle files
+.gradle/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/libraries
+
+# Keystore files
+*.jks
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Google Services (gcm, firebase)
+google-services.json
+
+# Freeline
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+.svn/
+
+# lint reports
+lint-results*.xml
+lint-results*.html
+
+# IDEA
+*.iws
+*.ipr
+*.idea/
+
+# Mac OS
+.DS_Store
+
+# Windows thumbnail db
+Thumbs.db
+/app/release/*

+ 1 - 0
app/.gitignore

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

+ 77 - 0
app/build.gradle

@@ -0,0 +1,77 @@
+plugins {
+    alias(libs.plugins.android.application)
+}
+
+android {
+    namespace 'xn.huaxue.update'
+    compileSdk 34
+
+    defaultConfig {
+        applicationId "xn.huaxue.update"
+        minSdk 24
+        //noinspection ExpiredTargetSdkVersion
+        targetSdk 28
+        versionCode 1
+        versionName "2.2"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_11
+        targetCompatibility JavaVersion.VERSION_11
+    }
+
+    buildFeatures {
+        viewBinding true
+    }
+
+    applicationVariants.configureEach { variant ->
+        variant.outputs.configureEach { output ->
+            def formattedDate = new Date().format('yyyyMMddHHmm')
+            outputFileName = "xn_huaxue_update_${variant.versionName}_${formattedDate}.apk"
+        }
+    }
+}
+
+dependencies {
+    implementation files('lib/sdkapi.jar')
+    implementation libs.appcompat
+    implementation libs.material
+    implementation libs.activity
+    implementation libs.constraintlayout
+    testImplementation libs.junit
+    androidTestImplementation libs.ext.junit
+    androidTestImplementation libs.espresso.core
+
+    //noinspection UseTomlInstead
+    implementation 'com.blankj:utilcodex:1.31.1'
+    //noinspection UseTomlInstead
+    implementation 'com.github.getActivity:XXPermissions:23.0'
+    //noinspection GradleDependency,UseTomlInstead
+    implementation 'com.squareup.okhttp3:okhttp:3.12.13'
+    //noinspection UseTomlInstead
+    implementation 'com.google.android.flexbox:flexbox:3.0.0'
+    //noinspection UseTomlInstead
+    implementation 'com.github.li-xiaojun:XPopup:2.10.0'
+    //noinspection UseTomlInstead
+    implementation 'com.github.li-xiaojun:XPopupExt:1.0.1'
+    //noinspection UseTomlInstead
+    implementation 'com.github.AnyChart:AnyChart-Android:1.1.5'
+    //noinspection UseTomlInstead
+    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
+    //noinspection UseTomlInstead
+    implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
+    //noinspection UseTomlInstead,GradleDependency
+    implementation "androidx.work:work-runtime:2.9.1"
+    //noinspection UseTomlInstead
+    implementation("org.greenrobot:eventbus:3.3.1")
+    //noinspection UseTomlInstead
+    implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
+}

BIN
app/lib/sdkapi.jar


+ 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

+ 26 - 0
app/src/androidTest/java/xn/huaxue/update/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package xn.huaxue.update;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.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.getInstrumentation().getTargetContext();
+        assertEquals("xn.huaxue.update", appContext.getPackageName());
+    }
+}

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

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.NOTIFICATION_SERVICE" />
+    <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.BROADCAST_PACKAGE_ADDED" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+    <queries>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:mimeType="application/vnd.android.package-archive" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.MAIN" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+        </intent>
+    </queries>
+
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
+    <permission
+        android:name="xn.huaxue.update.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
+        android:protectionLevel="signature" />
+    <uses-permission android:name="xn.huaxue.update.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" />
+
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/network_security_config"
+        android:requestLegacyExternalStorage="true"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.Xn_huaxue_update"
+        tools:targetApi="31">
+        <receiver
+            android:name=".receiver.BootReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <!-- 某些厂商的快速启动 -->
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" /> <!-- Android 12+ -->
+            </intent-filter>
+        </receiver>
+
+        <activity
+            android:name=".SettingActivity"
+            android:exported="false" />
+        <activity
+            android:name=".MainActivity"
+            android:clearTaskOnLaunch="true"
+            android:exported="false"
+            android:launchMode="singleTask">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.HOME" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:exported="false"
+            android:authorities="xn.huaxue.update.provider"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths"/>
+        </provider>
+
+
+
+        <meta-data
+            android:name="ScopedStorage"
+            android:value="true" />
+    </application>
+
+</manifest>

+ 369 - 0
app/src/main/java/xn/huaxue/update/MainActivity.java

@@ -0,0 +1,369 @@
+package xn.huaxue.update;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.util.Pair;
+
+import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.FileUtils;
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.NetworkUtils;
+import com.blankj.utilcode.util.SPUtils;
+import com.blankj.utilcode.util.ShellUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.blankj.utilcode.util.TimeUtils;
+import com.blankj.utilcode.util.ToastUtils;
+import com.hjq.permissions.Permission;
+import com.hjq.permissions.XXPermissions;
+import com.lxj.xpopup.XPopup;
+import com.lxj.xpopup.impl.InputConfirmPopupView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import okhttp3.Response;
+import xn.huaxue.update.constant.AppConstant;
+import xn.huaxue.update.databinding.ActivityMainBinding;
+import xn.huaxue.update.http.HttpTool;
+import xn.huaxue.update.http.bean.response.UpdateTask;
+import xn.huaxue.update.receiver.TimeTickReceiver;
+
+public class MainActivity extends AppCompatActivity {
+    private ActivityMainBinding binding;
+    private CountDownTimer initCdTimer;
+    private InputConfirmPopupView inputConfirmPopupView;
+    private CountDownTimer requestPermissionCdTimer;
+    private CountDownTimer timeCdTimer;
+    private TimeTickReceiver timeTickReceiver;
+    private CountDownTimer updateCdTimer;
+    private final AtomicBoolean isUploading = new AtomicBoolean(false);
+
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = ActivityMainBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+        timeCdTimer = new CountDownTimer(1000, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+
+            }
+
+            @Override
+            public void onFinish() {
+                if (isFinishing() || isDestroyed()) {
+                    return;
+                }
+                if (!new SimpleDateFormat("HH:mm", Locale.getDefault()).format(new Date()).equals("06:01")) {
+                    MainActivity.this.binding.time.setText(TimeUtils.getNowString());
+                    MainActivity.this.timeCdTimer.start();
+                } else {
+                    LogUtils.d("定时重启");
+                    ShellUtils.execCmd("reboot", true);
+                }
+
+            }
+        };
+        timeCdTimer.start();
+        requestPermissionCdTimer = new CountDownTimer(1000, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+
+            }
+
+            @Override
+            public void onFinish() {
+                requestPermission();
+            }
+        };
+        binding.main.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (!isFinishing() && !isDestroyed()) {
+                    if (!isUploading.get()) {
+                        LogUtils.d("初始化失败");
+                        ShellUtils.execCmd("reboot", true);
+                    } else {
+                        LogUtils.d("初始化成功");
+                    }
+                }
+            }
+        }, 60 * 1000);
+
+        initCdTimer = new CountDownTimer(5 * 1000, 100) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+                binding.tipsTV.setText((millisUntilFinished / 1000) + "秒后开始初始化!");
+            }
+
+            @Override
+            public void onFinish() {
+                MainActivity.this.binding.tipsTV.setText("开始初始化...");
+                AsyncTask.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            Tool.INSTANCE.showBar(false);
+                            MainActivity.this.requestPermissionCdTimer.start();
+                            MainActivity.this.isUploading.set(true);
+                        } catch (Exception e) {
+                            LogUtils.e("重启", e.getMessage());
+                        }
+                    }
+
+
+                });
+                MainActivity.this.initCdTimer.cancel();
+
+
+            }
+        };
+        initCdTimer.start();
+
+    }
+
+    private void requestPermission() {
+        if (!XXPermissions.isGrantedPermissions(this, Permission.READ_EXTERNAL_STORAGE)
+                || !XXPermissions.isGrantedPermissions(this, Permission.WRITE_EXTERNAL_STORAGE)
+                || !XXPermissions.isGrantedPermissions(this, Permission.NOTIFICATION_SERVICE)) {
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.READ_EXTERNAL_STORAGE");
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.WRITE_EXTERNAL_STORAGE");
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.NOTIFICATION_SERVICE");
+            this.requestPermissionCdTimer.start();
+            return;
+        }
+        this.requestPermissionCdTimer.cancel();
+        terminalAuth();
+
+    }
+
+    ThreadUtils.SimpleTask<Boolean> simpleTask = new ThreadUtils.SimpleTask<Boolean>() {
+        @Override
+        public Boolean doInBackground() throws Throwable {
+            ShellUtils.CommandResult result = ShellUtils.execCmd(String.format("ping -c 5 %s", Tool.INSTANCE.getBaseUrl().host()), false);
+            if (result.result == 0) {
+                PingResult pingResult = parsePingOutput(result.successMsg);
+                return pingResult.getAvgRtt() < 100;
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public void onSuccess(Boolean result) {
+            if (result) {
+                MainActivity.this.binding.tipsTV.setText("鉴权中...");
+                ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Pair<Boolean, String>>() {
+                    @Override
+                    public Pair<Boolean, String> doInBackground() throws Throwable {
+                        try {
+                            SPUtils.getInstance().put("TerminalAuth", "");
+                            Response responseTerminalAuth = HttpTool.INSTANCE.terminalAuth();
+                            if (responseTerminalAuth.isSuccessful()) {
+                                JSONObject jSONObject = new JSONObject(responseTerminalAuth.body().string());
+                                if (200 == jSONObject.getInt("code")) {
+                                    String string = jSONObject.getString("data");
+                                    SPUtils sPUtils = SPUtils.getInstance();
+                                    if (TextUtils.isEmpty(string)) {
+                                        string = "";
+                                    }
+                                    sPUtils.put("TerminalAuth", string);
+                                    return Pair.create(true, "");
+                                }
+                                return Pair.create(false, jSONObject.getString("message"));
+                            }
+                        } catch (Exception e) {
+                            LogUtils.e(Log.getStackTraceString(e));
+                        }
+                        return Pair.create(false, "鉴权异常,请联系管理员!");
+
+                    }
+
+                    @Override
+                    public void onSuccess(Pair<Boolean, String> result) {
+                        if (result.first) {
+                            ThreadUtils.cancel(MainActivity.this.simpleTask);
+                            MainActivity.this.timeTickReceiver = new TimeTickReceiver();
+                            MainActivity.this.registerReceiver(MainActivity.this.timeTickReceiver, new IntentFilter("android.intent.action.TIME_TICK"));
+                            MainActivity.this.updateCdTimer = new CountDownTimer(20000L, 1000L) { // from class: xn.huaxue.update.MainActivity.5.1.1
+                                @Override // android.os.CountDownTimer
+                                public void onTick(long j) {
+                                }
+
+                                @Override // android.os.CountDownTimer
+                                public void onFinish() {
+                                    UpdateTool.INSTANCE.checkUpdate(MainActivity.this);
+                                    AsyncTask.execute(new Runnable() { // from class: xn.huaxue.update.MainActivity.5.1.1.1
+                                        @Override // java.lang.Runnable
+                                        public void run() {
+                                            try {
+                                                HttpTool.INSTANCE.heartbeat();
+                                            } catch (Exception e) {
+                                                LogUtils.e(Log.getStackTraceString(e));
+                                            }
+                                        }
+                                    });
+                                    MainActivity.this.updateCdTimer.start();
+                                }
+                            };
+                            MainActivity.this.updateCdTimer.start();
+                            Tool.INSTANCE.stopApp("xn.hxp");
+                            Tool.INSTANCE.openApp("xn.hxp");
+                            AsyncTask.execute(() -> {
+                                if (SPUtils.getInstance().getBoolean("isSelfUpdate", false)) {
+                                    try {
+                                        UpdateTask.Task task = (UpdateTask.Task) GsonUtils.fromJson(SPUtils.getInstance().getString("SelfUpdate", ""), UpdateTask.Task.class);
+                                        if (task != null) {
+                                            HttpTool.INSTANCE.updateCallBack(task.getTaskId(), task.getDeviceCode(), true, true);
+                                            SPUtils.getInstance().put("isSelfUpdate", false, true);
+                                            Tool.INSTANCE.reboot();
+                                        }
+                                    } catch (IOException | JSONException e) {
+                                        LogUtils.e(Log.getStackTraceString(e));
+                                    }
+                                }
+                            });
+                            MainActivity.this.binding.tipsTV.setText("运行中");
+                        }
+
+                    }
+                });
+                ThreadUtils.cancel(MainActivity.this.simpleTask);
+            } else {
+                MainActivity.this.binding.tipsTV.setText("无法连接到服务器,正在重试...");
+            }
+            String iPAddress = NetworkUtils.getIPAddress(true);
+            TextView textView = MainActivity.this.binding.ipTV;
+            if (TextUtils.isEmpty(iPAddress)) {
+                iPAddress = "NULL";
+            }
+            textView.setText(iPAddress);
+            String serialNumber = Tool.INSTANCE.getSerialNumber();
+            MainActivity.this.binding.snTV.setText(TextUtils.isEmpty(serialNumber) ? "NULL" : serialNumber);
+
+        }
+    };
+
+    private void terminalAuth() {
+        ThreadUtils.executeByCachedAtFixRate(simpleTask, ThreadLocalRandom.current().nextInt(10, 20), TimeUnit.SECONDS);
+        // 清理一个月前的log
+        AsyncTask.execute(() -> {
+            List<File> fileList = FileUtils.listFilesInDir("/sdcard/logs/");
+            List<File> crashList = FileUtils.listFilesInDir("/sdcard/logs/crash");
+            for (int i = 0; i < fileList.size(); i++) {
+                File file = fileList.get(i);
+                long oneMonth = 30 * 86400 * 1000L;
+                if ((System.currentTimeMillis() - file.lastModified()) > oneMonth) {
+                    LogUtils.d("删除日志文件", file);
+                    FileUtils.delete(file);
+                }
+            }
+            for (int i = 0; i < crashList.size(); i++) {
+                File file = crashList.get(i);
+                long oneMonth = 30 * 86400 * 1000L;
+                if ((System.currentTimeMillis() - file.lastModified()) > oneMonth) {
+                    LogUtils.d("删除crash文件", file);
+                    FileUtils.delete(file);
+                }
+            }
+        });
+    }
+
+
+    @Override
+    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        binding.versionNameTV.setText(AppUtils.getAppVersionName());
+
+        inputConfirmPopupView = new XPopup.Builder(this).autoDismiss(false).asInputConfirm("Tips", "请输入管理员密码",
+                text -> {
+                    if (text.equals(SPUtils.getInstance().getString(AppConstant.ADMIN_PASSWORD, "admin@098&"))) {
+                        ActivityUtils.startActivity(SettingActivity.class);
+                        inputConfirmPopupView.dismiss();
+                    } else {
+                        ToastUtils.showLong("密码不正确,请重新输入!");
+                    }
+                });
+        binding.logoIV.setOnLongClickListener(v -> {
+            if (AppUtils.isAppDebug()) {
+                ActivityUtils.startActivity(SettingActivity.class);
+            } else {
+                EditText editText = inputConfirmPopupView.getEditText();
+                if (null != editText) {
+                    editText.setText("");
+                }
+                inputConfirmPopupView.show();
+            }
+            return true;
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        TimeTickReceiver timeTickReceiver = this.timeTickReceiver;
+        if (timeTickReceiver != null) {
+            unregisterReceiver(timeTickReceiver);
+            this.timeTickReceiver = null;
+        }
+        Tool.INSTANCE.exitApp();
+
+    }
+
+    public PingResult parsePingOutput(String pingOutput) {
+        PingResult result = new PingResult();
+        // 解析目标IP
+        Pattern ipPattern = Pattern.compile("PING (\\d+\\.\\d+\\.\\d+\\.\\d+)");
+        Matcher ipMatcher = ipPattern.matcher(pingOutput);
+        if (ipMatcher.find()) {
+            result.setTargetIP(ipMatcher.group(1));
+        }
+        // 解析统计信息
+        Pattern statsPattern = Pattern.compile(
+                "(\\d+) packets transmitted, (\\d+) received, (\\d+)% packet loss, time (\\d+)ms"
+        );
+        Matcher statsMatcher = statsPattern.matcher(pingOutput);
+        if (statsMatcher.find()) {
+            result.setPacketsTransmitted(Integer.parseInt(statsMatcher.group(1)));
+            result.setPacketsReceived(Integer.parseInt(statsMatcher.group(2)));
+            result.setPacketLossPercentage(Double.parseDouble(statsMatcher.group(3)));
+        }
+        // 解析RTT信息
+        Pattern rttPattern = Pattern.compile(
+                "rtt min/avg/max/mdev = (\\d+\\.\\d+)/(\\d+\\.\\d+)/(\\d+\\.\\d+)/(\\d+\\.\\d+) ms"
+        );
+        Matcher rttMatcher = rttPattern.matcher(pingOutput);
+        if (rttMatcher.find()) {
+            result.setMinRtt(Double.parseDouble(rttMatcher.group(1)));
+            result.setAvgRtt(Double.parseDouble(rttMatcher.group(2)));
+            result.setMaxRtt(Double.parseDouble(rttMatcher.group(3)));
+            result.setMdevRtt(Double.parseDouble(rttMatcher.group(4)));
+        }
+        return result;
+    }
+}

+ 76 - 0
app/src/main/java/xn/huaxue/update/PingResult.java

@@ -0,0 +1,76 @@
+package xn.huaxue.update;
+
+public class PingResult {
+    private String targetIP;
+    private int packetsTransmitted;
+    private int packetsReceived;
+    private double packetLossPercentage;
+    private double minRtt;
+    private double avgRtt;
+    private double maxRtt;
+    private double mdevRtt;
+
+    public String getTargetIP() {
+        return targetIP;
+    }
+
+    public void setTargetIP(String targetIP) {
+        this.targetIP = targetIP;
+    }
+
+    public int getPacketsTransmitted() {
+        return packetsTransmitted;
+    }
+
+    public void setPacketsTransmitted(int packetsTransmitted) {
+        this.packetsTransmitted = packetsTransmitted;
+    }
+
+    public int getPacketsReceived() {
+        return packetsReceived;
+    }
+
+    public void setPacketsReceived(int packetsReceived) {
+        this.packetsReceived = packetsReceived;
+    }
+
+    public double getPacketLossPercentage() {
+        return packetLossPercentage;
+    }
+
+    public void setPacketLossPercentage(double packetLossPercentage) {
+        this.packetLossPercentage = packetLossPercentage;
+    }
+
+    public double getMinRtt() {
+        return minRtt;
+    }
+
+    public void setMinRtt(double minRtt) {
+        this.minRtt = minRtt;
+    }
+
+    public double getAvgRtt() {
+        return avgRtt;
+    }
+
+    public void setAvgRtt(double avgRtt) {
+        this.avgRtt = avgRtt;
+    }
+
+    public double getMaxRtt() {
+        return maxRtt;
+    }
+
+    public void setMaxRtt(double maxRtt) {
+        this.maxRtt = maxRtt;
+    }
+
+    public double getMdevRtt() {
+        return mdevRtt;
+    }
+
+    public void setMdevRtt(double mdevRtt) {
+        this.mdevRtt = mdevRtt;
+    }
+}

+ 81 - 0
app/src/main/java/xn/huaxue/update/SettingActivity.java

@@ -0,0 +1,81 @@
+package xn.huaxue.update;
+
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.NetworkUtils;
+import com.blankj.utilcode.util.SPUtils;
+import com.blankj.utilcode.util.ToastUtils;
+
+import xn.huaxue.update.constant.AppConstant;
+import xn.huaxue.update.databinding.ActivitySettingBinding;
+
+public class SettingActivity extends AppCompatActivity {
+
+    private ActivitySettingBinding binding;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = ActivitySettingBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+    }
+
+    @Override
+    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        binding.varBT.setText("版本:" + AppUtils.getAppVersionName());
+        binding.snBT.setText("SN:" + Tool.INSTANCE.getSerialNumber());
+        binding.ipBT.setText(NetworkUtils.getIPAddress(true));
+        binding.rebootBT.setOnClickListener(v -> Tool.INSTANCE.cmd("reboot"));
+        binding.fileBrowserBT.setOnClickListener(v -> Tool.INSTANCE.openFileBrowser());
+        binding.settingBT.setOnClickListener(v -> Tool.INSTANCE.openSetting());
+        binding.barSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> Tool.INSTANCE.showBar(isChecked));
+
+        binding.httpUriET.setText(SPUtils.getInstance().getString(AppConstant.BASE_URL, Tool.INSTANCE.getBaseUrl().toString()));
+        // 管理员密码
+        binding.oldPasswordET.setText(SPUtils.getInstance().getString(AppConstant.ADMIN_PASSWORD, "admin@098&"));
+        // 测试地址输入
+        binding.httpUriTestBT.setOnClickListener(v -> binding.httpUriET.setText("http://192.168.1.8/api/"));
+        // 线上地址输入
+        binding.httpUriXnBT.setOnClickListener(v -> binding.httpUriET.setText("http://10.148.100.82/api/"));
+        // 保存
+        binding.save.setOnClickListener(v -> save());
+
+    }
+
+
+    @Override
+    public void onBackPressed() {
+    }
+
+    private void save() {
+        try {
+            Editable text = this.binding.httpUriET.getText();
+            if (text != null && !TextUtils.isEmpty(text)) {
+                Editable text2 = this.binding.oldPasswordET.getText();
+                if (text2 != null && !TextUtils.isEmpty(text2)) {
+                    SPUtils.getInstance().put(AppConstant.ADMIN_PASSWORD, text2.toString(), true);
+                    SPUtils.getInstance().put(AppConstant.BASE_URL, text.toString(), true);
+                    finish();
+                    return;
+                }
+                this.binding.oldPasswordET.setError("请输入管理员密码");
+                return;
+            }
+            this.binding.httpUriET.setError("请输入Http服务地址");
+        } catch (Exception e) {
+            ToastUtils.showLong("参数异常无法保存,请核对参数!");
+            LogUtils.e(Log.getStackTraceString(e));
+        }
+
+    }
+
+}

+ 113 - 0
app/src/main/java/xn/huaxue/update/Tool.java

@@ -0,0 +1,113 @@
+package xn.huaxue.update;
+
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.NetworkUtils;
+import com.blankj.utilcode.util.SPUtils;
+import com.blankj.utilcode.util.ShellUtils;
+import com.blankj.utilcode.util.Utils;
+import com.lztek.toolkit.Lztek;
+
+import java.util.Locale;
+
+import okhttp3.HttpUrl;
+import xn.huaxue.update.constant.AppConstant;
+
+public enum Tool {
+    INSTANCE;
+    private Lztek lztek;
+
+    private Tool() {
+        lztek = Lztek.create(Utils.getApp());
+    }
+
+    public void cmd(String cmd) {
+        LogUtils.d(cmd);
+        lztek.suExec(cmd);
+    }
+
+    public void reboot() {
+        lztek.hardReboot();
+    }
+
+    public void reStartApp() {
+        exitApp();
+        cmd("monkey -p " + AppUtils.getAppPackageName() + " -c android.intent.category.LAUNCHER 1");
+    }
+
+    public void openFileBrowser() {
+        openApp("display.interactive.filebrowser");
+    }
+
+    public void exitApp() {
+        cmd("am force-stop " + AppUtils.getAppPackageName());
+    }
+
+    public void openSetting() {
+        openApp("com.android.settings");
+    }
+
+    public void openApp(String packageName) {
+        cmd("monkey -p " + packageName + " -c android.intent.category.LAUNCHER 1");
+    }
+
+    public void setBluetooth(boolean isEnable) {
+        cmd("settings put global bluetooth_on " + (isEnable ? "1" : "0"));
+    }
+
+    public ShellUtils.CommandResult stopApp(String packageName) {
+        return ShellUtils.execCmd("am force-stop " + packageName, true);
+    }
+
+    public void setLauncher() {
+        cmd("pm set-home-activity " + AppUtils.getAppPackageName());
+    }
+
+
+    public void startTaskService() {
+        cmd("am startservice -n xn.huaxue.update/xn.huaxue.update.service.TaskService");
+    }
+
+    public String getSerialNumber() {
+        String mac = lztek.getEthMac().toUpperCase(Locale.getDefault());
+        return mac.replace(":", "");
+    }
+
+    public HttpUrl getBaseUrl() {
+        String baseUrl = SPUtils.getInstance().getString(AppConstant.BASE_URL);
+        if (TextUtils.isEmpty(baseUrl)) {
+            if (AppUtils.isAppDebug()) {
+                return HttpUrl.get("http://192.168.1.8/api/");
+            } else {
+                return HttpUrl.get("http://10.148.100.82/api/");
+            }
+        } else {
+            return HttpUrl.get(baseUrl);
+        }
+    }
+
+    public void showBar(boolean isShow) {
+        lztek.navigationBarSlideShow(isShow);
+    }
+
+    public String getIp() {
+        return NetworkUtils.getIPAddress(true);
+    }
+
+    public String checkUrl(String url) {
+        try {
+            String uriStr = url;
+            if (!url.matches("https?://.*") && !url.matches("http?://.*")) {
+                uriStr = Uri.parse(getBaseUrl() + url).toString();
+            }
+            return uriStr;
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
+            return url;
+        }
+    }
+}

+ 207 - 0
app/src/main/java/xn/huaxue/update/UpdateTool.java

@@ -0,0 +1,207 @@
+package xn.huaxue.update;
+
+import android.annotation.SuppressLint;
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.FileUtils;
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.SPUtils;
+import com.blankj.utilcode.util.ShellUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.blankj.utilcode.util.Utils;
+
+import org.json.JSONException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import okhttp3.Response;
+import xn.huaxue.update.constant.AppConstant;
+import xn.huaxue.update.http.HttpTool;
+import xn.huaxue.update.http.bean.response.UpdateTask;
+
+public enum UpdateTool {
+    INSTANCE;
+
+    private UpdateTask.Task task;
+    @SuppressLint("SdCardPath")
+    private final String apkPath = "/sdcard/Download/apk/";
+    private final String apkName = "app.apk";
+    private DownloadManager downloadManager;
+    private long downloadId;
+    private long lastRunTime;
+
+    private final AtomicBoolean isUploading = new AtomicBoolean(false);
+
+    public void checkUpdate(final Context context) {
+        // 若是3分钟都没下载完成,则认为下载失败
+        long runTime = (System.currentTimeMillis() - lastRunTime);
+        if (lastRunTime > 0 && runTime > 1000 * 60 * 3) {
+            isUploading.set(false);
+        }
+        // 空闲中
+        if (!this.isUploading.get()) {
+            this.lastRunTime = System.currentTimeMillis();
+            this.isUploading.set(true);
+            if (this.downloadManager == null) {
+                downloadManager = (DownloadManager) context.getApplicationContext().getSystemService(Context.DOWNLOAD_SERVICE);
+            }
+            ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Boolean>() {
+                @Override
+                public Boolean doInBackground() throws Throwable {
+                    try {
+                        Response response = HttpTool.INSTANCE.update();
+                        if (response.isSuccessful()) {
+                            String json = response.body().string();
+                            UpdateTask updateTask = GsonUtils.fromJson(json, UpdateTask.class);
+                            if (updateTask.getCode() == 200) {
+                                List<UpdateTask.Task> updateTaskData = updateTask.getData();
+                                if (null != updateTaskData && !updateTaskData.isEmpty()) {
+                                    task = updateTaskData.get(0);
+                                    FileUtils.createOrExistsDir(apkPath);
+                                    FileUtils.deleteAllInDir(apkPath);
+                                    if (!AppUtils.getAppPackageName().equals(task.getStartLaunchPackage())) {
+                                        SPUtils.getInstance().put(AppConstant.KeySP.MASTER_PACKAGE_NAME, task.getStartLaunchPackage());
+                                    }
+                                    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(Tool.INSTANCE.checkUrl(task.getApkUrl())));
+                                    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "apk/" + apkName);
+                                    request.setMimeType("application/vnd.android.package-archive");
+                                    request.setVisibleInDownloadsUi(false);
+                                    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
+                                    context.registerReceiver(downloadApkReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+                                    downloadId = downloadManager.enqueue(request);
+                                    LogUtils.d("发起下载", downloadId);
+                                    return true;
+                                }
+                            } else {
+                                LogUtils.e(updateTask.getMessage());
+                            }
+                        }
+                    } catch (Exception e) {
+                        LogUtils.e(Log.getStackTraceString(e));
+                    }
+                    return false;
+                }
+
+                @Override
+                public void onSuccess(Boolean bool) {
+                    UpdateTool.this.isUploading.set(bool.booleanValue());
+                }
+            });
+            return;
+        }
+        LogUtils.d("正在升级中...");
+    }
+
+
+    private final BroadcastReceiver downloadApkReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
+            LogUtils.d("下载成功", downloadId, task);
+            if (downloadId == id) {
+                if (null != task) {
+                    AsyncTask.execute(() -> {
+                        try {
+                            HttpTool.INSTANCE.updateCallBack(UpdateTool.this.task.getTaskId(), UpdateTool.this.task.getDeviceCode(), true, false);
+                        } catch (IOException | JSONException e) {
+                            LogUtils.e(Log.getStackTraceString(e));
+                        }
+                        if (UpdateTool.this.task.getStartLaunchPackage().equals(AppUtils.getAppPackageName())) {
+                            SPUtils.getInstance().put("isSelfUpdate", true, true);
+                            SPUtils.getInstance().put("SelfUpdate", GsonUtils.toJson(UpdateTool.this.task), true);
+                            Uri uriForFile = FileProvider.getUriForFile(context, "xn.huaxue.update.provider", new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "apk/app.apk"));
+                            Intent installIntent = new Intent("android.intent.action.VIEW");
+                            installIntent.setDataAndType(uriForFile, "application/vnd.android.package-archive");
+                            installIntent.putExtra("IMPLUS_INSTALL", "SILENT_INSTALL");
+                            installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                            context.startActivity(installIntent);
+                        } else {
+                            ShellUtils.execCmdAsync("pm install -d -g /sdcard/Download/apk/app.apk", true, new Utils.Consumer<ShellUtils.CommandResult>() {
+                                @Override
+                                public void accept(ShellUtils.CommandResult commandResult) {
+                                    if (commandResult.result == 0) {
+                                        Tool.INSTANCE.openApp(UpdateTool.this.task.getStartLaunchPackage());
+                                    } else {
+                                        LogUtils.e("安装失败", commandResult);
+                                    }
+                                    AsyncTask.execute(() -> {
+                                        try {
+                                            HttpTool.INSTANCE.updateCallBack(UpdateTool.this.task.getTaskId(), UpdateTool.this.task.getDeviceCode(), true, commandResult.result == 0);
+                                        } catch (Exception e) {
+                                            LogUtils.e(Log.getStackTraceString(e));
+                                        }
+
+                                    });
+                                }
+                            });
+                        }
+                        UpdateTool.this.isUploading.set(false);
+
+                    });
+                }
+            }
+        }
+    };
+
+    /**
+     * 解析未安装的APK文件
+     *
+     * @param context 上下文
+     * @param apkPath APK文件路径
+     */
+    public void parseApkFile(Context context, String apkPath) {
+        PackageManager pm = context.getPackageManager();
+        PackageInfo packageInfo = pm.getPackageArchiveInfo(apkPath,
+                PackageManager.GET_PERMISSIONS | PackageManager.GET_META_DATA);
+
+        if (packageInfo != null) {
+            // 获取应用信息
+            ApplicationInfo appInfo = packageInfo.applicationInfo;
+
+            // 修复资源路径问题
+            appInfo.sourceDir = apkPath;
+            appInfo.publicSourceDir = apkPath;
+
+            // 获取基本信息
+            String packageName = packageInfo.packageName;
+            String versionName = packageInfo.versionName;
+            int versionCode = packageInfo.versionCode;
+
+            // 获取权限
+            String[] permissions = packageInfo.requestedPermissions;
+
+            // 获取APK大小
+            File apkFile = new File(apkPath);
+            long apkSize = apkFile.length();
+
+            // 打印信息
+            Log.d("APK-INFO", "包名: " + packageName);
+            Log.d("APK-INFO", "版本: " + versionName + "(" + versionCode + ")");
+            Log.d("APK-INFO", "大小: " + (apkSize / 1024) + "KB");
+
+            if (permissions != null) {
+                for (String perm : permissions) {
+                    Log.d("APK-INFO", "权限: " + perm);
+                }
+            }
+        }
+    }
+}

+ 64 - 0
app/src/main/java/xn/huaxue/update/app/App.java

@@ -0,0 +1,64 @@
+package xn.huaxue.update.app;
+
+import android.annotation.SuppressLint;
+import android.app.Application;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.CrashUtils;
+import com.blankj.utilcode.util.FileUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.blankj.utilcode.util.ToastUtils;
+import com.github.anrwatchdog.ANRError;
+import com.github.anrwatchdog.ANRWatchDog;
+
+import xn.huaxue.update.Tool;
+
+
+public class App extends Application {
+    @Override
+    @SuppressLint("SdCardPath")
+    public void onCreate() {
+        super.onCreate();
+        new ANRWatchDog(5000).setANRListener(new ANRWatchDog.ANRListener() {
+            @Override
+            public void onAppNotResponding(ANRError error) {
+                LogUtils.e("ANR", error.getMessage(), error);
+                ToastUtils.showLong("检测到应用卡顿,设备即将重启恢复!");
+                ThreadUtils.runOnUiThreadDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        Tool.INSTANCE.reboot();
+                    }
+                }, 3000);
+            }
+        }).start();
+        String logPath = "/sdcard/logs/";
+        String crashPath = "/sdcard/logs/crash/";
+        // log文件存储地址
+        FileUtils.createOrExistsDir(logPath);
+        FileUtils.createOrExistsDir(crashPath);
+        CrashUtils.init(crashPath, crashInfo -> {
+            LogUtils.e(crashInfo);
+            Tool.INSTANCE.reStartApp();
+        });
+        LogUtils.Config config = LogUtils.getConfig();
+        // log开关控制
+        config.setLogSwitch(true);
+        // log控制台开关
+        config.setConsoleSwitch(true);
+        // logTag
+        config.setGlobalTag("Jayce");
+        // log头部信息开关
+        config.setLogHeadSwitch(true);
+        // log文件开关
+        config.setLog2FileSwitch(true);
+        config.setDir(logPath);
+        // log文件前缀
+        config.setFilePrefix(AppUtils.getAppName());
+        // log边框开关
+        config.setBorderSwitch(true);
+        // log文件保存天数
+        config.setSaveDays(30);
+    }
+}

+ 33 - 0
app/src/main/java/xn/huaxue/update/constant/AppConstant.java

@@ -0,0 +1,33 @@
+package xn.huaxue.update.constant;
+
+public final class AppConstant {
+    private AppConstant() {
+    }
+
+    public static final class KeySP {
+        // 主应用包名
+        public static final String MASTER_PACKAGE_NAME = "masterPackageName";
+        // MASTER ANR 重启次数
+        public static final String MASTER_ANR_COUNT = "masterAnrCount";
+        // App ANR 重启次数
+        public static final String APP_ANR_COUNT = "appAnrCount";
+    }
+
+    /**
+     * 自动更新应用
+     */
+    public static final String AUTO_UPDATE = "auto_update";
+    /**
+     * 自动拉起主应用
+     */
+    public static final String AUTO_MASTER = "auto_master";
+    /**
+     * BaseUrl
+     */
+    public static final String BASE_URL = "base_url";
+
+    /**
+     * 管理员密码
+     */
+    public static final String ADMIN_PASSWORD = "admin_password";
+}

+ 17 - 0
app/src/main/java/xn/huaxue/update/evnet/UpdateUiEvent.java

@@ -0,0 +1,17 @@
+package xn.huaxue.update.evnet;
+
+public class UpdateUiEvent {
+    public String msg;
+
+    public UpdateUiEvent(String msg) {
+        this.msg = msg;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+}

+ 77 - 0
app/src/main/java/xn/huaxue/update/http/HttpTool.java

@@ -0,0 +1,77 @@
+package xn.huaxue.update.http;
+
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.NetworkUtils;
+import com.blankj.utilcode.util.ShellUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import okhttp3.Response;
+import xn.huaxue.update.Tool;
+
+public enum HttpTool {
+    INSTANCE;
+
+    public Response terminalAuth() throws IOException, JSONException {
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("deviceCode", Tool.INSTANCE.getSerialNumber());
+        jsonObject.put("code", "aio_chemical");
+        return OkHttpUtils.INSTANCE.postSync(Tool.INSTANCE.getBaseUrl() + "terminal/authorize", jsonObject.toString());
+    }
+
+    public Response update() throws IOException, JSONException {
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("deviceCode", Tool.INSTANCE.getSerialNumber());
+        return OkHttpUtils.INSTANCE.postSync(Tool.INSTANCE.getBaseUrl() + "terminal/upgrade/device/task", jsonObject.toString());
+    }
+
+    public void updateCallBack(String taskId, String deviceCode, boolean isDownload, boolean isInstall) throws IOException, JSONException {
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("taskId", taskId);
+        jsonObject.put("deviceCode", deviceCode);
+        if (isDownload) {
+            jsonObject.put("status", "downloaded");
+        }
+        if (isInstall) {
+            jsonObject.put("status", "success");
+        }
+        OkHttpUtils.INSTANCE.postSync(Tool.INSTANCE.getBaseUrl() + "terminal/upgrade/device/callback", jsonObject.toString());
+    }
+
+    public Response heartbeat() throws JSONException, IOException {
+        JSONObject jsonObject = new JSONObject();
+        ShellUtils.CommandResult commandResult = ShellUtils.execCmd("dumpsys activity activities", true);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(commandResult.successMsg.getBytes())));
+        String line;
+        boolean isShow = false;
+        while ((line = reader.readLine()) != null) {
+            if (line.contains("mResumedActivity") || line.contains("mCurrentFocus")) {
+                String[] itemArray = line.split(" ", -1);
+                for (String item : itemArray) {
+                    if (item.contains("/")) {
+                        String packageName = item.substring(0, item.indexOf("/"));
+                        isShow = AppUtils.getAppPackageName().equals(packageName);
+                        break;
+                    }
+                }
+                break;
+            }
+        }
+        // Ip
+        jsonObject.put("deviceNo", Tool.INSTANCE.getSerialNumber());
+        jsonObject.put("versionName", AppUtils.getAppVersionName());
+        jsonObject.put("packageName", AppUtils.getAppPackageName());
+        jsonObject.put("isShow", isShow);
+        jsonObject.put("adbEnabled", true);
+        jsonObject.put("ip", NetworkUtils.getIPAddress(true));
+        return OkHttpUtils.INSTANCE.postSync(Tool.INSTANCE.getBaseUrl() + "terminal/machine/upgrade/heartbeat", jsonObject.toString());
+    }
+
+}

+ 61 - 0
app/src/main/java/xn/huaxue/update/http/LogInterceptor.java

@@ -0,0 +1,61 @@
+package xn.huaxue.update.http;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.SPUtils;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+import okio.BufferedSource;
+import xn.huaxue.update.Tool;
+
+public class LogInterceptor implements Interceptor {
+    @NonNull
+    @Override
+    public Response intercept(@NonNull Chain chain) throws IOException {
+        try {
+            Request oldRequest = chain.request();
+            Request.Builder newRequestBuilder = oldRequest.newBuilder();
+            String terminalAuth = SPUtils.getInstance().getString("TerminalAuth", "");
+            if (null != terminalAuth && !TextUtils.isEmpty(terminalAuth)) {
+                newRequestBuilder.header("TerminalAuth", "Bearer " + terminalAuth);
+            }
+            newRequestBuilder.header("SN", Tool.INSTANCE.getSerialNumber());
+            newRequestBuilder.header("IP", SPUtils.getInstance().getString("IP", "127.0.0.1"));
+
+            Request newRequest = newRequestBuilder.build();
+            // 记录请求报文
+            String requestLog = "--> " + newRequest.method() + " " + newRequest.url() + "\n"
+                    + newRequest.headers() + "\n"
+                    + (newRequest.body() != null ? GsonUtils.toJson(newRequest.body()) : ""); // 请求体可能为空
+            LogUtils.d(requestLog);
+
+            Response response = chain.proceed(newRequest);
+            ResponseBody responseBody = response.body();
+            BufferedSource source = responseBody.source();
+            source.request(Long.MAX_VALUE);
+            Buffer buffer = source.getBuffer();
+            String responseBodyString = buffer.clone().readString(StandardCharsets.UTF_8);
+            // 记录响应报文
+            String responseLog = "<-- " + response.code() + " " + response.message() + " " + response.request().url() + "\n"
+                    + response.headers() + "\n"
+                    + (response.body() != null ? responseBodyString : ""); // 响应体可能为空
+            LogUtils.d(responseLog);
+            return response.newBuilder().body(ResponseBody.create(responseBody.contentType(), responseBodyString)).build();
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
+        }
+        return chain.proceed(chain.request());
+    }
+}

+ 42 - 0
app/src/main/java/xn/huaxue/update/http/OkHttpUtils.java

@@ -0,0 +1,42 @@
+package xn.huaxue.update.http;
+
+import java.io.IOException;
+
+import okhttp3.Call;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import xn.huaxue.update.http.bean.AllLogInterceptor;
+
+public enum OkHttpUtils {
+    INSTANCE;
+    private final OkHttpClient client = new OkHttpClient.Builder()
+            .addInterceptor(new AllLogInterceptor())
+            .build();
+    private final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+
+    private OkHttpUtils() {
+    }
+
+    // 同步 GET 请求
+    public Response getSync(String url) throws IOException {
+        Request request = new Request.Builder()
+                .url(url)
+                .build();
+        Call call = client.newCall(request);
+        return call.execute();
+    }
+
+    // 同步 POST 请求
+    public Response postSync(String url, String json) throws IOException {
+        RequestBody body = RequestBody.create(JSON, json);
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+        Call call = client.newCall(request);
+        return call.execute();
+    }
+}

+ 86 - 0
app/src/main/java/xn/huaxue/update/http/bean/AllLogInterceptor.java

@@ -0,0 +1,86 @@
+package xn.huaxue.update.http.bean;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.SPUtils;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+import okio.BufferedSource;
+import xn.huaxue.update.Tool;
+
+public class AllLogInterceptor implements Interceptor {
+    private int heartbeatTimeOutCount = 0;
+
+    @NonNull
+    @Override
+    public Response intercept(@NonNull Chain chain) throws IOException {
+        LogUtils.d("心跳失败次数:" + heartbeatTimeOutCount);
+        Request request = chain.request();
+        try {
+            Request.Builder newRequestBuilder = request.newBuilder();
+            String terminalAuth = SPUtils.getInstance().getString("TerminalAuth", "");
+            if (null != terminalAuth && !TextUtils.isEmpty(terminalAuth)) {
+                newRequestBuilder.header("TerminalAuth", "Bearer " + terminalAuth);
+            }
+            newRequestBuilder.header("SN", Tool.INSTANCE.getSerialNumber());
+            request = newRequestBuilder.build();
+            long t1 = System.nanoTime();
+            // 记录请求信息
+            StringBuilder requestLog = new StringBuilder();
+            requestLog.append("--> ").append(request.method()).append(" ").append(request.url()).append("\n");
+            requestLog.append(request.headers()).append("\n");
+            // 记录请求体
+            RequestBody requestBody = request.body();
+            if (requestBody != null) {
+                Buffer buffer = new Buffer();
+                requestBody.writeTo(buffer);
+                Charset charset = requestBody.contentType() != null ? requestBody.contentType().charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8;
+                requestLog.append(buffer.readString(charset)).append("\n");
+            }
+            // 打印请求信息
+            LogUtils.d(requestLog);
+            // 执行请求
+            Response response = chain.proceed(request);
+            long t2 = System.nanoTime();
+            // 记录响应信息
+            StringBuilder responseLog = new StringBuilder();
+            responseLog.append("<-- ").append(response.code()).append(" ").append(response.message()).append(" ").append(response.request().url()).append(" (").append((t2 - t1) / 1e6d).append("ms)\n\n");
+//            responseLog.append(response.headers()).append("\n");
+            // 记录响应体
+            ResponseBody responseBody = response.body();
+            if (responseBody != null) {
+                BufferedSource source = responseBody.source();
+                source.request(Long.MAX_VALUE); // Buffer the entire body.
+                Buffer buffer = source.getBuffer();
+                Charset charset = responseBody.contentType() != null ? responseBody.contentType().charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8;
+                responseLog.append(buffer.clone().readString(charset)).append("\n"); // 使用 clone() 避免消耗响应体
+                response = response.newBuilder()
+                        .body(ResponseBody.create(responseBody.contentType(), buffer.size(), buffer))
+                        .build();
+            }
+            // 打印响应信息
+            LogUtils.d(responseLog);
+            return response;
+        } catch (Exception e) {
+            String url = request.url().toString();
+            if (url.contains("terminal/machine/upgrade/heartbeat")) {
+                heartbeatTimeOutCount++;
+            }
+            LogUtils.e(url, Log.getStackTraceString(e));
+        }
+        return chain.proceed(chain.request());
+    }
+}

+ 145 - 0
app/src/main/java/xn/huaxue/update/http/bean/request/DeviceInfo.java

@@ -0,0 +1,145 @@
+package xn.huaxue.update.http.bean.request;
+
+import java.util.List;
+
+public class DeviceInfo {
+    // 状态栏是否开启
+    private boolean isStatusBarEnable;
+    // 导航栏是否开启
+    private boolean isNavBarEnable;
+    // 网络配置
+    private NetworkInfo networkInfo;
+    // 时间配置
+    private TimeInfo timeInfo;
+    // 系统配置
+    private SystemInfo systemInfo;
+    // App配置
+    private List<AppInfo> appInfoList;
+
+
+    private static class AppInfo {
+        private String versionName;
+        private String packageName;
+        private boolean isShow;
+    }
+
+    private static class TimeInfo {
+        // 开机时间  默认-1
+        private long onTime;
+        // 关机时间 默认-1
+        private long offTime;
+        /**
+         * 门禁点锁状态1
+         * true-当前锁处于上电锁门状态
+         * false-当前锁处于断电开门状态或参数错误
+         */
+        private boolean electricLock1;
+        /**
+         * 门禁点锁状态2
+         * true-当前锁处于上电锁门状态
+         * false-当前锁处于断电开门状态或参数错误
+         */
+        private boolean electricLock2;
+    }
+
+    private static class SystemInfo {
+        /**
+         * 设备型号
+         * 返回设备型号,获取失败时返回默认值 DS-D60A
+         */
+        private String deviceType;
+        /**
+         * 设备序列号
+         * 返回设备序列号, 获取失败时返回默认值 40000000
+         */
+        private String sn;
+        /**
+         * adb状态
+         * 0 代表 ADB 未打开
+         * 1 代表 ADB 打开
+         */
+        private int adbStatus;
+        /**
+         * 设备箱体温度
+         * 返回失败或没有温度传感器,”faild”字符串
+         * 成功返回例 37
+         */
+        private String temperature;
+        /**
+         * 系统编译版本号
+         * 获取失败返回”unknown”字符串
+         * 成功返回例 V2.0.0 build 171226
+         */
+        private String buildDesc;
+        /**
+         * 系统MCU版本号
+         * 获取失败返回 null
+         * 成功返回例 V1.0.0 build 171226
+         */
+        private String mcuVersion;
+
+        /**
+         * CPU使用率 当前 Cpu 的使用率,百分比,
+         * 获取失败时返回”-1.00”字符串
+         */
+        private String cpuUsageRate;
+        /**
+         * GPU使用率 当前 Gpu 的使用率,百分比,
+         * 获取失败时返回”-1.00”字符串
+         */
+        private String gpuUsageRate;
+        // 已使用内存大小(单位 MB)
+        private int used;
+        // 总内存大小(MB)
+        private int total;
+        // 583.00MB/1.96GB
+        private String describe;
+        private int describeContents;
+        // 获取SDK版本 返回 SDK 版本号,获取失败返回”none”字符串
+        private String sdkVersion;
+        // 为当前生效的默认 Launcher 应用包名,默认(始终)Launcher 应用修改后该值即时更新。如果当前系统未设置默认(始终)Launcher,则该值为空字符串(“”)。
+        private String currentLauncherName;
+        // 为持久化文件中保存的默认 Launcher 应用包名,默认 Launcher 应用修改后约 10 秒后该值才会更新,该值仅当系统内存在 2 个及以上Launcher 应用时才会存在,否则为空字符串(“”)。
+        private String preferredLauncherName;
+    }
+
+    private static class NetworkInfo {
+        /**
+         * ip
+         * 代表有线网络 ip 地址
+         * 默认值:”0.0.0.0”
+         */
+        private String ip;
+        /**
+         * dhcp
+         * 0—代表自动获取网络(默认)
+         * 1—代表静态设置网络信息模式
+         */
+        private int dhcp;
+        // dns1 默认值:”0.0.0.0”
+        private String dns1;
+        // dns2 默认值:”0.0.0.0”
+        private String dns2;
+        /**
+         * mac
+         * 代表有线网络 mac 地址,
+         * 有线网络未连接时,该属性仍有值,
+         * 默认值:”00:00:00:00:00:00”
+         */
+        private String mac;
+        /**
+         * route
+         * 代表有线网络网关地址
+         * 默认值:”0.0.0.0”
+         */
+        private String route;
+        /**
+         * subnetMask
+         * 代表有线网络子网掩码地址
+         * 默认值:”0.0.0.0”
+         */
+        private String subnetMask;
+        // 最优Ip地址   以太网>无线网>3、4G
+        private String optimalIp;
+    }
+}

+ 126 - 0
app/src/main/java/xn/huaxue/update/http/bean/response/UpdateTask.java

@@ -0,0 +1,126 @@
+package xn.huaxue.update.http.bean.response;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UpdateTask {
+
+
+    /**
+     * code : 200
+     * message : 操作成功
+     * data : [{"taskId":"1866732769045250050","deviceCode":"EBOARD_FG5320499","apkUrl":"http://192.168.1.8/api/statics/bigFile/20241206/22e4d366-2df8-4a19-8002-d11b205a1ba3.apk","versionName":"1.3","startLaunchPackage":"com.dlc.xn.eboard","isForceUpdate":false}]
+     */
+
+    private int code;
+    private String message;
+    private List<Task> data = new ArrayList<>();
+
+    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<Task> getData() {
+        return data;
+    }
+
+    public void setData(List<Task> data) {
+        this.data = data;
+    }
+
+    public static class Task {
+        /**
+         * taskId : 1866732769045250050
+         * deviceCode : EBOARD_FG5320499
+         * apkUrl : http://192.168.1.8/api/statics/bigFile/20241206/22e4d366-2df8-4a19-8002-d11b205a1ba3.apk
+         * versionName : 1.3
+         * startLaunchPackage : com.dlc.xn.eboard
+         * isForceUpdate : false
+         */
+
+        private String taskId;
+        private String deviceCode;
+        private String apkUrl;
+        private String versionName;
+        // Apk包名
+        private String startLaunchPackage;
+        private boolean isForceUpdate;
+//        private String appPackageName = "xn.hxp";
+
+        public boolean isForceUpdate() {
+            return isForceUpdate;
+        }
+
+        public void setForceUpdate(boolean forceUpdate) {
+            isForceUpdate = forceUpdate;
+        }
+
+//        public String getAppPackageName() {
+//            return appPackageName;
+//        }
+//
+//        public void setAppPackageName(String appPackageName) {
+//            this.appPackageName = appPackageName;
+//        }
+
+        public String getTaskId() {
+            return taskId;
+        }
+
+        public void setTaskId(String taskId) {
+            this.taskId = taskId;
+        }
+
+        public String getDeviceCode() {
+            return deviceCode;
+        }
+
+        public void setDeviceCode(String deviceCode) {
+            this.deviceCode = deviceCode;
+        }
+
+        public String getApkUrl() {
+            return apkUrl;
+        }
+
+        public void setApkUrl(String apkUrl) {
+            this.apkUrl = apkUrl;
+        }
+
+        public String getVersionName() {
+            return versionName;
+        }
+
+        public void setVersionName(String versionName) {
+            this.versionName = versionName;
+        }
+
+        public String getStartLaunchPackage() {
+            return startLaunchPackage;
+        }
+
+        public void setStartLaunchPackage(String startLaunchPackage) {
+            this.startLaunchPackage = startLaunchPackage;
+        }
+
+        public boolean isIsForceUpdate() {
+            return isForceUpdate;
+        }
+
+        public void setIsForceUpdate(boolean isForceUpdate) {
+            this.isForceUpdate = isForceUpdate;
+        }
+    }
+}

+ 19 - 0
app/src/main/java/xn/huaxue/update/receiver/BootReceiver.java

@@ -0,0 +1,19 @@
+package xn.huaxue.update.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.blankj.utilcode.util.ActivityUtils;
+
+import xn.huaxue.update.MainActivity;
+
+
+public class BootReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+            ActivityUtils.startActivity(MainActivity.class);
+        }
+    }
+}

+ 26 - 0
app/src/main/java/xn/huaxue/update/receiver/ProcessReceiver.java

@@ -0,0 +1,26 @@
+package xn.huaxue.update.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.SPUtils;
+
+import xn.huaxue.update.constant.AppConstant;
+
+
+public class ProcessReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String aPackage = intent.getPackage();
+        // 其他应用发送过来的信息
+        if (null != aPackage && !TextUtils.isEmpty(aPackage) && !AppUtils.getAppPackageName().equals(aPackage)) {
+            boolean autoMaster = intent.getBooleanExtra("auto_master", false);
+            LogUtils.d("其它应用更新", autoMaster);
+            SPUtils.getInstance().put(AppConstant.AUTO_MASTER, autoMaster);
+        }
+    }
+}

+ 68 - 0
app/src/main/java/xn/huaxue/update/receiver/TimeTickReceiver.java

@@ -0,0 +1,68 @@
+package xn.huaxue.update.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.ShellUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+
+import xn.huaxue.update.Tool;
+
+
+public class TimeTickReceiver extends BroadcastReceiver {
+
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Intent newIntent = new Intent("XN_ACTION");
+        intent.putExtra("heartbeat", "heartbeat");
+        context.sendBroadcast(newIntent);
+        // 拉起主应用
+        startMasterApp();
+    }
+
+    private void startMasterApp() {
+        ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Object>() {
+            @Override
+            public Object doInBackground() throws Throwable {
+                try {
+                    ShellUtils.CommandResult commandResult = ShellUtils.execCmd("dumpsys activity activities", true);
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(commandResult.successMsg.getBytes())));
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        if (line.contains("mResumedActivity") || line.contains("mCurrentFocus")) {
+                            String[] itemArray = line.split(" ", -1);
+                            for (String item : itemArray) {
+                                if (item.contains("/")) {
+                                    String packageName = item.substring(0, item.indexOf("/"));
+                                    if (!"xn.hxp".equals(packageName)) {
+                                        LogUtils.d("定时拉起主应用");
+                                        Tool.INSTANCE.stopApp("xn.hxp");
+                                        Tool.INSTANCE.openApp("xn.hxp");
+                                    }
+                                    break;
+                                }
+                            }
+                            break;
+                        }
+                    }
+                } catch (Exception e) {
+                    LogUtils.e(Log.getStackTraceString(e));
+                }
+                return null;
+            }
+
+            @Override
+            public void onSuccess(Object result) {
+
+            }
+        });
+    }
+}

BIN
app/src/main/res/drawable-hdpi/ic_admin.png


BIN
app/src/main/res/drawable-hdpi/ic_back.png


BIN
app/src/main/res/drawable-hdpi/ic_celsius.png


BIN
app/src/main/res/drawable-hdpi/ic_cpu.png


BIN
app/src/main/res/drawable-hdpi/ic_eixt.png


BIN
app/src/main/res/drawable-hdpi/ic_et_err.png


BIN
app/src/main/res/drawable-hdpi/ic_folder.png


BIN
app/src/main/res/drawable-hdpi/ic_http.png


BIN
app/src/main/res/drawable-hdpi/ic_ip.png


BIN
app/src/main/res/drawable-hdpi/ic_mqtt.png


BIN
app/src/main/res/drawable-hdpi/ic_mqtt_account.png


BIN
app/src/main/res/drawable-hdpi/ic_mqtt_password.png


BIN
app/src/main/res/drawable-hdpi/ic_reboot.png


BIN
app/src/main/res/drawable-hdpi/ic_save.png


BIN
app/src/main/res/drawable-hdpi/ic_settings.png


BIN
app/src/main/res/drawable-hdpi/ic_sn.png


BIN
app/src/main/res/drawable-hdpi/ic_time.png


BIN
app/src/main/res/drawable-hdpi/ic_ver.png


BIN
app/src/main/res/drawable-mdpi/ic_admin.png


BIN
app/src/main/res/drawable-mdpi/ic_back.png


BIN
app/src/main/res/drawable-mdpi/ic_celsius.png


BIN
app/src/main/res/drawable-mdpi/ic_cpu.png


BIN
app/src/main/res/drawable-mdpi/ic_eixt.png


BIN
app/src/main/res/drawable-mdpi/ic_et_err.png


BIN
app/src/main/res/drawable-mdpi/ic_folder.png


BIN
app/src/main/res/drawable-mdpi/ic_http.png


BIN
app/src/main/res/drawable-mdpi/ic_ip.png


BIN
app/src/main/res/drawable-mdpi/ic_mqtt.png


BIN
app/src/main/res/drawable-mdpi/ic_mqtt_account.png


BIN
app/src/main/res/drawable-mdpi/ic_mqtt_password.png


BIN
app/src/main/res/drawable-mdpi/ic_reboot.png


BIN
app/src/main/res/drawable-mdpi/ic_save.png


BIN
app/src/main/res/drawable-mdpi/ic_settings.png


BIN
app/src/main/res/drawable-mdpi/ic_sn.png


BIN
app/src/main/res/drawable-mdpi/ic_time.png


BIN
app/src/main/res/drawable-mdpi/ic_ver.png


BIN
app/src/main/res/drawable-xhdpi/ic_admin.png


BIN
app/src/main/res/drawable-xhdpi/ic_back.png


BIN
app/src/main/res/drawable-xhdpi/ic_celsius.png


BIN
app/src/main/res/drawable-xhdpi/ic_cpu.png


BIN
app/src/main/res/drawable-xhdpi/ic_eixt.png


BIN
app/src/main/res/drawable-xhdpi/ic_et_err.png


BIN
app/src/main/res/drawable-xhdpi/ic_folder.png


BIN
app/src/main/res/drawable-xhdpi/ic_http.png


BIN
app/src/main/res/drawable-xhdpi/ic_ip.png


BIN
app/src/main/res/drawable-xhdpi/ic_mqtt.png


BIN
app/src/main/res/drawable-xhdpi/ic_mqtt_account.png


BIN
app/src/main/res/drawable-xhdpi/ic_mqtt_password.png


BIN
app/src/main/res/drawable-xhdpi/ic_reboot.png


BIN
app/src/main/res/drawable-xhdpi/ic_save.png


BIN
app/src/main/res/drawable-xhdpi/ic_settings.png


BIN
app/src/main/res/drawable-xhdpi/ic_sn.png


BIN
app/src/main/res/drawable-xhdpi/ic_time.png


BIN
app/src/main/res/drawable-xhdpi/ic_ver.png


BIN
app/src/main/res/drawable-xxhdpi/ic_admin.png


BIN
app/src/main/res/drawable-xxhdpi/ic_back.png


BIN
app/src/main/res/drawable-xxhdpi/ic_celsius.png


BIN
app/src/main/res/drawable-xxhdpi/ic_cpu.png


BIN
app/src/main/res/drawable-xxhdpi/ic_eixt.png


BIN
app/src/main/res/drawable-xxhdpi/ic_et_err.png


BIN
app/src/main/res/drawable-xxhdpi/ic_folder.png


BIN
app/src/main/res/drawable-xxhdpi/ic_http.png


BIN
app/src/main/res/drawable-xxhdpi/ic_ip.png


BIN
app/src/main/res/drawable-xxhdpi/ic_mqtt.png


BIN
app/src/main/res/drawable-xxhdpi/ic_mqtt_account.png


BIN
app/src/main/res/drawable-xxhdpi/ic_mqtt_password.png


BIN
app/src/main/res/drawable-xxhdpi/ic_reboot.png


BIN
app/src/main/res/drawable-xxhdpi/ic_save.png


BIN
app/src/main/res/drawable-xxhdpi/ic_settings.png


BIN
app/src/main/res/drawable-xxhdpi/ic_sn.png


BIN
app/src/main/res/drawable-xxhdpi/ic_time.png


BIN
app/src/main/res/drawable-xxhdpi/ic_ver.png


+ 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="#3DDC84"
+        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>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 30 - 0
app/src/main/res/drawable/ic_launcher_foreground.xml


+ 12 - 0
app/src/main/res/drawable/shape_rect.xml

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

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


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.